RunLoop(一)

作者: dandelionYD | 来源:发表于2019-04-12 09:01 被阅读10次

    面试题

    1.讲讲runloop,项目中有用?
    2.runloop的内部实现?
    3.runloop和线程的关系?
    4.timer和runloop的关系?
    5.程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决? 
    6.runloop是怎么响应用户操作的,具体流程是什么样的? 
    7.说说runLoop的几种状态 
    8.runloop的mode作用是什么? 
    

    在下面的文章里面,我们一一介绍
    本文Demo代码可见gitHub_Demo

    PS:如果想看更加具体的 详见:深入理解RunLoop

    • 什么是runloop?

      • 运行循环,在程序运行过程中循环做一些事情
    • 应用范畴

      • 定 时 器( Timer )、PerformSeIector
      • GCDAsynMainQueue
      • 事件响应、手势识别、界面刷新
      • 网络请求
      • AutoreleasePool
    • 有/没有runloop对象

      1.没有runloop
      int main(int argc, char * argv[]) {
          @autoreleasepool {
              NSLog(@"Hello world");
          }
      }
      //执行完后会退出程序
      
      2.有runloop
      int main(int argc, char * argv[]) {
          @autoreleasepool {
              return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
          }
      }
      
      3.伪代码
      int main(int argc, char * argv[]) {
          @autoreleasepool {
              int retVal = 0;
              do{
                  //睡眠中等待消息
                  int message = sleep_and_wait();
                  //处理消息
                  retVal = process_message(message);
              }while (retVal == 0);
              return 0;
          }
      }
      
    • 有了runloop之后,程序并不会退出,而是保持运行状态

    • Runloop的基本作用

      • 保持程序的持续运行
      • 处理app的各种事件
        • 手势识别
        • 界面更新
        • 定时器
        • PerformSelector
        • AutoreleasePool
        • 事件响应
        • 关于GCD
        • 网络请求
        • ...
      • 节省CPU资源,提高创新性能,该做事的时候做事,该休息的时候休息
    • RunLoop对象


      runloop_01.png

    https://opensource.apple.com/tarballs/CF/

    • RunLoop与线程

      我们先来看看底层的实现(这里我是根据上面地址下载【CF-1151.16】)

      CFRunLoop.c文件 
      ========================
      //全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
      static CFMutableDictionaryRef __CFRunLoops = NULL;
      
      //访问 loopsDic 时的锁
      static CFLock_t loopsLock = CFLockInit;
      
      //获取一个 pthread 对应的 RunLoop
      CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t){
          if (pthread_equal(t, kNilPthreadT)) {
              t = pthread_main_thread_np();
          }
          __CFLock(&loopsLock);
          if (!__CFRunLoops) { // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop
              __CFUnlock(&loopsLock);
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        
        CFRelease(mainLoop);
            __CFLock(&loopsLock);
        }
        
        //直接从 Dictionary 里获取
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
         __CFUnlock(&loopsLock);
          if (!loop) {
          //取不到时,创建一个
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
          __CFLock(&loopsLock);
          loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
          if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
          }
              // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
           __CFUnlock(&loopsLock);
        CFRelease(newLoop);
          }
          if (pthread_equal(t, pthread_self())) {
            _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
            if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            //注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop
             _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
              }
          }
          return loop;
      }
      
      • 从源码中我们可以看出
        • 每一条线程都有一个唯一的与之对应的Runloop对象
        • Runloop保存在一个全局的Dictionary里,线程作为key,Runloop作为value
        • 线程刚创建时并没有Runloop对象,Runloop会在第一次获取时创建
        • Runloop会在线程结束时销毁
        • 主线程的Runloop已经自动获取(创建),子线程默认没有开启Runloop
    • 获取RunLoop对象

      • Foundation

      • [NSRunLoop currentRunLoop] ;//获取当前线程的RunLoop对象

      • [NSRunLoop mainRunLoop]; //获取主线程的RunLoop对象

      • Core Foundation

        • CFRunLoopGetCu rrent() ;//获取当前线程的RunLoop对象

        • CFRunLoopGetMain(); //获取主线程的RunLoop对象

          //获取Runnloop对象
          NSRunLoop *runloop = [NSRunLoop currentRunLoop]; //OC
          CFRunLoopRef runloop2 = CFRunLoopGetCurrent(); //C
          NSLog(@"%p",runloop);
          NSLog(@"%p",runloop2);
          
          //%p 打印出来的地址不一样:因为OC是C的包装
          
    • RunLoop相关的类

      //类CFRunLoopRef(在CFRunLoop.h里)
      typedef struct __CFRunLoop * CFRunLoopRef; 
      
      //类CFRunLoopSourceRef(在CFRunLoop.h里)
      typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
      
      //类CFRunLoopObserverRef(在CFRunLoop.h里)
      typedef struct __CFRunLoopObserver * CFRunLoopObserverRef; 
      
      //类CFRunLoopTimerRef(在CFRunLoop.h里)
      typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
      
      //类CFRunLoopModeRef(在CFRunLoop.c里)
      //(这个类并没有暴露出来,只是通过 CFRunLoopRef的接口进行了封装)
      typedef struct __CFRunLoopMode *CFRunLoopModeRef;
      
      struct __CFRunLoopMode {
          CFRuntimeBase _base;
          pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
          CFStringRef _name;
          Boolean _stopped;
          char _padding[3];
          CFMutableSetRef _sources0;
          CFMutableSetRef _sources1;
          CFMutableArrayRef _observers;
          CFMutableArrayRef _timers;
          CFMutableDictionaryRef _portToV1SourceMap;
          __CFPortSet _portSet;
          CFIndex _observerMask;
      #if USE_DISPATCH_SOURCE_FOR_TIMERS
          dispatch_source_t _timerSource;
          dispatch_queue_t _queue;
          Boolean _timerFired; // set to true by the source when a timer has fired
          Boolean _dispatchTimerArmed;
      #endif
      #if USE_MK_TIMER_TOO
          mach_port_t _timerPort;
          Boolean _mkTimerArmed;
      #endif
      #if DEPLOYMENT_TARGET_WINDOWS
          DWORD _msgQMask;
          void (*_msgPump)(void);
      #endif
          uint64_t _timerSoftDeadline; /* TSR */
          uint64_t _timerHardDeadline; /* TSR */
      };
      
      //typedef struct __CFRunLoop * CFRunLoopRef; (暴露在.h文件外面的)
      
      struct __CFRunLoop {
          CFRuntimeBase _base;
          pthread_mutex_t _lock;            /* locked for accessing mode list */
          __CFPort _wakeUpPort;         // used for CFRunLoopWakeUp 
          Boolean _unused;
          volatile _per_run_data *_perRunData;  // reset for runs of the run loop
          pthread_t _pthread;
          uint32_t _winthread;
          CFMutableSetRef _commonModes;
          CFMutableSetRef _commonModeItems;
          CFRunLoopModeRef _currentMode;
          CFMutableSetRef _modes;
          struct _block_item *_blocks_head;
          struct _block_item *_blocks_tail;
          CFAbsoluteTime _runTime;
          CFAbsoluteTime _sleepTime;
          CFTypeRef _counterpart;
      };
      
    runloop_02.png

    下面我们具体的分析

    • CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1

      • Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件
      • Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程
    • CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

    • CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:

      //CFRunLoop.h LINE--58
      /* Run Loop Observer Activities */
      typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
          kCFRunLoopEntry = (1UL << 0),// 即将进入Loop
          kCFRunLoopBeforeTimers = (1UL << 1),// 即将处理 Timer
          kCFRunLoopBeforeSources = (1UL << 2),// 即将处理 Source
          kCFRunLoopBeforeWaiting = (1UL << 5),// 即将进入休眠
          kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
          kCFRunLoopExit = (1UL << 7),// 即将退出Loop
          kCFRunLoopAllActivities = 0x0FFFFFFFU
      };
      
    • CFRunLoopModeRef

      CFRunLoopMode 和 CFRunLoop 的结构大致如下:
      struct __CFRunLoopMode {
         CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"
         CFMutableSetRef _sources0;    // Set
         CFMutableSetRef _sources1;    // Set
         CFMutableArrayRef _observers; // Array
         CFMutableArrayRef _timers;    // Array
         ...
      };
      
      struct __CFRunLoop {
         CFMutableSetRef _commonModes;     // Set
         CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
         CFRunLoopModeRef _currentMode;    // Current Runloop Mode
         CFMutableSetRef _modes;           // Set
         ...
      };
      
      这里有个概念叫 “CommonModes”:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里
      
      CFRunLoop对外暴露的管理 Mode 接口只有下面2个:
       1.CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
       2.CFRunLoopRunInMode(CFStringRef modeName, ...);
       
      Mode 暴露的管理 mode item 的接口有下面几个:
       1. CFRunLoopContainsSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);
       2. void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);
       3. void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);
      
       4. Boolean CFRunLoopContainsObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);
       5. void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);
       6. void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);
      
       7. Boolean CFRunLoopContainsTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
       8. void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
       9. void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
      
      • CFRunLoopModeRef代表RunLoop的运行模式

      • 一个RunLoop包含若干个Mode,每个Mode又包含若干个 Source0/Source1/Observer/Timer

      • RunLoop启动时只能选择其中一个Mode,作为currentMode

      • 如果需要切换Mode,只能退出当前的Loop,再重新选择一个mode进入

        • 不同组的Source0/Source1/Observer/Timer能分隔开来,互不影响
      • 如果mode里面没有任何Source0/Source1/Observer/Timer,RunLoop会立马退出

      • 常见的2种mode

        • kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行的
        • UITrackingRunLoopMode:界面跟踪的Mode,用于scrollview追踪触摸滚动,保证界面滑动时不受其他的Mode影响
      • kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode


    下面我们通过代码来看看

    • CFRunLoopObserverRef
    -(void)creatObserver1{
        //创建Observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
        // 添加Observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        // 释放
        CFRelease(observer);
    }
    
    //监听的方法
    void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
    {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"kCFRunLoopEntry--即将进入Loop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"kCFRunLoopBeforeTimers--即将处理 Timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"kCFRunLoopBeforeSources--即将处理 Source");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"kCFRunLoopBeforeWaiting-- 即将进入休眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"kCFRunLoopAfter-(void)creatObserver2{Waiting-- 刚从休眠中唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"kCFRunLoopExit--即将退出Loop");
                break;
            default:
                break;
        }
    }
    
    ============================================================
    -(void)creatObserver2{
        //创建Observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
                case kCFRunLoopEntry: {
                    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                    NSLog(@"kCFRunLoopEntry - 即将进入Loop %@", mode);
                    CFRelease(mode);
                    break;
                }
                case kCFRunLoopBeforeTimers: {
                    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                    NSLog(@"kCFRunLoopBeforeTimers - 即将处理 Timer %@", mode);
                    CFRelease(mode);
                    break;
                }
                    
                case kCFRunLoopBeforeSources: {
                    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                    NSLog(@"kCFRunLoopBeforeSources -即将处理 Source  %@", mode);
                    CFRelease(mode);
                    break;
                }
                    
                case kCFRunLoopBeforeWaiting: {
                    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                    NSLog(@"kCFRunLoopBeforeWaiting - 即将进入休眠 %@", mode);
                    CFRelease(mode);
                    break;
                }
                    
                case kCFRunLoopAfterWaiting: {
                    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                    NSLog(@"kCFRunLoopAfterWaiting - 刚从休眠中唤醒 %@", mode);
                    CFRelease(mode);
                    break;
                }
                    
                case kCFRunLoopExit: {
                    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                    NSLog(@"kCFRunLoopExit - 即将退出Loop %@", mode);
                    CFRelease(mode);
                    break;
                }
                default:
                    break;
            }
        });
        // 添加Observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        // 释放
        CFRelease(observer);
    }
    

    友情链接:

    相关文章

      网友评论

        本文标题:RunLoop(一)

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