一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第23天,点击查看活动详情。
消息转发 -> 使用中间件NSProxy
同样的,我们可以使用一个中间件,使NSTimer强引用中间件,而中间件弱引用UIViewController,从而打破引用环。
对于中间件,我们选用比NSObject更为轻量级的NSProxy。
中间件的实现代码如下:
@interface ORCWeakProxy : NSProxy
@property (nonatomic, readonly, weak) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation ORCWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[self.class alloc] initWithTarget:target];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [self.target respondsToSelector:aSelector];
}
// 转发目标选择器
- (id)forwardingTargetForSelector:(SEL)selector {
return self.target;
}
// 函数执行器
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
// 方法签名的选择器
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
@end
使用时的代码如下:
ORCWeakProxy *proxy = [ORCWeakProxy proxyWithTarget:aTarget];
self.timer = [NSTimer scheduledTimerWithTimeInterval:ti
target:proxy
selector:aSelector
userInfo:nil
repeats:YES];
中间件的加入,使得NSTimer的target成为中间件,而中间件对UIViewController是弱引用,所以UIViewController可以被释放。
因为NSTimer的target成为中间件,所以NSTimer在执行定时任务时,会调用中间件的selector方法,然而中间件肯定是没有这个selector方法的,所以我们需要在中间件中进行方法的转发。我们使用forwardingTargetForSelector:使得响应selector方法的对象转移为self.target,即UIViewController。
既然方法已经被转发了,后续的消息转发接口就不会被执行了,那么我们还有必要重写methodSignatureForSelector:和forwardInvocation:吗?
答案是:很有必要! 。因为,当UIViewController被释放之后,就会出现在target上找不到selector,如果不重写,那么恭喜你,崩溃等着你(手动坏笑)!不过这两个方法只要实现,随便写写就行,只要有,其它都不要求。
不过使用这种方式,有以下几点需要注意:
NSProxy是一个虚类,所以我们无法直接使用,而是创建一个该类的子类。- 重要的事情又来了,在
UIViewController的dealloc方法中,记得加上[self.timer invalidate],谢谢(手动微笑)!如果不手动释放NSTimer,NSTimer依旧会持续执行定时任务,虽然你看不到,但它就在那里执行。有兴趣的小伙伴可以在中间件的forwardInvocation:打个断点试试。
中间件解决NSTimer的循环引用问题的目的已经达到了。但是在每个使用的地方,都需要引入ORCWeakProxy的头文件,这让我用起来有点不乐意啊!所以我们又添加一个NSTimer分类,代码如下:
@interface NSTimer (NoRetainCycleWithProxy)
@end
@implementation NSTimer (NoRetainCycleWithProxy)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self orc_exchangeSelector:@selector(scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:)
toSelector:@selector(orc_scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:)];
[self orc_exchangeSelector:@selector(timerWithTimeInterval:target:selector:userInfo:repeats:)
toSelector:@selector(orc_timerWithTimeInterval:target:selector:userInfo:repeats:)];
});
}
+ (NSTimer *)orc_timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {
return [self orc_timerWithTimeInterval:ti
target:[ORCWeakProxy proxyWithTarget:aTarget]
selector:aSelector
userInfo:userInfo
repeats:yesOrNo];
}
+ (NSTimer *)orc_scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {
return [self orc_scheduledTimerWithTimeInterval:ti
target:[ORCWeakProxy proxyWithTarget:aTarget]
selector:aSelector
userInfo:userInfo
repeats:yesOrNo];
}
@end
我们在分类里交换了创建NSTimer的方法,在新的方法里来创建NSTimer,并将中间件作为target传给这个NSTimer。这样,我们就可以无感知的使用中间件来解决NSTimer的循环引用问题了(nice)!
其实写到这里,关于消息转发 -> 使用中间件NSProxy来处理NSTimer的循环引用问题,就已经写完了。但是说实话,总是被提醒:要我们手动释放NSTimer,否则就会造成内存泄漏!这让我怎么的都不是很爽。秉着如果觉得不爽,那就要让自己爽起来的态度,我又在中间件中加了一些小东西:
- 为
ORCWeakProxy添加属性timer。 - 在
forwardingTargetForSelector:对target进行判断,如果target为nil则认为target已经被释放,这个时候就释放timer。
代码片段如下:
@interface ORCWeakProxy : NSProxy
@property (nonatomic, readonly, weak) id target;
@property (nonatomic, weak) id timer;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
- (id)forwardingTargetForSelector:(SEL)selector {
// 如果发现target为nil后,就停掉timer
if (self.target == nil) {
[self.timer invalidate];
}
return self.target;
}
timer属性使用weak修饰,避免出现循环引用问题。target为nil后,调用invalidate方法释放timer,那么就不需要再在考虑UIViewController被释放,而NSTimer没被释放的问题了(手动开心)。
以上,我们在使用NSTimer时,只需要调用接口创建NSTimer对象,并使用它满足我们的各种需求,而不再需要去关心,它会不会在满足我们的需求之后,对我们造成什么不好的影响了。