一 CADisplayLink、NSTimer使用注意
- CADisplayLink吗冤、NSTimer會對target產(chǎn)生強引用又厉,如果target又對它們產(chǎn)生強引用九府,那么就會引發(fā)循環(huán)引用
示例代碼如下
CADisplayLink
@property (strong, nonatomic) CADisplayLink *link;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 保證調用頻率和屏幕的刷幀頻率一致,60FPS
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)linkTest {
NSLog(@"%s", __func__);
}
執(zhí)行幾秒后點擊退出當前控制器
執(zhí)行結果如下
由打印結果可知覆致,雖然已經(jīng)控制器已經(jīng)消失了侄旬,但是沒有調用其
dealloc
方法,造成內存泄露.
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í)行幾秒后點擊退出當前控制器
執(zhí)行結果如下
由運行結果可知煌妈,控制器已經(jīng)消失了儡羔,但是仍然沒有調用其
dealloc
方法,導致內存泄露璧诵。
解決方案
- 使用block
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
運行結果
由運行結果可知汰蜘,控制器退出時,調用了其
dealloc
方法之宿,不會造成內存泄露鉴扫。
- 使用代理對象(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];
運行結果
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];
運行結果
直接繼承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對象不需要調用init,因為它本來就沒有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
的特點
Proxy *proxy1 = [Proxy proxyWithTarget:self];
Proxy1 *proxy2 = [Proxy1 proxyWithTarget:self];
NSLog(@"%d %d",[proxy1 isKindOfClass:[ViewController class]],[proxy2 isKindOfClass:[ViewController class]]);
運行結果
分析:因為
Proxy1
是繼承自NSProxy
,會直接進行消息轉發(fā)機制澈缺,所以執(zhí)行[proxy2 isKindOfClass:[ViewController class]])
坪创,相當于vc
執(zhí)行執(zhí)行isKindOfClass
方法,而isKindOfClass
內部也是進行了消息轉發(fā)姐赡,所以返回1莱预。
二 GCD定時器
- NSTimer依賴于RunLoop,如果RunLoop的任務過于繁重项滑,可能會導致NSTimer不準時
- 而GCD的定時器會更加準時
分裝GCD定時器類實例代碼如下
- 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
// 保存定時器的字典
static NSMutableDictionary *timers_;
// 信號量
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 {
// 如果認為不存在 開始時間小于0 重復并且時間小于0 則返回空
if (!task || start < 0 || (interval <= 0 && repeats)) return nil;
// 隊列 - 是主隊列還是并發(fā)隊列
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
// 創(chuàng)建定時器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 設置時間
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);
// 定時器的唯一標識
NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[name] = timer;
dispatch_semaphore_signal(semaphore_);
// 設置回調
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeats) { // 不重復的任務
[self cancelTask:name];
}
});
// 啟動定時器
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];
}
// 取消定時器操作
+ (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_);
}
外部調用
// 開始定時器操作
- (void)startTimer {
// 1.使用block回調
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];
}
// 定時執(zhí)行任務
- (void)doTask {
NSLog(@"doTask - %@", [NSThread currentThread]);
}
// 停止定時器
[CSTimer cancelTask:self.task];
本文參考:
路飛_Luck (http://www.reibang.com/p/07f7b96bb03f)
以及借鑒MJ的教程視頻
非常感謝.
項目連接地址 - MemoryManage-CADisplayLink+Timer