# NSTimer 中的内存泄露 - GCD 的 timer - NSProxy - 采用 Block 的形式为 NSTimer 增加分类 ```objective-c @interface ViewController() @property (nonatomic, strong) NSTimer *timer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(p_doSomeThing) userInfo:nil repeats:YES]; } - (void)p_doSomeThing { // doSomeThing } - (void)p_stopDoSomeThing { [self.timer invalidate]; self.timer = nil; } - (void)dealloc { [self.timer invalidate]; } @end ``` 上面的代码主要是利用定时器重复执行 p_doSomeThing 方法,在合适的时候调用 p_stopDoSomeThing 方法使定时器失效。 能看出问题吗?在开始讨论上面代码问题之前,需要对 NSTimer 做一点说明。NSTimer 的 `scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:` 方法的最后一个参数为 YES 时,NSTimer 会保留目标对象,等到自身失效才释放目标对象。执行完任务后,一次性的定时器会自动失效;重复性的定时器,需要主动调用 invalidate 方法才会失效。 当前的 VC 和 定时器互相引用,造成循环引用。 如果能在合适的时机打破循环引用就不会有问题了 1. 控制器不再强引用定时器 2. 定时器不再保留当前的控制器 ```objective-c //.h文件 #import @interface NSTimer (UnRetain) + (NSTimer *)lbp_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void(^)(NSTimer *timer))block; @end //.m文件 #import "NSTimer+SGLUnRetain.h" @implementation NSTimer (SGLUnRetain) + (NSTimer *)lbp_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block{ return [NSTimer scheduledTimerWithTimeInterval:inerval target:self selector:@selector(lbp_blcokInvoke:) userInfo:[block copy] repeats:repeats]; } + (void)lbp_blcokInvoke:(NSTimer *)timer { void (^block)(NSTimer *timer) = timer.userInfo; if (block) { block(timer); } } @end //控制器.m #import "ViewController.h" #import "NSTimer+UnRetain.h" //定义了一个__weak的self_weak_变量 #define weakifySelf \ __weak __typeof(&*self)weakSelf = self; //局域定义了一个__strong的self指针指向self_weak #define strongifySelf \ __strong __typeof(&*weakSelf)self = weakSelf; @interface ViewController () @property(nonatomic, strong) NSTimer *timer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; __block NSInteger i = 0; weakifySelf self.timer = [NSTimer lbp_scheduledTimerWithTimeInterval:0.1 repeats:YES block:^(NSTimer *timer) { strongifySelf [self p_doSomething]; NSLog(@"----------------"); if (i++ > 10) { [timer invalidate]; } }]; } - (void)p_doSomething { } - (void)dealloc { // 务必在当前线程调用invalidate方法,使得Runloop释放对timer的强引用(具体请参阅官方文档) [self.timer invalidate]; } @end ``` 上面的方法之所以能解决内存泄漏的问题,关键在于把保留转移到了定时器的类对象身上,这样就避免了实例对象被保留。 当我们谈到循环引用时,其实是指实例对象间的引用关系。类对象在 App 杀死时才会释放,在实际开发中几乎不用关注类对象的内存管理。下面的代码摘自苹果开源的 NSObject.mm 文件,从中可以看出,对于类对象,并不需要像实例对象那样进行内存管理。 ```objective-c + (id)retain { return (id)self; } // Replaced by ObjectAlloc - (id)retain { return ((id)self)->rootRetain(); } + (oneway void)release { } // Replaced by ObjectAlloc - (oneway void)release { ((id)self)->rootRelease(); } + (id)autorelease { return (id)self; } // Replaced by ObjectAlloc - (id)autorelease { return ((id)self)->rootAutorelease(); } + (NSUInteger)retainCount { return ULONG_MAX; } - (NSUInteger)retainCount { return ((id)self)->rootRetainCount(); } ``` iOS 10 中,定时器 api 增加了 block 方法,实现原理与此类似,这里采用分类为 NSTimer 增加 block 参数的方法,最终的行为一致