概要
使用 NSProxy 优雅的解决 NSTimer 、CADisplayLink 可能造成的循环引用问题。
循环引用案例
@interface ViewController ()
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerTest)
userInfo:nil
repeats:YES];
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
@end
上面的代码,退出当前页的时候会造成循环引用,原因是创建timer的时候,target参数传入self,timer内部会强引用target,而self本身又强引用了timer。
查看GNUStep仿写的NSTimer源码,可以看到,timer内部是强引用target的。
使用NSProxy优雅解决
1. 创建一个继承NSProxy的子类
* 弱引用 target
// 类声明
@interface FYProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
// 类实现
@implementation FYProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy对象不需要调用init,因为它本来就没有init方法
FYProxy *proxy = [FYProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end
2. 构造timer时target参数传入NSProxy子类对象
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:[FYProxy proxyWithTarget:self]
selector:@selector(timerTest)
userInfo:nil
repeats:YES];
NSProxy原理
在给NSProxy对象发消息是,如果方法未实现会直接调用methodSignatureForSelector:
和forwardInvocation:
方法。在本例中,给NSProxy发送timerTest
消息时,在上述两个方法中直接调用NSProxy的target(即ViewController)的方法签名,并把实现传递给target,实际效果就是给target发送了timerTest
消息。
小结
在项目中新建一个NSProx子类对象,可以优雅的解决此类循环引用问题。