场景: 使用NSTimer会产生循环引用, 所以, 当对象应该被销毁的时候, 需要首先手动移除NSTimer, 这样, 该对象才对被销毁. 手动设置timer = nil.
timer之所以不能自动像其他对象跟随对象释放而释放
解决方案: 不让定时器强引用对象.
问题出在这里, 把target设置成self, 出现了循环引用. 如果target不设置成self, 就不会出现循环引用了.
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
创建另外一个对象作为定时间消息的接受者, 但是不实现@selector(timerTest)
, 然后通过消息转发, 再发消息转发到当前对象中执行.
@interface ProxyObj : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation ProxyObj
+ (instancetype)proxyWithTarget:(id)target
{
MJProxyObj *proxy = [[MJProxyObj alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end
- (id)forwardingTargetForSelector:(SEL)aSelector
因为ProxyObj最为定时器消息的接受者, 但是这个接受者并未实现定时器需要执行的方法, 所以会执行消息转发, 把这个消息转发到能够处理该消息的对象里, 很明显, 能够处理消息的就是ProxyObj.target
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[ProxyObj proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
@end
这样就可以办到timer不引用当前对象, 不会造成循环引用. 也不用担心timer何时释放了.
优化方法查找流程
继承自NSObject的进行消息转发会先从方法缓存列表中找, 找不到再找当前类对象的方法列表中找, 找不到, 再依次往父类中找, 最终找到NSObject的类对象,
依然找不到, 然后抛出动态方法解析, 动态方法解析没有实现, 再抛出消息转发, 此时才会真正去调用到定时器的方法
按照目前的流程, 必定是需要消息转发的, 但是还是先进行一番的方法查找, 最后才进行转发, 但是我们已经很清楚的知道, 根本不需要进行查找, 直接进行转发即可.
NSProxy: 专门做消息转发的, 只要调用到NSProxy里面的方法, 就会立即执行转发
@interface Proxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation Proxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy对象不需要调用init,因为它本来就没有init方法
MJProxy *proxy = [MJProxy alloc];
proxy.target = target;
return proxy;
}
// NSProxy 是专门做消息转发的, 只要调用到NSProxy里面的方法, 就会立即执行下面的函数
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end