內(nèi)存管理 01 - NSTimer、CADisplayLink、GCD 定時(shí)器
使用 NSTimer、CADisplayLink 需要注意什么?
NSTimer、CADisplayLink 在失效前會(huì)一直對(duì) target 產(chǎn)生強(qiáng)引用,導(dǎo)致 target 沒(méi)法釋放。
NSTimer 的解決方式有三種:使用 NSProxy 中間對(duì)象、使用包裝類、通過(guò) Category 擴(kuò)展 NSTimer。
CADisplayLink 的解決方式有兩種:使用 NSProxy 中間對(duì)象、使用包裝類。
1. 使用 NSProxy 中間對(duì)象
- 將 NSTimer 的 target 指定為 NSProxy,NSProxy 中對(duì) 真正的 target 進(jìn)行弱引用。
- NSProxy 中利用消息轉(zhuǎn)發(fā)機(jī)制調(diào)用真正的 target 的 Selector;
使用 NSProxy 的優(yōu)勢(shì):
- NSProxy 在消息發(fā)送階段不會(huì)到父類中查找方法
- 消息發(fā)送失敗后也不會(huì)嘗試動(dòng)態(tài)方法解析。
- 消息發(fā)送失敗后直接進(jìn)行消息轉(zhuǎn)發(fā),并且直接調(diào)用 methodSignatureForSelector: 方法。
TargetProxy
@interface TargetProxy : NSProxy
@property (nonatomic, weak) id target;
+ (instancetype)targetProxyWithTarget:(id)target;
+
@end
@implementation TargetProxy
+ (instancetype)targetProxyWithTarget:(id)target {
TargetProxy *proxy = [self alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
TargetProxy 使用示例
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) CADisplayLink *displayLink;
@end
@implementation ViewController
- (void)dealloc {
[self.timer invalidate];
[self.displayLink invalidate];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:[TargetProxy targetProxyWithTarget:self]
selector:@selector(doWork)
userInfo:nil
repeats:YES];
self.displayLink = [CADisplayLink displayLinkWithTarget:[TargetProxy targetProxyWithTarget:self]
selector:@selector(doWork)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)doWork {
NSLog(@"%s", __func__);
}
@end
2. 包裝 CADisplayLink、NSTimer
- 將 NSTimer、CADisplayLink 作為屬性包裝到自定義類中。
- 通過(guò)包裝類對(duì)外提供 NSTimer、CADisplayLink 的功能。
NoRetainTimer
@interface NoRetainTimer : NSObject
@property (nonatomic, readonly, getter=isValid) BOOL valid;
+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
- (void)invalidate;
@end
@interface NoRetainTimer ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation NoRetainTimer
- (void)dealloc {
[self invalidate];
}
- (BOOL)isValid {
return self.timer.isValid;
}
+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block {
NoRetainTimer *instance = [[NoRetainTimer alloc] init];
instance.timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(__blockInvokeWithTimer:)
userInfo:[block copy]
repeats:repeats];
return instance;
}
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode {
[runloop addTimer:self.timer forMode:mode];
}
- (void)invalidate {
[self.timer invalidate];
}
+ (void)__blockInvokeWithTimer:(NSTimer *)timer {
void (^block)(NSTimer *timer) = timer.userInfo;
if (block) {
block(timer);
}
}
@end
NoRetainDisplayLink
@interface NoRetainDisplayLink : NSObject
@property (nonatomic, readonly, getter=isValid) BOOL valid;
+ (instancetype)displayLinkWithBlock:(void(^)(NoRetainDisplayLink *displayLink))block;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
- (void)invalidate;
@end
@interface NoRetainDisplayLink ()
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, readwrite, getter=isValid) BOOL valid;
@property (nonatomic, copy) void (^block)(NoRetainDisplayLink *displayLink);
@end
@implementation NoRetainDisplayLink
- (void)dealloc {
[self invalidate];
}
- (BOOL)isPaused {
return self.displayLink;
}
+ (instancetype)displayLinkWithBlock:(void(^)(NoRetainDisplayLink *displayLink))block {
NoRetainDisplayLink *instance = [[self alloc] init];
instance.valid = YES;
instance.block = block;
instance.displayLink = [CADisplayLink displayLinkWithTarget:instance selector:@selector(__blockInvoke)];
return instance;
}
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode {
[self.displayLink addToRunLoop:runloop forMode:NSRunLoopCommonModes];
}
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode {
[self.displayLink removeFromRunLoop:runloop forMode:NSRunLoopCommonModes];
}
- (void)invalidate {
instance.valid = NO;
[self.displayLink invalidate];
}
- (void)__blockInvoke {
self.block(self);
}
@end
NoRetainTimer & NoRetainDisplayLink 使用示例
@interface ViewController ()
@property (nonatomic, strong) NoRetainTimer *timer;
@property (nonatomic, strong) NoRetainDisplayLink *displayLink;
@end
@implementation ViewController
- (void)dealloc {
[self.timer invalidate];
[self.displayLink invalidate];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NoRetainTimer scheduledTimerWithTimeInterval:1.0
repeats:YES
block:^(NSTimer *timer) {
NSLog(@"%s", __func__);
}];
self.displayLink = [NoRetainDisplayLink displayLinkWithBlock:^(NoRetainDisplayLink *displayLink) {
NSLog(@"%s", __func__);
}];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
@end
3. 通過(guò) Category 擴(kuò)展 NSTimer
- 創(chuàng)建一個(gè) NSTimer 的分類,提供一個(gè) +scheduledTimerWithTimeInterval:repeats:block 方法。
- 將 NSTimer 的 target 指定為 [NSTimer class],selector 為 Category 中的一個(gè)類方法,并將 block 作為 NSTimer 的 userInfo。
- 當(dāng) Category 的類方法被 NSTimer 回調(diào)時(shí),取出 NSTimer 中的 block(userInfo),調(diào)用 block。
NSTimer+NoRetain
@interface NSTimer (NoRetain)
+ (instancetype)noretain_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block;
@end
@implementation NSTimer (NoRetain)
+ (instancetype)noretain_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block {
return [self scheduledTimerWithTimeInterval:ti
target:self
selector:@selector(__blockInvokeWithTimer:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)__blockInvokeWithTimer:(NSTimer *)timer {
void (^block)(NSTimer *timer) = timer.userInfo;
if (block) {
block(timer);
}
}
@end
NSTimer+NoRetain 使用示例
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)dealloc {
[self.timer invalidate];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer noretain_scheduledTimerWithTimeInterval:1.0
repeats:YES
block:^(NSTimer *timer) {
NSLog(@"%s", __func__);
}];
}
@end
GCD 定時(shí)器
- NSTimer 依賴于 RunLoop,當(dāng) RunLoop 的任務(wù)過(guò)于繁重時(shí),可能會(huì)導(dǎo)致 NSTimer 不準(zhǔn)時(shí)。
- GCD 定時(shí)器基于內(nèi)核,不依賴于 RunLoop,時(shí)間調(diào)度會(huì)非常準(zhǔn)時(shí)。
GCDTimer 實(shí)現(xiàn)
@interface GCDTimer : NSObject
@property (nonatomic, readonly, getter=isValid) BOOL valid;
+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
inBackground:(BOOL)background
block:(void (^)(GCDTimer *timer))block;
- (void)invalidate;
@end
@interface GCDTimer ()
@property (nonatomic, strong) dispatch_source_t timer;
@property (nonatomic, readwrite, getter=isValid) BOOL valid;
@property (nonatomic, copy) void (^block)(GCDTimer *displayLink);
@end
@implementation GCDTimer
- (void)dealloc {
[self invalidate];
}
+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
inBackground:(BOOL)background
block:(void (^)(GCDTimer *timer))block {
GCDTimer *instance = [[GCDTimer alloc] init];
// 隊(duì)列
dispatch_queue_t taskQueue = background
? dispatch_queue_create([GCDTimer.description cStringUsingEncoding:NSUTF8StringEncoding], DISPATCH_QUEUE_SERIAL)
: dispatch_get_main_queue();
// 創(chuàng)建定時(shí)器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, taskQueue);
// 設(shè)置時(shí)間
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * interval),
NSEC_PER_SEC * interval, 0);;
// 設(shè)置回調(diào)
__weak typeof(instance) weakInstance = instance;
dispatch_source_set_event_handler(timer, ^{
weakInstance.block(weakInstance);
// 不重復(fù)
if (!repeats) {
[weakInstance invalidate];
}
});
// 啟動(dòng)定時(shí)器
instance.timer = timer;
instance.block = block;
instance.valid = YES;
dispatch_resume(timer);
return instance;
}
- (void)invalidate {
self.valid = NO;
dispatch_cancel(self.timer);
}
GCDTimer 使用示例
@interface ViewController ()
@property (nonatomic, strong) GCDTimer *timer;
@end
@implementation ViewController
- (void)dealloc {
[self.timer invalidate];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [GCDTimer scheduledTimerWithTimeInterval:1.0 repeats:YES inBackground:YES block:^(GCDTimer *timer) {
NSLog(@"%s", __func__);
}];
}
@end