Files
knowledge-kit/Chapter1 - iOS/1.45.md
2020-02-25 17:46:51 +08:00

171 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <Foundation/Foundation.h>
@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 参数的方法,最终的行为一致