一 CADisplayLink、NSTimer使用注意
- CADisplayLink、NSTimer會(huì)對(duì)target產(chǎn)生強(qiáng)引用,如果target又對(duì)它們產(chǎn)生強(qiáng)引用,那么就會(huì)引發(fā)循環(huán)引用
示例代碼如下
CADisplayLink
@property (strong, nonatomic) CADisplayLink *link;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 保證調(diào)用頻率和屏幕的刷幀頻率一致,60FPS
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)linkTest {
NSLog(@"%s", __func__);
}
執(zhí)行幾秒后點(diǎn)擊退出當(dāng)前控制器
執(zhí)行結(jié)果如下

由打印結(jié)果可知,雖然已經(jīng)控制器已經(jīng)消失了,但是沒(méi)有調(diào)用其
dealloc方法,造成內(nèi)存泄露.
NSTimer
@property (strong, nonatomic) NSTimer *timer;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timerTest {
NSLog(@"%s", __func__);
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.timer invalidate];
}
執(zhí)行幾秒后點(diǎn)擊退出當(dāng)前控制器
執(zhí)行結(jié)果如下

由運(yùn)行結(jié)果可知,控制器已經(jīng)消失了,但是仍然沒(méi)有調(diào)用其
dealloc方法,導(dǎo)致內(nèi)存泄露。
解決方案
- 使用block
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
運(yùn)行結(jié)果

由運(yùn)行結(jié)果可知,控制器退出時(shí),調(diào)用了其
dealloc方法,不會(huì)造成內(nèi)存泄露。
- 使用代理對(duì)象(NSProxy)
CADisplayLink
Proxy類
// Proxy.h
@interface Proxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
// Proxy.m
@implementation Proxy
+ (instancetype)proxyWithTarget:(id)target {
Proxy *proxy = [[Proxy alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
@end
使用CADisplayLink
self.link = [CADisplayLink displayLinkWithTarget:[Proxy proxyWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
運(yùn)行結(jié)果

NSTimer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[Proxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
運(yùn)行結(jié)果

直接繼承NSProxy
// Proxy1.h文件
@interface Proxy1 : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
// Proxy1.m文件
@implementation Proxy1
+ (instancetype)proxyWithTarget:(id)target {
// NSProxy對(duì)象不需要調(diào)用init,因?yàn)樗緛?lái)就沒(méi)有init方法
Proxy1 *proxy = [Proxy1 alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
-
NSProxy的特點(diǎn)
Proxy *proxy1 = [Proxy proxyWithTarget:self];
Proxy1 *proxy2 = [Proxy1 proxyWithTarget:self];
NSLog(@"%d %d",[proxy1 isKindOfClass:[ViewController class]],[proxy2 isKindOfClass:[ViewController class]]);
運(yùn)行結(jié)果

分析:因?yàn)?code>Proxy1是繼承自
NSProxy,會(huì)直接進(jìn)行消息轉(zhuǎn)發(fā)機(jī)制,所以執(zhí)行[proxy2 isKindOfClass:[ViewController class]]),相當(dāng)于vc執(zhí)行執(zhí)行isKindOfClass方法,而isKindOfClass內(nèi)部也是進(jìn)行了消息轉(zhuǎn)發(fā),所以返回1。
二 GCD定時(shí)器
- NSTimer依賴于RunLoop,如果RunLoop的任務(wù)過(guò)于繁重,可能會(huì)導(dǎo)致NSTimer不準(zhǔn)時(shí)
- 而GCD的定時(shí)器會(huì)更加準(zhǔn)時(shí)
分裝GCD定時(shí)器類實(shí)例代碼如下
- CSTimer.h
@interface CSTimer : NSObject
+ (NSString *)execTask:(void(^)(void))task
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
+ (NSString *)execTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
+ (void)cancelTask:(NSString *)name;
@end
- CSTimer.m
@implementation CSTimer
// 保存定時(shí)器的字典
static NSMutableDictionary *timers_;
// 信號(hào)量
dispatch_semaphore_t semaphore_;
// 初始化操作
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
semaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString *)execTask:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async {
// 如果認(rèn)為不存在 開(kāi)始時(shí)間小于0 重復(fù)并且時(shí)間小于0 則返回空
if (!task || start < 0 || (interval <= 0 && repeats)) return nil;
// 隊(duì)列 - 是主隊(duì)列還是并發(fā)隊(duì)列
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
// 創(chuàng)建定時(shí)器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 設(shè)置時(shí)間
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC, 0);
// 保證線程安全
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定時(shí)器的唯一標(biāo)識(shí)
NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[name] = timer;
dispatch_semaphore_signal(semaphore_);
// 設(shè)置回調(diào)
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeats) { // 不重復(fù)的任務(wù)
[self cancelTask:name];
}
});
// 啟動(dòng)定時(shí)器
dispatch_resume(timer);
return name;
}
+ (NSString *)execTask:(id)target selector:(SEL)selector start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async {
if (!target || !selector) return nil;
return [self execTask:^{
if ([target respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:selector];
#pragma clang diagnostic pop
}
} start:start interval:interval repeats:repeats async:async];
}
// 取消定時(shí)器操作
+ (void)cancelTask:(NSString *)name {
if (name.length == 0) return;
// 線程安全
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = timers_[name];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:name];
}
dispatch_semaphore_signal(semaphore_);
}
外部調(diào)用
// 開(kāi)始定時(shí)器操作
- (void)startTimer {
// 1.使用block回調(diào)
self.task = [CSTimer execTask:^{
NSLog(@"111111 - %@", [NSThread currentThread]);
} start:2.0 interval:1.0 repeats:YES async:YES];
// 2.使用selector
// self.task = [CSTimer execTask:self selector:@selector(doTask) start:2.0 interval:1.0 repeats:YES async:YES];
}
// 定時(shí)執(zhí)行任務(wù)
- (void)doTask {
NSLog(@"doTask - %@", [NSThread currentThread]);
}
// 停止定時(shí)器
[CSTimer cancelTask:self.task];
本文參考:
路飛_Luck (http://www.itdecent.cn/p/07f7b96bb03f)
以及借鑒MJ的教程視頻
非常感謝.
項(xiàng)目連接地址 - MemoryManage-CADisplayLink+Timer