iOS高级进阶之RunLoop

作者: 平安喜乐698 | 来源:发表于2018-10-29 11:43 被阅读0次
    目录
    
    
    

    简介

    为了避免主线程阻塞导致界面卡顿,会创建子线程(任务执行完毕后则销毁)
        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]];
    

    相关文章

      网友评论

        本文标题:iOS高级进阶之RunLoop

        本文链接:https://www.haomeiwen.com/subject/cfivtqtx.html