iOS开发之笔记摘录

iOS高级进阶之RunLoop

2018-10-29  本文已影响0人  平安喜乐698
目录


简介

为了避免主线程阻塞导致界面卡顿,会创建子线程(任务执行完毕后则销毁)
    NSThread *thread=[[NSThread alloc]initWithBlock:^{
        // 任务...
    }];
    [thread start];


如果该任务需要频繁执行,频繁创建会消耗资源。
线程任务执行完毕后会进入死亡状态,不能再次开启。在线程内部死循环可以让线程保留,但又会疯狂地执行。
我们需要的是有任务再去执行,没任务则休眠。

解决机制:
    RunLoop是一个消息循环机制。它保证了线程不会退出,在没有消息时让线程休眠节约资源,在收到消息时唤醒线程处理任务。
RunLoop
iOS有两种RunLoop:
    1、NSRunLoop
      对CFRunLoopRef进行封装,面向对象API,非线程安全
    2、CFRunLoopRef
      在CoreFoundation框架(开源)内,纯C函数API,线程安全。
      

4种创建方式(不允许直接创建):
    1、[NSRunLoop currentRunLoop]; // 获取当前线程的RunLoop
    2、[NSRunLoop mainRunLoop];
    3、CFRunLoopGetMain();
    4、CFRunLoopGetCurrent();
3种运行方式:
    1、[runLoop run];
    不建议使用,除非希望子线程永远存在。
    Run Loop会永久性的运行NSDefaultRunLoopMode模式,CFRunLoopStop(runloopRef);无法停止RunLoop的运行
    2、[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60]];
    设置超时时间,运行NSDefaultRunLoopMode模式,CFRunLoopStop(runloopRef);无法停止RunLoop的运行
    3、[runLoop runMode:UITrackingRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:60]];
    在非Timer事件触发:CFRunLoopStop或limitDate可停止。
    Timer事件触发:无法停止
能正常运行的条件:
    1、至少包含一个Mode(RunLoop默认包含了DefaultMode)
    2、该Mode下需要有至少一个的事件源(Timer/Source)。
    3、运行在该Mode下。
    NSRunLoop只可以往mode中添加两类事件源:NSPort(对应的是source1)和NSTimer。
RunLoop可包含多个Mode。Mode由Source、Observer、Timer组成。
Mode有3种:
    1、
    kCFRunLoopDefaultMode(CFRunLoop)
    NSDefaultRunLoopMode(NSRunLoop)
    默认Mode(滚动时失效)
    2、    
    UITrackingRunLoopMode(CFRunLoop)
    NSEventTrackingRunLoopMode(NSRunLoop)
    仅在滚动时有效
    3、
    kCFRunLoopCommonModes(CFRunLoop)
    NSRunLoopCommonModes(NSRunLoop)
    组合模式:1+2
添加Mode:
    CFRunLoopAddCommonMode(CFRunLoopGetCurrent(), (CFStringRef)UITrackingRunLoopMode);

Source由source0和source1组成:
    1.source0
    例:UIEvent(触摸事件、滑动事件等),performSelector这种需要手动触发的操作。
    2.source1:
    系统内核的mach_msg事件(系统内部的端口事件)。了解即可。
    例:唤醒RunLoop或者让RunLoop进入休眠节省资源等。

Timer为定时源事件。
    NSTimer定时器的触发基于RunLoop。如果一个任务执行时间较长,那么当错过一个时间点后只能等到下一个时间点执行,并不会延后执行。

Observer
    相当于消息循环中的一个监听器,随时通知外部当前RunLoop的运行状态。只能通过CFRunLoop相关方法创建(NSRunLoop没有相关方法创建)。
    // 创建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
    });
    // 添加观察者:监听RunLoop的状态
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

    self.thread=[[NSThread alloc]initWithBlock:^{
        // 获取当前子线程的RunLoop
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        // 下面这一行必须加,否则RunLoop无法正常启用。
        // 给RunLoop添加了一个占位事件源,告诉RunLoop有事可做,让RunLoop运行起来。暂时这个事件源不会有具体的动作,而是要等RunLoop跑起来过后等有消息传递了才会有具体动作。
        [runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
        NSLog(@"RunLoop:%@",runLoop);
        // 让RunLoop跑起来
        [runLoop run];
    }];
    [self.thread start];

    // 
    [self performSelector:@selector(taskToDo) onThread:self.thread withObject:nil waitUntilDone:NO];

以下并非源码(对源码进行了可读性优化,便于理解)

/// 全局的Dictionary,key 是 线程, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
 
/// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
    OSSpinLockLock(&loopsLock);
    
    if (!loopsDic) {
        // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
        loopsDic = CFDictionaryCreateMutable();
        CFRunLoopRef mainLoop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
    }
    
    /// 直接从 Dictionary 里获取。
    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
     
    if (!loop) {
        /// 取不到时,创建一个
        loop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, thread, loop);
        /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
        _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
    }
  
    OSSpinLockUnLock(&loopsLock);
    return loop;
}
 
CFRunLoopRef CFRunLoopGetMain() {
    return _CFRunLoopGet(pthread_main_thread_np());
}
 
CFRunLoopRef CFRunLoopGetCurrent() {
    return _CFRunLoopGet(pthread_self());
}



可以看出
    1、线程默认不开启RunLoop(主线程除外)。RunLoop仅在第一次获取时创建,仅能在线程内部获取RunLoop。
    2、线程和RunLoop之间是一一对应的关系,对应关系保存在全局Dic中。

autoreleasepool

    NSThread和NSOperationQueue开辟子线程需要手动创建autoreleasepool。
    GCD开辟子线程不需要手动创建autoreleasepool,因为GCD的每个队列都会自行创建autoreleasepool。

    @autoreleasepool {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
        //[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
    }

解决UITableView有大量图片滚动时卡顿

    当tableView的cell上有大量从网络获取的图片时,异步线程会去加载图片,加载完成后主线程就会设置cell的图片,有可能会造成卡顿。
    将设置图片的任务在CFRunLoopDefaultMode下进行,滚动时不会执行
    [self.myImageView performSelector:@selector(setImage:)
                           withObject:[UIImage imageNamed:@""]
                           afterDelay:0
                              inModes:@[NSDefaultRunLoopMode]];
上一篇 下一篇

猜你喜欢

热点阅读