1. 循环引证
问题代码
日常开发中,经常会用到NSTimer
定时器,一些不正确的写法,会导致NSTimer
形成循环引证,如下:
@interface TargetViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation TargetViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector: @selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void**)timerAction {
static uint64_t num = 0;
NSLog(@"%s, num = %@", __func__ , @(num));
num++;
}
- (void)dealloc {
[self.timer invalidate];
self.title = nil;
NSLog(@"%s", __func__);
}
@end
这种代码必然会形成循环引证:
-
创立
timer
时,将self
传入target
,导致timer
持有self
,而self
又持有timer
-
运用
timer
的invalidate
办法能够免除timer
对self
的持有,可是timer
持有self
,导致self
不可能调用dealloc
, 从而不能调用invalidate
故此两边彼此等候,形成循环引证
运用__weak
可行否
在解决 block
循环引证时,我们运用 __weak typeof(self) weakSelf = self;
,那么 针对tiemer
是不是也能够这样呢?修改代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = elf;
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector: @selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
运转代码,依然不行,为啥呢? 查看官文 :
-
target
:定时器触发时指定的音讯发送到的目标。计时器维护对该目标的强引证,直到它(计时器)失效 -
在
timerWithTimeInterval
内部,运用强引证目标接纳target
参数,所以这里在外部界说为弱引证目标没有任何含义, 类似于这种代码:__weak typeof(self) weakSelf = self; // 外部 __strong typeof(weakSelf)strongSelf = weakSelf; // 内部
和Block
的区别,Block
将捕获到的弱引证目标,内部赋值给一个弱引证的临时变量,当Block
履行完毕,临时变量会主动销毁,免除对外部变量的持有
2. 惯例解决办法
运用 带block
的 API
运用带着Block
的办法创立NSTimer
,防止target
的强持有
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block;
在恰当机遇调用invalidate
如:在didMoveToParentViewController
办法中
- (void)didMoveToParentViewController:(UIViewController *)parent{
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
}
}
3. 打断强持有
除了惯例解决方案,还能够经过打断target
的强持有,解决循环引证的问题
中介者形式
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *objc = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(timerAction), (IMP)timerActionObjc, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:objc selector:@selector(timerAction) userInfo:nil repeats:YES];
}
void timerActionObjc(id obj){
static uint64_t num = 0;
NSLog(@"%s - %@ - %@", __func__ , @(num), obj);
num++;
}
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s", **__func__** );
}
-
创立
NSObject
实例目标objc
,经过Runtime
对NSObject
增加timerAction
办法,IMP
指向timerActionObjc
的函数地址 -
创立
NSTimer
,将objc
传入target
参数,这样防止NSTimer
对self
的强持有 -
当页面退出时,由于
self
没有被NSTimer
持有,正常调用dealloc
办法- 在
dealloc
中,对NSTimer
进行开释。此刻NSTimer
对objc
的强持有免除,objc
也跟着开释
- 在
封装自界说Timer
创立YJWeakTimer
,完成自界说Timer
的封装
翻开YJWeakTimer.h
文件,写入以下代码:
#import <Foundation/Foundation.h>
@interface YJWeakTimer : NSObject
- (instancetype)yj_initWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats;
- (void)yj_invalidate;
@end
翻开YJWeakTimer.m
文件,写入以下代码:
#import "YJWeakTimer.h"
#import <objc/message.h>
@interface YJWeakTimer()
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation YJWeakTimer
- (instancetype)yj_initWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {
if (self == [super init]) {
self.target = target;
self.aSelector = selector;
if ([self.target respondsToSelector:self.aSelector])
Method method = class_getInstanceMethod([self.target class], selector);
const char *type = method_getTypeEncoding(method);
class_addMethod([self class], selector, (IMP)fireHomeWapper, type);
self.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:selector userInfo:userInfo repeats:repeats];
}
}
return self;
}
void fireHomeWapper(YJWeakTimer *wtimer){
if (wtimer.target) {
void (*yj_msgSend)(void *, SEL, id) = (void *)objc_msgSend;
yj_msgSend((__bridge void *)(wtimer.target), wtimer.aSelector, wtimer.timer);
} else {
[wtimer yj_invalidate];
}
}
- (void)yj_invalidate {
[self.timer invalidate];
self.timer = nil;
}
@end
YJWeakTimer
的运用:
- (void)viewDidLoad {
[super viewDidLoad];
self.weakTimer = [[YJWeakTimer alloc] yj_initWithTimeInterval:1 target:self selector: @selector(timerAction) userInfo:nil repeats:YES];
}
- (void)timerAction {
static uint64_t num = 0;
NSLog(@"%s, num = %@", __func__ , @(num));
num++;
}
-
在
YJWeakTimer
中界说yj_initWithTimeInterval
和yj_invalidate
办法-
YJWeakTimer
经过weak
润饰的target,对
ViewController`进行弱持有 -
检测
target
中是否能呼应selector
。能呼应,对当前类经过Runtime API
添加同名办法编号,指向本身内部fireHomeWapper
的函数地址 -
创立真正的
NSTimer
定时器,将控件本身的实例目标传入target
,防止NSTimer
对ViewController
强持有
-
-
当
NSTimer
回调时,会进入fireHomeWapper
函数- 函数内部不负责事务处理,假如
target
存在,运用objc_msgSend
,将音讯发送给target
本身下的selector
办法
- 函数内部不负责事务处理,假如
-
当页面退出时,
ViewController
能够正常开释。但YJWeakTimer
和NSTimer
彼此持有,两边都无法开释 -
由于两边都无法开释,
NSTimer
的回调会持续调用-
当进入
fireHomeWapper
函数,发现target
已经不存在了,调用YJWeakTimer
的yj_invalidate
办法,内部对NSTimer
进行开释 -
当
NSTimer
开释后,对YJWeakTimer
的强持有免除,YJWeakTimer
也跟着开释
-
NSProxy
虚基类
NSProxy
的效果:
-
OC
不支持多承继,可是它依据运转机遇制,能够经过NSProxy
来完成伪多承继 -
NSProxy
和NSObject
属于同一级其他类,也能够说是一个虚拟类,只完成了NSObject
的协议部分 -
NSProxy
实质是一个音讯转发封装的抽象类,类似一个代理人
能够经过承继NSProxy
,并重写以下两个办法完成音讯转发
- (void)forwardInvocation:(NSInvocation *)invocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
NSProxy
除了能够用于多承继,也能够作为堵截强持有的中间人
翻开YJProxy.h
文件,写入以下代码:
翻开YJProxy.m
文件,写入以下代码:
YJProxy
的调用运用:
- (void)viewDidLoad {
[uper viewDidLoad];
self.proxy = [YJProxy proxyWithTarget:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(timerAction) userInfo:nil repeats:YES];
}
- (void)timerAction {
static uint64_t num = 0;
NSLog(@"%s, num = %@", __func__ , @(num));
num++;
}
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s", **__func__** );
}
-
YJProxy
初始化办法,将传入的object
赋值给弱引证目标 -
在
UIViewController
中,创立YJProxy
目标proxy
。创立NSTimer
目标,将proxy
传入target
,防止NSTimer
对ViewController
强持有 -
当
NSTimer
回调时,触发YJProxy
的音讯转发办法-
methodSignatureForSelector
:设置办法签名 -
forwardInvocation
:本身不做事务处理,将音讯转发给object
-
-
当页面退出时,
ViewController
能够正常开释- 在
dealloc
中,对NSTimer
进行开释。此刻NSTimer
对proxy
的强持有免除,proxy
也跟着开释
- 在
总结:
循环引证:
-
创立
NSTimer
时,运用带有target
参数的办法,会对传入的目标进行强引证。假如传入的是持有timer
的目标,双发会彼此持有,形成循环引证 -
不能在
UIViewController
的dealloc
办法中开释timer
。只需timer
有用,UIViewController
的dealloc
办法就不会履行。故此两边彼此等候,谁都无法开释 -
对
NSTimer
的target
参数传入一个弱引证的self
没有任何含义,由于在创立NSTimer
的办法内部,运用强引证目标接纳target
参数
惯例解决方案:
-
运用带着
Block
的办法创立NSTimer
,防止target
的强持有 -
依据事务需求,在恰当机遇调用
invalidate
。例如:viewWillDisappear
、didMoveToParentViewController
堵截target
的强持有:
-
中介者形式
-
封装自界说
Timer
-
运用
NSProxy
虚基类