RunLoop优化Tableview多图加载性能及避免CFRunLoopRef循环引用

在tableView快速滑动同时加载很多图片的时候经常会帧数很低 ,通常是因为runloop在一次循环中处理很多图片导致。解决办法就是让runloop的每次循环只处理一张图片:

    CFRunLoopRef _rl = CFRunLoopGetCurrent();
    CFRunLoopMode _mode = kCFRunLoopCommonModes;
    CFRunLoopObserverRef _observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        if (self.tasks.count == 0) {
            return;
        }
        Action action = self.tasks.firstObject;
        [self.tasks removeObjectAtIndex:0];
        action();
    });
    CFRunLoopAddObserver(_rl, _observer, _mode);
    CFRelease(_observer);

会发现在这个viewController pop之后不会走dealloc,因为self强引用了CFRunLoopObserverRef,CFRunLoopObserverRef强引用handler,handler里又强引用了self,形成了一个retaincycle。而编译器并不会提示你。解决方法就是

    __weak typeof(self) weakSelf = self;
    CFRunLoopRef _rl = CFRunLoopGetCurrent();
    CFRunLoopMode _mode = kCFRunLoopCommonModes;
    CFRunLoopObserverRef _observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        if (weakSelf.tasks.count == 0) {
            return;
        }
        Action action = weakSelf.tasks.firstObject;
        [weakSelf.tasks removeObjectAtIndex:0];
        action();
    });
    CFRunLoopAddObserver(_rl, _observer, _mode);
    CFRelease(_observer);
CFRunLoopRemoveObserver(_rl, _observer, _mode)

但是第二种方法并没有一个好的调用时机


当runloop处于kCFRunLoopDefaultMode模式下的时候,如果没有事件处理,runloop就处在休眠状态就不去处理图片了,这时候一般来个Timer驱动,如果使用NSTimer或CADisplayLink repeats时NSTimer不会invalidate,所以会保持对target的强引用,会导致循环引用,解决方法可以参考YYFPSLabel的处理方式:

    _link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(tick:)];
    [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

使用另外一个对象YYWeakProxy作为CADisplayLink的target,YYWeakProxy同时持有一个self的弱引用。同时重写消息传递过程

- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}

这样当target调用方法的时候,会由于找不到方法而调用消息转发,_target就是弱引用的self,由self来调用方法
一图来解释:

从而打破引用循环

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • runtime 和 runloop 作为一个程序员进阶是必须的,也是非常重要的, 在面试过程中是经常会被问到的, ...
    made_China阅读 1,235评论 0 7
  • runtime 和 runloop 作为一个程序员进阶是必须的,也是非常重要的, 在面试过程中是经常会被问到的, ...
    SOI阅读 21,898评论 3 63
  • RunLoop 文章目录 RunLoop简介 1.1 什么是RunLoop? 1.2 RunLoop和线程 1.3...
    May_d8f1阅读 305评论 0 1
  • ios 常用的定时器有三种:NSTime,CADisplayLink和GCD。 NsTimer // 参数:Int...
    殿小七阅读 870评论 0 2
  • 前段时间有朋友问我,平时写哪一类的文章较多。 其实对于这个问题我没有定向的标准,看书写文都比较杂。有时上厕所手中一...
    文海珀阅读 3,717评论 79 103