定时器NSTimer、CADisplayLink及代理NSProxy的基本使用

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

Leave a Reply