1、NSTimer常用的两种方式:scheduled与使用Runloop addTimer
1 2 3 4 5 6 7 8 9 10 11 12 | [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"code..."); }]; [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES]; NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"code..."); }]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; NSTimer *timer = [NSTimer timerWithTimeInterval:1.9 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; |
2、CADisplayLink常用的方式是addToRunloop
1 2 | CADisplayLink *link = [CADisplayLink displayLinkWithTarget:[SAProxy proxyWithTarget:self] selector:@selector(linkMethod)]; [link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; |
3、这两个定时器的本质都是加入到runloop中循环调用,因此对象本身会被RunLoop引用,需要在业务上主动将其置为失效可释放,这样一来就容易使定时器自身与调用者形成循环引用的关系;
4、现在的问题是定时器在使用的时候会被外部引用,而定时器又可能直接引用了业务控制器,业务控制器需要在释放时将定时器销毁,可定时器引用了业务控制器,业务控制器不会被销毁;解决思路是避免定时器直接引用控制器,而是引用一个代理类,让代理类弱引用控制器,这样就可以解决循环引用的问题;
5、所谓的代理可以是任何对象,因为只需要完成所有的方法调用转发给业务器就行,参考:Objective-C消息发送机制原理objc_msgSend( ),也可以使用NSProxy,专业代理类;
6、NSProxy的作用也是消息转发,相比常规的类实现NSProxy省去了父类方法递归查找的过程,更高效,基本原理是相同的,实现methodSignatureForSelector:与forwardInvocation:方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @interface SAProxy : NSProxy @property (nonatomic, weak) id target; @end @implementation SAProxy + (instancetype)proxyWithTarget:(id)target { SAProxy *proxy = [SAProxy alloc]; proxy.target = target; return proxy; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.target methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:self.target]; } @end |
7、业务控制器参考代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | @interface ViewController () { NSTimer *_timer; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[SAProxy proxyWithTarget:self] selector:@selector(timerMethod) userInfo:nil repeats:YES]; } - (void)timerMethod { NSLog(@"%s", __func__); } - (void)dealloc { [_timer invalidate]; NSLog(@"%s", __func__); } @end |