25-內(nèi)存管理之定時(shí)器

一 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é)果如下

1653926-2b14b150ac9547df.png

由打印結(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é)果如下

1653926-ecf4809af703c4af.png

由運(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é)果

1653926-fa507d3d69b333e9.png

由運(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é)果

1653926-244ab58c1b421c70.png
  • 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é)果

1653926-f64b7d101a1aa77b.png
直接繼承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é)果

1653926-1370f466197b108b.png

分析:因?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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容