zoukankan      html  css  js  c++  java
  • Runloop源码

    源码地址: https://opensource.apple.com/tarballs/CF/
    官方文档介绍: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW3

    RunLoop图解

    • 从下面这张图中可以看到一个RunLoop相关的元素主要有这些,下面的循环对应的就是RunLoop,在应用程序启动之后会首先在主线程开启一个RunLoop循环,不段来处理程序的timer事件,来自用户或系统的MatchPort的source事件,知道它超时或结束.

    • 它的左侧有一个容器形状的线程,很形象生动表达了当前的RunLoop被线程所拥有,线程销毁则这个RunLoop也会被销毁

    • runUntilDate:右边的黄色箭头断开后链接说明RunLoop超时会退出循环

    • 右边代表来自各个线程的sources事件

    RunLoopMode

    • 它主要是为当前的运行循环提供一个特性的执行模式,所有的事件只有在他们所支持的模式下运行,默认情况下RunLoop是在kCFRunLoopDefaultMode下运行,RunLoop在微观上每次只能执行,如果系统有一些优先级较高的事件一直占用某个Mode那么就会阻塞其他Mode上注册的事件,RunLoop也提供了解决方案,将事件注册到CommonMode中那么它会在每次RunLoop循环周期都会去检查是否有未处理的事件需要执行,当某个事件需要在多个Mode下执行的时候可以考虑将其加入到CommonModes中。 C struct __CFRunLoopMode {
      CFRuntimeBase _base;
      pthread_mutex_t _lock; /* must have the run loop locked before locking this */
      CFStringRef _name; //Mode名称
      Boolean _stopped; //Mode是否已经停止工作
      char _padding[3];
      CFMutableSetRef _sources0; //Source0事件集合,用户相关的事件
      CFMutableSetRef _sources1; //Source1事件集合,来自MatchPort内核相关的事件,如IOKit的事件分发
      CFMutableArrayRef _observers; //观察者
      CFMutableArrayRef _timers; //timer事件
      CFMutableDictionaryRef _portToV1SourceMap;
      __CFPortSet _portSet; //用于接收来自MatchPort的事件
      CFIndex _observerMask;
      mach_port_t _timerPort; //用于接收来自timerPort的事件
      Boolean _mkTimerArmed;
      uint64_t _timerSoftDeadline; /* TSR */
      uint64_t _timerHardDeadline; /* TSR */
      };
    • 系统常用的Mode如下

    InputSource

    • Input Source异步的发送事件到当前线程,输入源的事件由它的类型决定,通常情况下由两种类型。
      • 一种是监听应用程序Match端口的输入,另外一种则是监听用户自定义事件.
      • 对于运行循环而言,输入源是基于端口或者是自定义并不重,操作系统会实现两种输入源类型,取决于他们如何被触发,基于端口的source1是由kernel自动触发的
      • 自定义的事件source0只能由其他线程手动去触发
    • Port-Based Sources: Cocoa和CoreFunction框架提供了一些类支持我们创建基于端口的事件源,如NSPort,(CFMachPortRef, CFMessagePortRef, or CFSocketRef)
    • Custom Input Sources: CoreFunction提供了CFRunLoopSourceRef,可以通过它来创建自定义的输入源,需要设置如何处理将要执行的事件,怎样关闭source当它从RunLoop中移除时,同时也需要对它的事件机制进行定义
    • Cocoa Perform Selector Sources: Cocoa框架实现了一一个自定义的输入源允许直接在某个线程上执行方法,利用多线程协同处理的方式很大的程度上减少了主线程的阻塞

      • 需要主意的是,当在某个线程执行方法时,这个线程需要有自己的RunLoop,如果是子线程需要自己手动的开启RunLoop.
      • 可以在应用程序启动之后就开启这个线程的RunLoop,这样就能够处理队列上的所有方法
      • 常用的方法如下
        ```Objective-C
        performSelectorOnMainThread:withObject:waitUntilDone
        performSelectorOnMainThread:withObject:waitUntilDone:modes:

      performSelector:onThread:withObject:waitUntilDone:
      performSelector:onThread:withObject:waitUntilDone:modes:

      performSelector:withObject:afterDelay:

      performSelector:withObject:afterDelay:inModes:

      cancelPreviousPerformRequestsWithTarget:
      cancelPreviousPerformRequestsWithTarget:selector:object:

      - __CFRunLoopSource
      ```Objective-C
      struct __CFRunLoopSource {
      CFRuntimeBase _base;
      uint32_t _bits;
      pthread_mutex_t _lock;
      CFIndex _order; /* immutable */
      CFMutableBagRef _runLoops;
      union {
      CFRunLoopSourceContext version0; /* immutable, except invalidation */
      CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
      } _context;
      };

      TimerSources

      • 事件源会在指定的时间同步的发送一个事件到当前线程,它是线程自我通知的一种实现方式,通常可以用来做连续事件的过滤处理,比如频繁的点击,频繁的文本搜索输入,通过设定一定的时间buff,可以减少频繁冗余的方法调用
      • 尽管它实现了一个基于时间的通知,但它并不是一个真正的timer运行机制,和大都数的input source一样,Timer它也有自己运行的Mode和RunLoop,如果Timer运行的Mode不在当前的RunLoop的观察下,它将不会执行,同样的当一个计时器触发时,如果运行循环正在处理程序例程,timer将不会立即执行,直到下一个RunLoop再去回调它,如果这个RunLoop被销毁它将不会执行。
      • CFRunLoopTimer Objective-C struct __CFRunLoopTimer {
        CFRuntimeBase _base;
        uint16_t _bits;
        pthread_mutex_t _lock;
        CFRunLoopRef _runLoop;
        CFMutableSetRef _rlModes; //Timer可以支持在多个mode下运行
        CFAbsoluteTime _nextFireDate;
        CFTimeInterval _interval; /* immutable */
        CFTimeInterval _tolerance; /* mutable */
        uint64_t _fireTSR; /* TSR units */
        CFIndex _order; /* immutable */
        CFRunLoopTimerCallBack _callout; /* immutable */
        CFRunLoopTimerContext _context; /* immutable, except invalidation */
        };

    Run Loop Observers

    • 监听RunLoop的生命周期, Objective-C struct __CFRunLoopObserver {
      CFRuntimeBase _base;
      pthread_mutex_t _lock;
      CFRunLoopRef _runLoop;
      CFIndex _rlCount;
      CFOptionFlags _activities; /* immutable */
      CFIndex _order; /* immutable */
      CFRunLoopObserverCallBack _callout; /* immutable */
      CFRunLoopObserverContext _context; /* immutable, except invalidation */
      };
    • 对应6种状态的监听 Objective-C kCFRunLoopEntry = (1UL << 0),
      kCFRunLoopBeforeTimers = (1UL << 1),
      kCFRunLoopBeforeSources = (1UL << 2),
      kCFRunLoopBeforeWaiting = (1UL << 5),
      kCFRunLoopAfterWaiting = (1UL << 6),
      kCFRunLoopExit = (1UL << 7),
    • 监听的流程也是RunLopp的运行流程
      1. 通知观察者运行循环已经进入__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
      2. 通知观察者事件触发器已经就绪__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
      3. 通知观观察者非端口事件已经就绪__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
      4. 执行非端口且准备完成的事件源,(block事件)__CFRunLoopDoBlocks(rl, rlm);
      5. 如果一个基于口的输入源以及准备完毕并且等待执行,跳转到第9步if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL))
      6. 通知观察者RunLoop将要开始休眠__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting),__CFRunLoopSetSleeping(rl)
      7. 保持线程一直处于睡眠状态,知道接收到如下事件
        • 一个机基于端口的事件__CFRunLoopServiceMachPort
        • timer事件触发 if (livePort == rl->_wakeUpPort)
        • 为运行循环设置的超时值过期
        • RunLoop显示的被唤醒
      8. 通知观察者线程被唤醒
      9. 处理pending的事件
        • 处理timer事件
        • 处理input source事件
      10. 通知观察者RunLoop已经退出运行循环

    RunLoop定义

    struct __CFRunLoop {
        CFRuntimeBase _base; //不负责自动引用计数
        pthread_mutex_t _lock;          /* locked for accessing mode list */
        __CFPort _wakeUpPort;       //RunLoop被唤醒时的port  // used for CFRunLoopWakeUp 
        Boolean _unused;
        volatile _per_run_data *_perRunData; //重制当前RunLoop的状态
        pthread_t _pthread;  //当前RunLoop所运行的线程
        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运行的状态 _perRunData->stopped = 0x53544F50; // 'STOP'
      _perRunData->ignoreWakeUps = 0x57414B45; // 'WAKE'
      __CFRunLoopSetSleeping
      __CFRunLoopSetDeallocating
    • RunLoop自带一个互斥锁,用于锁定当前线程资源,避免多线程冲突 CF_INLINE void __CFRunLoopLockInit(pthread_mutex_t *lock) {
      pthread_mutexattr_t mattr;
      pthread_mutexattr_init(&mattr);
      pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
      int32_t mret = pthread_mutex_init(lock, &mattr);
      pthread_mutexattr_destroy(&mattr);
      if (0 != mret) {
      }
      }

    创建RunLoop

    - (void)mainThread {
        // The application uses garbage collection, so no autorelease pool is needed.
        NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
     
        // Create a run loop observer and attach it to the run loop.
        CFRunLoopObserverContext  context = {0, self, NULL, NULL, NULL};
        CFRunLoopObserverRef    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
     
        if (observer)
        {
            CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];
            CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
        }
     
        // Create and schedule the timer.
        [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
                    selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
     
        NSInteger    loopCount = 10;
        do
        {
            // Run the run loop 10 times to let the timer fire.
            [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
            loopCount--;
        }
        while (loopCount);
    }
    

    Configuring Run Loop Sources

    • 创建自定于源需要具备以下信息

      • 输入源的处理信息,CFRunLoopSource的事件
      • 调度函数,让客户端知道什么时候需要处理事件
      • 一个callback回调函数
      • 输入源销毁的函数(cancellation)
    • 下图描述了2个线程之间的RunLoop的通信过程,应用程序Main Thread维护了对Input source的引用,Main Thread的任务通过Command Buffer将事件传递给WorkerThread,(因为Worker Thread的Input Source和Main Thread都可以访问Command Buffer,所以必须同步访问。)一旦发出命令,Main Thread就向Input Source发出信号,并唤醒Worker Thread的Runloop。在接收到wake up命令后,Runloop调用Input Source的处理程序,该处理程序处理在Command Buffer中找到的命令。

    RunLoop与GCD的关系

    • GCD是Grand Center Dispatch,由系统底层API调用,精度更高,在RunLoop的实现中,才用了GCD来监听RunLoop是否超时
    • 执行GCD派发的block任务时,如dispatch_async(dispatch_get_main_queue(), block)会向主线成的RunLoop发送信息,并在CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE执行具体的block事件
    • 其他线程如果有设置RunLoop也是如此.主要是看GCD的block派发到哪个线程,子线程默认不会开启RunLoop

    RunLoop与内存关系

    • 通过在RunLoop开启和结束注册2个不同Observers来实现的RunLoop创建和结束监听
    • 监听到开始事件时通过_objc_autoreleasePoolPush创建自动释放池,
    • 监听到结束事件时通过_objc_autoreleasePoolPop销毁释放池

    RunLoop与事件响应

    • 系统注册了一个Source1的输入源,用来接收系统事件,其回调函数是__IOHIDEventSystemClientQueueCallback,当接收到来自硬件的事件时,首先由IOKit.framework生成一个IOHIDEvent事件,并交由SpringBoard接收,
    • SpringBoard接收按键(锁屏/静音等),触摸,加速,接近传感器等事件,通过mach port 转发给需要的App进程。
    • 进而触发Source1事件的回调,通过_UIApplicationHandleEventQueue将事件派发到应用程序的事件对类上,UIApplication接收到事件后再往下传递
    • _UIApplicationHandleEventQueue会将IOHIDEvent包装成UIEvent进行分发。

    利用RunLoop保持线程常驻

    • 例如在AFNetworking中开启了一个常驻线程用来处理网络请求事件,避免了线程的频繁创建销毁所带来的开销
    • 在对应的线程开启一个RunLoop,设置MatchPort作为它的输入源,然后开启一个运行循环
    • addPort:forMode: 在RunLoop指定模式下添加一个输入源

      + (void)networkRequestThreadEntryPoint:(id)__unused object {
      @autoreleasepool {
      [[NSThread currentThread] setName:@"AFNetworking"];
      NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
      //通常情况其它线程访问这个pot就能给这个线程的RunLoop发送消息,此处只是为了让那个RunLoop有事件源被监听,避免退出
      [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
      [runLoop run];
      }
      }
    • 通过dispatch once token,原子执行,安全的生成一个线程,下面的例子开启了一个信息的线程AFNetworking(在这个线程内部的bloc块中已经设置了它的名字)

      /// target
      ///     - 只接收消息的对象
      /// selector
      /// - 用于发送消息给对象(target),此方法只能有一个参数并且不能有返回值
      /// argument
      /// - 发送给target的参数
      /// - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;
      + (NSThread *)networkRequestThread {
      static NSThread *_networkRequestThread = nil;
      static dispatch_once_t oncePredicate;
      dispatch_once(&oncePredicate, ^{
      //创建一个新的线程
      _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
      //异步开启一个新的线程,并在此线程上调用它的入口方法
      [_networkRequestThread start];
      });
      return _networkRequestThread;
      }
  • 相关阅读:
    Java编程之委托代理回调、内部类以及匿名内部类回调(闭包回调)
    JavaEE开发之记事本完整案例(SpringBoot + iOS端)
    JavaEE开发之SpringBoot整合MyBatis以及Thymeleaf模板引擎
    JavaEE开发之SpringBoot工程的创建、运行与配置
    JavaEE开发之SpringMVC中的自定义消息转换器与文件上传
    Scala.js v0.1 发布,在浏览器直接运行 Scala
    如何编写 Cloud9 JavaScript IDE 的功能扩展
    在 Cloud 9 中搭建和运行 Go
    MicroPHP 2.2.0 发布
    StrongSwan 5.1.1 发布,Linux 的 IPsec 项目
  • 原文地址:https://www.cnblogs.com/wwoo/p/runloop-yuan-ma.html
Copyright © 2011-2022 走看看