iOS - RunLoop

作者: lieon | 来源:发表于2021-04-19 11:22 被阅读0次

    RunLoop

    • 运行循环
    • 在程序运行过程中循环做一些事情
    • 应用范畴
      • 定时器(Timer)、PerformSelector
      • GCD Async Main Queue
      • 事件响应、手势识别、界面刷新
      • 网络请求
      • AutoreleasePool
      • 如果没有RunLoop, 执行完代码后,会即将退出程序
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSLog(@"Hello, World!");
        }
        return 0;
    }
    
    • 如果有了RunLoop,程序并不会马上退出,而是保持运行状态
    • 并不是说程序的运行需要runloop,runloop作用仅仅只是为了程序不立即退出,这点不能混淆了
    • RunLoop的基本作用
      • 保持程序的持续运行
      • 处理App中的各种事件(比如触摸事件,定时器事件等)
      • 节约CPU资源,提高程序性能:该做事时做事,改休息时休息
        int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil,   NSStringFromClass([AppDelegate class]));
          }
      }
      
      //等价于
      int main*(int argc, char *argv[]) {
          @autoreleasepool {
             int retVal = 0;
             do {
                 int message = sleep_and_wait();
                 retval = process_message(message)
            } while(0 == retval)
          }
      }
      

    RunLoop对象

    • iOS中有2套API来访问和使用RunLoop
      • Foundation:NSRunLoop
      • Core Foundation:CFRunLoopRef
    • NSRunLoopCFRunLoopRef都代表着RunLoop对象
     [NSRunLoop currentRunLoop]; // 获取当前线程的RunLoop对象
     [NSRunLoop mainRunLoop]; // 获取主线程的RunLoop对象
            
     CFRunLoopGetCurrent(); // 获取当前线程的RunLoop对象
     CFRunLoopGetMain(); // 获取主线程的RunLoop对象
    

    RunLoop与线程

    • 每个runloop对应一个线程,并不是说一个线程就一定对应一个runloop
    • RunLoop保存在一个全局的Dictionary里,线程作为keyRunLoop作为value
    • 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
    • RunLoop会在线程结束时销毁
    • 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop(或默认没有runloop对象),所以子线程任务执行完毕之后,直接直接退出了
    • MainRunloop的运行逻辑发生在主线程


    • 必须明确一点:什么是线程?线程就是代码执行的最小单元,既然runloop要执行while循环,那就必须在一个线程中执行

    证明子线程中默认没有runloop对象

    • 通过查看NSThread源码可知
    
    - (void) start
    {
      pthread_attr_t    attr;
    
      if (_active == YES)
        {
          [NSException raise: NSInternalInconsistencyException
                      format: @"[%@-%@] called on active thread",
            NSStringFromClass([self class]),
            NSStringFromSelector(_cmd)];
        }
      if (_cancelled == YES)
        {
          [NSException raise: NSInternalInconsistencyException
                      format: @"[%@-%@] called on cancelled thread",
            NSStringFromClass([self class]),
            NSStringFromSelector(_cmd)];
        }
      if (_finished == YES)
        {
          [NSException raise: NSInternalInconsistencyException
                      format: @"[%@-%@] called on finished thread",
            NSStringFromClass([self class]),
            NSStringFromSelector(_cmd)];
        }
    
      /* Make sure the notification is posted BEFORE the new thread starts.
       */
      gnustep_base_thread_callback();
    
      /* The thread must persist until it finishes executing.
       */
      RETAIN(self);
    
      /* Mark the thread as active while it's running.
       */
      _active = YES;
    
      errno = 0;
      pthread_attr_init(&attr);
      /* Create this thread detached, because we never use the return state from
       * threads.
       */
      pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
      /* Set the stack size when the thread is created.  Unlike the old setrlimit
       * code, this actually works.
       */
      if (_stackSize > 0)
        {
          pthread_attr_setstacksize(&attr, _stackSize);
        }
      if (pthread_create(&pthreadID, &attr, nsthreadLauncher, self))
        {
          DESTROY(self);
          [NSException raise: NSInternalInconsistencyException
                      format: @"Unable to detach thread (last error %@)",
                      [NSError _last]];
        }
    }
    
    
    static void *
    nsthreadLauncher(void *thread)
    {
      NSThread *t = (NSThread*)thread;
    
      setThreadForCurrentThread(t);
    
      /*
       * Let observers know a new thread is starting.
       */
      if (nc == nil)
        {
          nc = RETAIN([NSNotificationCenter defaultCenter]);
        }
      [nc postNotificationName: NSThreadDidStartNotification
                object: t
              userInfo: nil];
    
      [t _setName: [t name]];
    
      [t main];
    
      [NSThread exit];
      // Not reached
      return NULL;
    }
    
    
    - (void) main
    {
      if (_active == NO)
        {
          [NSException raise: NSInternalInconsistencyException
                      format: @"[%@-%@] called on inactive thread",
            NSStringFromClass([self class]),
            NSStringFromSelector(_cmd)];
        }
    
      [_target performSelector: _selector withObject: _arg];
    }
    
    
    GSRunLoopThreadInfo *
    GSRunLoopInfoForThread(NSThread *aThread)
    {
      GSRunLoopThreadInfo   *info;
    
      if (aThread == nil)
        {
          aThread = GSCurrentThread();
        }
      if (aThread->_runLoopInfo == nil)
        {
          [gnustep_global_lock lock];
          if (aThread->_runLoopInfo == nil)
            {
              aThread->_runLoopInfo = [GSRunLoopThreadInfo new];
        }
          [gnustep_global_lock unlock];
        }
      info = aThread->_runLoopInfo;
      return info;
    }
    

    RunLoop相关的类

    • Core Foundation中关于RunLoop的5个类
      • CFRunLoopRef
      • CFRunLoopModeRef
      • CFRunLoopSourceRef
      • CFRunLoopTimerRef
      • CFRunLoopObserverRef
    typedef struct __CFRunLoop * CFRunLoopRef;
    struct __CFRunLoop {
        pthread_t _pthread;
        CFMutableSetRef _commonModes;
        CFMutableSetRef _commonModeItems;
        CFRunLoopModeRef _currentMode;
        CFMutableSetRef _modes;
    };
    
    typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    struct __CFRunLoopMode {
        CFStringRef _name;
        Boolean _stopped;
        char _padding[3];
        CFMutableSetRef _sources0;
        CFMutableSetRef _sources1;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;
    };
    

    CFRunLoopModeRef

    • CFRunLoopModeRef代表RunLoop的运行模式
    • 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
    • RunLoop启动时只能选择其中一个Mode,作为currentMode
    • 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
    • 不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
    • 如果Mode里没有任何Source0/Source1/Timer/ObserverRunLoop会立马退出
    • 常见的2种Mode
      • kCFRunLoopDefaultModeNSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
      • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响

    CFRunLoopObserverRef

    • Observer的监听状态
    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
    };
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // 创建Observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
                case kCFRunLoopEntry: {
                    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                    NSLog(@"kCFRunLoopEntry - %@", mode);
                    CFRelease(mode);
                    break;
                }
                    
                case kCFRunLoopExit: {
                    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                    NSLog(@"kCFRunLoopExit - %@", mode);
                    CFRelease(mode);
                    break;
                }
                    
                default:
                    break;
            }
        });
        // 添加Observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        // 释放
        CFRelease(observer);
    }
    

    RunLoop的运行逻辑

    • Source0
      • 触摸事件处理
      • performSelector:onThread:
    • Source1
      • 基于Port的线程间通信
      • 系统事件捕捉
    • Timers
      • NSTimer
      • performSelector:withObject:afterDelay:
    • Observers
      • 用于监听RunLoop的状态
      • UI刷新(BeforeWaiting)
      • Autorelease pool(BeforeWaiting)

    RunLoop的运行流程

    • 01、通知Observers:进入Loop
    • 02、通知Observers:即将处理Timers
    • 03、通知Observers:即将处理Sources
    • 04、处理Blocks
    • 05、处理Source0(可能会再次处理Blocks)
    • 06、如果存在Source1,就跳转到第8步
    • 07、通知Observers:开始休眠(等待消息唤醒)
    • 08、通知Observers:结束休眠(被某个消息唤醒)
      • 01> 处理Timer
      • 02> 处理GCD Async To Main Queue
      • 03> 处理Source1
    • 09、处理Blocks
    • 10、根据前面的执行结果,决定如何操作
      • 01> 回到第02步
      • 02> 退出Loop
    • 11、通知Observers:退出Loop


    void CFRunLoopRun(void) { 
        int32_t result;
        do {
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
            CHECK_FOR_FORK();
        } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }
    
    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
        // 通知Observers:进入Loop
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        // 核心的Loop逻辑
        __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        // 通知Observers:退出Loop
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
        
        return result;
    }
    
    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
        
        do {
            // 通知Observer:即将处理timers
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            // 通知Observer:即将处理Sources
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
            // 处理Block
            __CFRunLoopDoBlocks(rl, rlm);
            // 处理Source0
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            if (sourceHandledThisLoop) {
                // 处理Block
                __CFRunLoopDoBlocks(rl, rlm);
            }
            
            Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
            // 判断是否有Source1,如果有,就跳转到handle_msg
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
            didDispatchPortLastTime = false;
            
            // 通知Observers:即将休眠
            if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            // 进入休眠,等待其他消息唤醒
            __CFRunLoopSetSleeping(rl);
            __CFPortSetInsert(dispatchPort, waitSet);
            
            do {
                __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
                
            } while (1);
            
            // 通知
            __CFPortSetRemove(dispatchPort, waitSet);
            __CFRunLoopSetIgnoreWakeUps(rl);
            
            // 通知Observer:已经唤醒
            __CFRunLoopUnsetSleeping(rl);
            if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
            
        handle_msg: // 看看是谁唤醒了RunLoop,进行相应的处理
            __CFRunLoopSetIgnoreWakeUps(rl);
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                // 被timer唤醒的,处理timer
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    __CFArmNextTimerInMode(rlm, rl);
                }
            }
            else if (livePort == dispatchPort) { // 被GCD唤醒
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
                
            } else {// 被Source1唤醒的
                __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
            }
            // 执行Blocks
            __CFRunLoopDoBlocks(rl, rlm);
            
            // 根据之前的执行结果,决定怎么做
            if (sourceHandledThisLoop && stopAfterHandle) {
                retVal = kCFRunLoopRunHandledSource;
            } else if (timeout_context->termTSR < mach_absolute_time()) {
                retVal = kCFRunLoopRunTimedOut;
            } else if (__CFRunLoopIsStopped(rl)) {
                __CFRunLoopUnsetStopped(rl);
                retVal = kCFRunLoopRunStopped;
            } else if (rlm->_stopped) {
                rlm->_stopped = false;
                retVal = kCFRunLoopRunStopped;
            } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
                retVal = kCFRunLoopRunFinished;
            }
            
            
        } while (0 == retVal);
        
        if (timeout_timer) {
            dispatch_source_cancel(timeout_timer);
            dispatch_release(timeout_timer);
        } else {
            free(timeout_context);
        }
        
        return retVal;
     }
    
    

    RunLoop休眠的实现原理

    • 等待消息
      • 没有消息就让线程休眠
      • 有消息就唤醒线程
      • 在用户态和内核态之间来回切换,用户态唤醒线程处理消息,内核态休眠
    • 休眠是调用了内核级的函数 mach_msg()

    RunLoop在实际开中的应用

    • 控制线程生命周期(线程保活)
    • 解决NSTimer在滑动时停止工作的问题
    • 监控应用卡顿
    • 性能优化

    线程保活

    • 如果直接线程NSThread强引用的话,是不能办到线程保活的,因为线程的任务是在创建这个线程对象的时候传入的
    • 任务结束之后,现在就结束了,要执行新的任务,就只能重新创建一个新的线程对象
    • 所谓线程保活,就是为了让线程的生命周期与它被引用的对象生命周期一致
    • 所以只能自定以一个线程
    
    @interface MJPermenantThread()
    @property (strong, nonatomic) MJThread *innerThread;
    @property (assign, nonatomic, getter=isStopped) BOOL stopped;
    @end
    
    @implementation MJPermenantThread
    #pragma mark - public methods
    - (instancetype)init
    {
       if (self = [super init]) {
           self.stopped = NO;
           __weak typeof(self) weakSelf = self;
           self.innerThread = [[MJThread alloc] initWithBlock:^{
    //  往RunLoop里面添加Source\Timer\Observer, 
               [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    // while里面的代码不会多次执行,因为runloop已经让线程休眠了
               while (weakSelf && !weakSelf.isStopped) {
                   [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
               }
      // NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
           //        [[NSRunLoop currentRunLoop] run];
           }];
           [self.innerThread start];
       }
       return self;
    }
    
    - (void)executeTask:(MJPermenantThreadTask)task {
       if (!self.innerThread || !task) return;
       
       [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
    }
    
    - (void)stop {
       if (!self.innerThread) return;
       //  在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
       [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
    }
    
    - (void)dealloc {
       NSLog(@"%s", __func__);
       [self stop];
    }
    
    #pragma mark - private methods
    - (void)__stop
    {
       self.stopped = YES;
       CFRunLoopStop(CFRunLoopGetCurrent());
       self.innerThread = nil;
    }
    
    - (void)__executeTask:(MJPermenantThreadTask)task {
       task();
    }
    

    RunLoop监听页面卡顿

    • RunLoop处理事件的两个主要阶段
      • kCFRunLoopBeforeSouces和KCFRunLoopBeforeWating之间
      • KCFRunLoopAfterwaiting之后
      • 如果每次检测runloop的状态一直停留在 kCFRunLoopBeforeSouces 或者 KCFRunLoopAfterwaiting,则runloop一直在处理事件(source0 source1)
    • 步骤
      • 创建RunLoopObserver
      • 将Observer添加到main loop中
      • 监听Observer的状态回调
        • 状态变更一次。信号量+1
      • 在子线程中开启一个while循环。通过信号量 dispatch_semaphore_wait设置超时时间,信号量的值不为0,要么是在runloop不断改变状态,要么就是等待超时了(超时了说明,RunLoop一直处于某一种状态)
      • 信号量不为0时,不断地去检查这个状态,如果这个状态是等于 kCFRunLoopBeforeSouces和KCFRunLoopBeforeWating,则出现卡顿
      • 出现卡顿之后,就可以上报调用栈信息了
    void runLoopOberCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
        RunLoopObserverViewController *monitor = (__bridge RunLoopObserverViewController*)info;
        monitor->activity = activity;
        long st = dispatch_semaphore_signal(monitor->semaphore);
        NSLog(@"dispatch_semaphore_signal:st=%ld,time:%@",st,[monitor getCurTime]);
        if (activity == kCFRunLoopEntry) {  // 即将进入RunLoop
            NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopEntry");
        } else if (activity == kCFRunLoopBeforeTimers) {    // 即将处理Timer
            NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeTimers");
        } else if (activity == kCFRunLoopBeforeSources) {   // 即将处理Source
            NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeSources");
        } else if (activity == kCFRunLoopBeforeWaiting) {   //即将进入休眠
            NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeWaiting");
        } else if (activity == kCFRunLoopAfterWaiting) {    // 刚从休眠中唤醒
            NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAfterWaiting");
        } else if (activity == kCFRunLoopExit) {    // 即将退出RunLoop
            NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopExit");
        } else if (activity == kCFRunLoopAllActivities) {
            NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAllActivities");
        }
    }
    
    /**
     # RunLoop处理事件的两个主要阶段
         kCFRunLoopBeforeSouces和KCFRunLoopBeforeWating之间
         KCFRunLoopAfterwaiting之后
        - 如果每次检测runloop的状态一直停留在 kCFRunLoopBeforeSouces 或者 KCFRunLoopAfterwaiting,则runloop一直在处理事件(source0 source1)
     
     */
    - (void)startMonitor {
        semaphore = dispatch_semaphore_create(1);
        CFRunLoopObserverContext context = { 0 , (__bridge  void*)self, NULL, NULL };
        obsever =  CFRunLoopObserverCreate(kCFAllocatorDefault,
                                           kCFRunLoopAllActivities,
                                           true,
                                           0,
                                           runLoopOberCallback,
                                           &context);
        CFRunLoopAddObserver(CFRunLoopGetMain(), obsever, kCFRunLoopCommonModes);
        
        // 在子线程中监控时长
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            while (1) {
                // dispatch_semaphore_wait 使信号量减一,如果超时了,则返回了一个非0的值,然后往下执行
                // dispatch_semaphore_wait 往下执行的条件 减一之后,信号量不为0 或 等待超时
                // runloop状态改变,使用信号量 +1, 这边wait之后,信号量不为0,要么是在runloop不断改变状态,要么就是等待超时了
                long st = dispatch_semaphore_wait(self->semaphore, dispatch_time(DISPATCH_TIME_NOW, 3 *NSEC_PER_MSEC));
                if (st != 0) { // 信号量超时了 - 即runloop的状态长时间没有发生变更,长期处于某一状态
                    if (!self->obsever) {
                       self->timeoutCount = 0;
                        self->semaphore = 0;
                        self->activity = 0;
                        return;
                    }
                    if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting) {
                        if (++self->timeoutCount < 5) {
                            continue; // 不足 5 次。直接 continue 当次循环,不将timtouotcount置为0
                        }
                        NSLog(@"-----卡顿了----上报栈信息");
                        self->timeoutCount = 0;
                    }
                }
            }
        });
    }
    
    
    

    相关文章

      网友评论

        本文标题:iOS - RunLoop

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