RunLoop

作者: MrQun | 来源:发表于2022-02-09 17:17 被阅读0次
    RunLoop_0.png

    一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。


    iShot2022-02-08 22.39.00.png

    CFRunLoopRef

    1. 代表 RunLoop 的对象
      1. CFRunLoopGetCurrent();

      1. [NSRunLoop currentRunLoop];
    2. 获得主线程的 RunLoop 对象

      1. CFRunLoopGetMain();
      2. [NSRunLoop mainRunLoop];

    CFRunLoopModeRef

    1. 代表 RunLoop 的运行模式
      1. kCFRunLoopDefaultMode
        1. App的默认运行模式,通常主线程是在这个运行模式下运行
      2. UITrackingRunLoopMode
        1. 跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)
      3. UIInitializationRunLoopMode
        1. 在刚启动App时第进入的第一个 Mode,启动完成后就不再使用
      4. GSEventReceiveRunLoopMode
        1. 接受系统内部事件,通常用不到
      5. kCFRunLoopCommonModes
        1. 是个模式集合,而不是一个具体的模式.

    CFRunLoopSourceRef

    1. 就是 RunLoop 模型图中提到的输入源 / 事件源
      1. Source0 :非基于Port
    - (void)testDemo4
    {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
            NSLog(@"starting thread.......");
    
            _runLoopRef = CFRunLoopGetCurrent();
            //初始化_source_context。
            bzero(&_source_context, sizeof(_source_context));
            //这里创建了一个基于事件的源,绑定了一个函数
            _source_context.perform = fire;
            //参数
            _source_context.info = "hello";
            //创建一个source
            _source = CFRunLoopSourceCreate(NULL, 0, &_source_context);
            //将source添加到当前RunLoop中去
            CFRunLoopAddSource(_runLoopRef, _source, kCFRunLoopDefaultMode);
    
            //开启runloop 第三个参数设置为YES,执行完一次事件后返回
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 9999999, YES);
    
            NSLog(@"end thread.......");
        });
    
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
            if (CFRunLoopIsWaiting(_runLoopRef)) {
                NSLog(@"RunLoop 正在等待事件输入");
                //添加输入事件
                CFRunLoopSourceSignal(_source);
                //唤醒线程,线程唤醒后发现由事件需要处理,于是立即处理事件
                CFRunLoopWakeUp(_runLoopRef);
            }else {
                NSLog(@"RunLoop 正在处理事件");
                //添加输入事件,当前正在处理一个事件,当前事件处理完成后,立即处理当前新输入的事件
                CFRunLoopSourceSignal(_source);
            }
        });
    
    }
    
    //此输入源需要处理的后台事件
    static void fire(void* info){
    
        NSLog(@"我现在正在处理后台任务");
    
        printf("%s",info);
    }
    
    
    // - 2016-11-24 10:42:24.045 TestRunloop3[4683:238183] starting thread.......
    // - 2016-11-24 10:42:26.045 TestRunloop3[4683:238082] RunLoop 正在等待事件输入 
    // - 2016-11-24 10:42:31.663 TestRunloop3[4683:238183] 我现在正在处理后台任务
    hello
    // - 2016-11-24 10:42:31.663 TestRunloop3[4683:238183] end thread.......
    
    1. Source1:基于Port,通过内核和其他线程通信,接收、分发系统事件
    - (void)testDemo3{
        //声明两个端口   随便怎么写创建方法,返回的总是一个NSMachPort实例
        NSMachPort *mainPort = [[NSMachPort alloc]init];
        NSPort *threadPort = [NSMachPort port];
        //设置线程的端口的代理回调为自己
        threadPort.delegate = self;
    
        //给主线程runloop加一个端口
        [[NSRunLoop currentRunLoop]addPort:mainPort forMode:NSDefaultRunLoopMode];
    
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
            //添加一个Port
            [[NSRunLoop currentRunLoop]addPort:threadPort forMode:NSDefaultRunLoopMode];
            [[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    
        });
    
        NSString *s1 = @"hello";
    
        NSData *data = [s1 dataUsingEncoding:NSUTF8StringEncoding];
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            // - 这个传参数组里面只能装两种类型的数据,一种是NSPort的子类,一种是NSData的子类
            NSMutableArray *array = [NSMutableArray arrayWithArray:@[mainPort,data]];
            //过2秒向threadPort发送一条消息,第一个参数:发送时间。msgid 消息标识。
            //components,发送消息附带参数。reserved:为头部预留的字节数(从官方文档上看到的,猜测可能是类似请求头的东西...)
            [threadPort sendBeforeDate:[NSDate date] msgid:1000 components:array from:mainPort reserved:0];
    
        });
    
    }
    
    //这个NSMachPort收到消息的回调,注意这个参数,可以先给一个id。如果用文档里的NSPortMessage会发现无法取值
    - (void)handlePortMessage:(id)message{
    
        NSLog(@"收到消息了,线程为:%@",[NSThread currentThread]);
    
        //只能用KVC的方式取值
        NSArray *array = [message valueForKeyPath:@"components"];
    
        NSData *data =  array[1];
        NSString *s1 = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@",s1);
    
    // - NSMachPort *localPort = [message valueForKeyPath:@"localPort"];
    // - NSMachPort *remotePort = [message valueForKeyPath:@"remotePort"];
    // - [NSRunloop currentRunloop] removePort: addPort:threadPort forMode: runMode:NSDefaultRunLoopMode];
    // - 2016-11-23 16:50:20.604 TestRunloop3[1322:120162] 收到消息了,线程为:<NSThread: 0x60800026d700>{number = 3, name = (null)}
    // - 2016-11-23 16:50:26.551 TestRunloop3[1322:120162] hello
    }
    

    CFRunLoopTimerRef

    1. 就是 RunLoop 模型图中提到的定时源
      1. 基于时间的触发器, 当timer入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调

      2. 创建timer

    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    // - CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
    CFRunLoopTimerContext context;
    bzero(&context, sizeof(context));
    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0, &myCFTimeCallback, &context);
    CFRunLoopAddTimer(runLoop, timer, kCFRunLoopDefaultMode);
    

    CFRunLoopObserverRef

    1. 观察者,能够监听 RunLoop 的状态改变
      1. 可监听的状态
    kCFRunLoopEntry = (1UL << 0),               // 即将进入Loop:1
    kCFRunLoopBeforeTimers = (1UL << 1),        // 即将处理Timer:2    
    kCFRunLoopBeforeSources = (1UL << 2),       // 即将处理Source:4
    kCFRunLoopBeforeWaiting = (1UL << 5),       // 即将进入休眠:32
    kCFRunLoopAfterWaiting = (1UL << 6),        // 即将从休眠中唤醒:64
    kCFRunLoopExit = (1UL << 7),                // 即将从Loop中退出:128
    kCFRunLoopAllActivities = 0x0FFFFFFFU       // 监听全部状态改变  
    
    1. 创建observer
    // 创建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    });
    // 添加观察者:监听RunLoop的状态
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    // 释放Observer
    CFRelease(observer);
    

    上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环, 所以如果希望runloop保活, 需要至少包含一个 modeitem(Source/Timer/Observer).

    RunLoop 的核心就是一个 mach_msg() 这个函数去接收消息,如果没有别人发送 port 消息(基于port的source事件/timer时间到了/runloop自身超时/被其他调用者唤醒)过来,内核会将线程置于等待状态.

    RunLoop 的 Mode

    struct __CFRunLoop {
      /**
        一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到
         RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时
        ,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 
        同步到具有 “Common” 标记的所有Mode里。
    */
        CFMutableSetRef _commonModes;     // - 标记为Common的mode集合
        CFMutableSetRef _commonModeItems; //- <Source/Observer/Timer>
        CFRunLoopModeRef _currentMode;    // - 当前的mode
        CFMutableSetRef _modes;
        ...
    };
    
    // - kCFRunLoopDefaultMode : 默认的mode
    // - kCFRunLoopCommonModes : 标记为 common的mode,  可以用这个字符串操作 Common Items,或标记一个 Mode 为 “Common”
    CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
    CFRunLoopRunInMode(CFStringRef modeName, ...);
    
    CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
    CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
    CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
    CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
    CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
    CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
    
    

    App 启动后 RunLoop 的状态

    CFRunLoop {
        current mode = kCFRunLoopDefaultMode
        common modes = {
            UITrackingRunLoopMode
            kCFRunLoopDefaultMode
        }
     
        common mode items = {
     
            // source0 (manual)
            CFRunLoopSource {order =-1, {
                callout = _UIApplicationHandleEventQueue}}
            CFRunLoopSource {order =-1, {
                callout = PurpleEventSignalCallback }}
            CFRunLoopSource {order = 0, {
                callout = FBSSerialQueueRunLoopSourceHandler}}
     
            // source1 (mach port)
            CFRunLoopSource {order = 0,  {port = 17923}}
            CFRunLoopSource {order = 0,  {port = 12039}}
            CFRunLoopSource {order = 0,  {port = 16647}}
            CFRunLoopSource {order =-1, {
                callout = PurpleEventCallback}}
            CFRunLoopSource {order = 0, {port = 2407,
                callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}
            CFRunLoopSource {order = 0, {port = 1c03,
                callout = __IOHIDEventSystemClientAvailabilityCallback}}
            CFRunLoopSource {order = 0, {port = 1b03,
                callout = __IOHIDEventSystemClientQueueCallback}}
            CFRunLoopSource {order = 1, {port = 1903,
                callout = __IOMIGMachPortPortCallback}}
     
            // Ovserver
            CFRunLoopObserver {order = -2147483647, activities = 0x1, // Entry
                callout = _wrapRunLoopWithAutoreleasePoolHandler}
            CFRunLoopObserver {order = 0, activities = 0x20,          // BeforeWaiting
                callout = _UIGestureRecognizerUpdateObserver}
            CFRunLoopObserver {order = 1999000, activities = 0xa0,    // BeforeWaiting | Exit
                callout = _afterCACommitHandler}
            CFRunLoopObserver {order = 2000000, activities = 0xa0,    // BeforeWaiting | Exit
                callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
            CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exit
                callout = _wrapRunLoopWithAutoreleasePoolHandler}
     
            // Timer
            CFRunLoopTimer {firing = No, interval = 3.1536e+09, tolerance = 0,
                next fire date = 453098071 (-4421.76019 @ 96223387169499),
                callout = _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv (QuartzCore.framework)}
        },
     
        modes = {
            CFRunLoopMode  {
                sources0 =  { /* same as 'common mode items' */ },
                sources1 =  { /* same as 'common mode items' */ },
                observers = { /* same as 'common mode items' */ },
                timers =    { /* same as 'common mode items' */ },
            },
     
            CFRunLoopMode  {
                sources0 =  { /* same as 'common mode items' */ },
                sources1 =  { /* same as 'common mode items' */ },
                observers = { /* same as 'common mode items' */ },
                timers =    { /* same as 'common mode items' */ },
            },
     
            CFRunLoopMode  {
                sources0 = {
                    CFRunLoopSource {order = 0, {
                        callout = FBSSerialQueueRunLoopSourceHandler}}
                },
                sources1 = (null),
                observers = {
                    CFRunLoopObserver >{activities = 0xa0, order = 2000000,
                        callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
                )},
                timers = (null),
            },
     
            CFRunLoopMode  {
                sources0 = {
                    CFRunLoopSource {order = -1, {
                        callout = PurpleEventSignalCallback}}
                },
                sources1 = {
                    CFRunLoopSource {order = -1, {
                        callout = PurpleEventCallback}}
                },
                observers = (null),
                timers = (null),
            },
            
            CFRunLoopMode  {
                sources0 = (null),
                sources1 = (null),
                observers = (null),
                timers = (null),
            }
        }
    }
    
    1. AutoreleasePool (_wrapRunLoopWithAutoreleasePoolHandler())
      1. Entry中(即将进入loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池
      2. BeforeWaiting(准备进入休眠)时候调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池, Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。
      3. 在主线程执行的代码,通常是写在事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏.
    2. 事件响应(__IOHIDEventSystemClientQueueCallback())
      1. 当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用_UIApplicationHandleEventQueue()包装成UIEvent进行应用的处理或分发。
    3. 手势识别(_UIGestureRecognizerUpdateObserver())
      1. 当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。当监听到 BeforeWaiting (Loop即将进入休眠)时候, 其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。
    4. 界面更新(_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv())
      1. 当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去, 当监听到BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件时, 会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。
    5. 计时器
      1. 一个 NSTimer 注册到 RunLoop 后,RunLoop 会为其重复的时间点注册好事件, 如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。
      2. CADisplayLink也是一样的, 如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer 相似),造成界面卡顿的感觉
    6. PerformSelecter
      1. 当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法不会执行.

    NSRunLoop

    1. -(void)run;
      1. 永久运行在NSDefaultRunLoopMode, 这种方式开启的runloop无法停止(除非移除这个runloop上的所有事件源),
      2. 无法使用CFRunLoopStop(runloopRef),停止Run Loop的运行。
    2. -(void)runUntilDate:(NSDate *)limitDate;
      1. 可以控制每次Run Loop的运行时间,也是运行在NSDefaultRunLoopMode模式。
      2. 这个方法运行Run Loop一段时间会退出给你检查运行条件的机会,如果需要可以再次运行Run Loop。
        3.无法使用CFRunLoopStop(runloopRef),停止Run Loop的运行。
    3. -(BOOL)runMode:(NSString *)mode beforeDate:(NSDtate *)limitDate;
      1. 可以使用CFRunLoopStop(runloopRef),停止Run Loop的运行

    CFRunLoopRef

    CFRunLoop能直接停止掉所有的CFRunLoop运行起来的runloop

    相关文章

      网友评论

          本文标题:RunLoop

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