NSProxy 做代理方法(解决 NSTimer 循环引用)

文章目录
  1. 1. 解决循环引用的方法
  2. 2. 使用block
  3. 3. 使用代理对象(NSProxy)
  4. 4. GCD timer

CADisplayLinkNSTimer 会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用

两个定时器的差别

  1. CADisplayLink 是页面的刷新频率,如果掉帧,他的频率也会变低
  2. NSTimer 是放在 RunLoop 里面的

解决循环引用的方法

1
2
3
4
5
6
7
8
9
__weak typeof(self) weakSelf = self;
// 只有闭包才会拿到强引用
// strong ptr = weakptr 是没有用的
// weak ptr = strongptr 是弱引用

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(executeTimer) userInfo:nil repeats:YES];

self.link = [CADisplayLink displayLinkWithTarget:weakSelf selector:@selector(executeTimer)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

cycle reference

使用block

block

1
2
3
[NSTimer scheduledTimerWithTimeInterval:1 repeats:true block:^(NSTimer * _Nonnull timer) {
[weakSelf executeTimer]
}];

使用代理对象(NSProxy)

proxy

NSProxy 本身就是用来作消息转发的

1
2
3
4
5
6
7
8
9
10
11
12
@interface NSProxy <NSObject> {
Class isa;
}
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
- (void)dealloc;
- (void)finalize;
+ (BOOL)respondsToSelector:(SEL)aSelector;
- (BOOL)allowsWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
- (BOOL)retainWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
// - (id)forwardingTargetForSelector:(SEL)aSelector;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@implementation MyProxy 
+ (instancetype)proxyWithTarget:(id)target {
// NSProxy对象不需要调用init,因为它本来就没有init方法
MyProxy *proxy = [MyProxy alloc];
proxy.target = target;
return proxy;
}

// 找到需要代理的 方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}

// 直接调用
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end

如果使用 NSObject 作代理

1
2
3
4
5
6
7
8
9
10
11
@implementation MyProxy
+ (instancetype)proxyWithTarget:(id)target {
MyProxy *proxy = [[MyProxy alloc] init];
proxy.target = target;
return proxy;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
@end

两者有什么差别呢?
使用 NSObject 要执行所有的消息查找流程,而是用NSProxy 他会直接找到target对应消息

NSProxy 就只做消息转发提供的类,他的大部分方法都是通过消息转发找到 target 的消息列表里

GCD timer

  1. 跟RunLoop 没关,是系统时钟
  2. tracingMode 当然也没有影响
  3. 精准
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dispatch_queue_t queue = dispatch_queue_create("timer", DISPATCH_QUEUE_SERIAL); 
// 创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

uint64_t start = 2.0; // 2秒后开始执行
uint64_t interval = 1.0; // 每隔1秒执行
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC, 0);

dispatch_source_set_event_handler_f(timer, fire);

// 启动定时器
dispatch_resume(timer);

// 取消定时器
dispatch_source_cancel(timer);