在iOS开发中我们经常会使用NSTimer,但是在使用过程中会遇到一个比较头疼的问题就是循环引用的问题,导致NSTimer所在的类不销毁,定时器也不会停止。
如何解决循环引用
导致循环引用的原因是:在创建NSTimer对象的时候需要指定一个target,而target我们一般直接使用self,当self强引用NSTimer对象就会导致循环引用。
所以解决办法就是:把循环引用的闭环断开。
针对这个问题的几种解决方案
方案一(推荐,使用方便)
NSTimer分类的实现:
@implementation NSTimer (XTTimer)
+ (NSTimer *)xt_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(void))block {
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(rw_timerBlockHandle:) userInfo:block repeats:repeats];
}
+ (void)xt_timerBlockHandle:(NSTimer *)timer {
void(^ timerBlock)(void) = timer.userInfo;
if (timerBlock) {
timerBlock();
}
}
@end
使用:
- (void)createTimer1 {
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer xt_scheduledTimerWithTimeInterval:1.0 repeats:YES block:^{
[weakSelf doSomething];
}];
}
- 解释:timer的持有者强引用timer这个对象,而timer强引用的是NSTimer的类对象,没有造成循环引用。
- 注意:在使用的时候block中使用self需要用__weak修饰,最后在dealloc方法里面调用
[timer invalidate];
。
其实在iOS10以后,官方就已经提供了一个新的方法+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
这个方法就可以解决循环引用的问题,但是要iOS10以后才能使用。
方案二
- (void)createTimer2 {
self.target = [NSObject new];
class_addMethod([self.target class], @selector(doSomething), class_getMethodImplementation([self class], @selector(doSomething)), "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self.target selector:@selector(doSomething) userInfo:nil repeats:YES];
}
实现思路:首先运用runtime给NSObject动态添加一个方法- (void)doSomething
,并且这个方法实现的指针指向[self class]
类中的- (void)doSomething
,然后指定self.target这个对象为NSTimer的target即可。当定时器回调的时候就会调用- (void)doSomething
方法。
方案三
@interface XTProxy : NSProxy
@property (nonatomic, weak) id target;
@end
@implementation XTProxy
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
使用:
- (void)createTimer3 {
self.proxy = [XTProxy alloc];
self.proxy.target = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self.proxy selector:@selector(doSomething) userInfo:nil repeats:YES];
}
这个方案的实现方式主要是加入了一个中间者proxy,使得timer不直接持有self,而是持有proxy,让proxy对象弱引用self来解决循环引用。当定时器回调的时候,通过消息转发机制,把消息重定向给self。
NSProxy拓展
官方文档:
Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or transform itself into) the real object. Subclasses of NSProxy can be used to implement transparent distributed messaging (for example, NSDistantObject) or for lazy instantiation of objects that are expensive to create.
NSProxy implements the basic methods required of a root class, including those defined in the NSObject protocol. However, as an abstract class it doesn’t provide an initialization method, and it raises an exception upon receiving any message it doesn’t respond to. A concrete subclass must therefore provide an initialization or creation method and override the forwardInvocation: and methodSignatureForSelector: methods to handle messages that it doesn’t implement itself. A subclass’s implementation of forwardInvocation: should do whatever is needed to process the invocation, such as forwarding the invocation over the network or loading the real object and passing it the invocation. methodSignatureForSelector: is required to provide argument type information for a given message; a subclass’s implementation should be able to determine the argument types for the messages it needs to forward and should construct an NSMethodSignature object accordingly. See the NSDistantObject, NSInvocation, and NSMethodSignature class specifications for more information.
NSProxy是一个用来做消息转发的抽象类,使用时需写一个子类继承自NSProxy并且子类需要实现两个方法,- (void)forwardInvocation:(NSInvocation *)invocation;
和- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
。
当NSProxy对象发送消息时,会跳过查找方法实现、动态方法解析、被援接受者几个步骤直接进行消息的重定向,所以相比较NSObject的消息转发而言,NSProxy减少了几个步骤,效率更高性能更优。
如有写得不对的地方或其他任何问题,欢迎私信或者留言一起交流。
欢迎大家点赞点关注,后续会持续更新文章~~~