OC 学习记录随笔 之 RunLoop

总资料
全是随笔 笔记 与 学习资料。没有规律。

苹果的闭源代码包括NSFoundation的GNUStep的重新实现,有一定的参考价值。
GNUStep: http://www.gnustep.org/resources/downloads.php

一、RunLoop 相关类

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef
typedef struct __CFRunLoop * CFRunLoopRef;

//全部结构
/*
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;
};
*/
//精简后 关键点:
struct __CFRunLoop {
    
    
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
};
struct __CFRunLoopMode {
    
    
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};

Runloop的相关状态:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    
    
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

二、CFRunLoopModeRef

  • CFRunLoopModeRef 代表 RunLoop的运行模式
  • 一个RunLoop包含若干个Mode, 每个Mode 又包含__CFRunLoopMode的里面的内容
  • RunLoop启动时只能选择其中一个mode, 作为currentMode
  • 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
  • 不同组的Mode能够分开来,互不影响
  • 如果当前Mode里面没有任何_sources0/_sources1/_observers/_timers, RunLoop会立刻退出。

常见的两种Mode

  • KCFRunLoop1DefaultMode(NSDefaultRunLoopMode): App的默认Mode,通常主线程就是运行的这个Mode
  • UITrackingRunLoopMode:界面跟踪Mode, 用于ScrollView 追踪触摸滑动,保证界面滑动不受其他Mode影响

三、RunLoop 运行逻辑与步骤

在这里插入图片描述

四、RunLoop 核心代码

这里为删除了大部分 不重要的代码,只保留了最主要的 逻辑代码。
轮询来处理消息

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    
{
    
    
   
    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {
    
    
        //通知observer 即将处理timer
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); 
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        //处理block
        __CFRunLoopDoBlocks(rl, rlm);
        
        //处理source0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
    
    
            //再次处理block
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
    
    
            msg = (mach_msg_header_t *)msg_buffer;
            //判断是否有source1
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
    
    
                //直接跳转到处理消息的地方
                goto handle_msg;
            }
        }
                
        //通知observer 即将休眠
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
       //休眠
        __CFRunLoopSetSleeping(rl);
        
        do {
    
    

            msg = (mach_msg_header_t *)msg_buffer;
            //等待其他消息 来唤醒当前线程。 阻塞中。。。
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            
        } while (1);
        
        //取消睡觉,已被消息唤醒
        __CFRunLoopUnsetSleeping(rl);
        //通知observer 休眠结束
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        if (MACH_PORT_NULL == livePort) {
    
    
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
    
    
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            // do nothing on Mac OS
        }
        
        //被timer 唤醒
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
    
    
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
    
    
                __CFArmNextTimerInMode(rlm, rl);
            }
        }

        //gcd 唤醒
        else if (livePort == dispatchPort) {
    
    
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {
    
    
            //source 唤醒 __CFRunLoopDoSource1
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            
            sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
        }
        //处理block
        __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);

    return retVal;
}

__CFRunLoopServiceMachPort 实现真正休眠的代码为:

mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
msg->msgh_bits = 0;
msg->msgh_local_port = port;
msg->msgh_remote_port = MACH_PORT_NULL;
msg->msgh_size = buffer_size;
msg->msgh_id = 0;
if (TIMEOUT_INFINITY == timeout) {
    
     CFRUNLOOP_SLEEP(); } else {
    
     CFRUNLOOP_POLL(); }
ret = mach_msg(msg, MACH_RCV_MSG|
                    (voucherState ? MACH_RCV_VOUCHER : 0)|
                    MACH_RCV_LARGE|
                    ((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|
                    MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|
                    MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV),
              0,
              msg->msgh_size, 
              port, 
              timeout, 
              MACH_PORT_NULL);

mach_msg这个函数在用户态和内核中都有实现的。也就是说这个函数从用户态到内核态的转变。真正休眠的代码是由内核态完成的,真正实现了让CPU和线程假死状态,不处理任何事情。

MACH_RCV_MSG 代表是接收端, 能够接受其他进程或者线程的 信息,当其他线程往这个端口发信息的时候,runloop就会被唤醒,处理信息中需要做的事情。 所以 mach port 可以用来作为最基础的进程间通信。

五、实际应用

  • 控制线程的生命周期,线程保活
  • NSTimer滑动停止工作
  • 监控应用卡顿
  • 性能优化

线程保活

  • 线程和RunLoop为1对1的关系,线程内部本身是没有RunLoop的,当线程内部第一次调用RunLoop时,会自动创建RunLoop
  • RunLoop 保证线程永远活着。

1.封装的类:NSRunloop

#import "MyThreadObject.h"

@interface MyThreadObject()
@property (nonatomic, strong) NSThread *innerThread;
@property (nonatomic, assign) BOOL isStopped;
@end

@implementation MyThreadObject

#pragma  mark - public

- (instancetype)init {
    
    
    if (self = [super init]) {
    
    
        __weak typeof(self) weakSelf = self;
        self.innerThread = [[NSThread alloc] initWithBlock:^{
    
    
            [[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
            while (weakSelf.innerThread && !weakSelf.isStopped) {
    
    
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
        }];
    }
    return  self;
}

//开始线程
- (void)run {
    
    
    if (self.isStopped)  return;
    [self.innerThread start];
}

//结束线程
- (void)stop {
    
    
    if (self.isStopped)  return;
    [self performSelector:@selector(_stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}

//运行block
- (void)execTask:(void (^)(void))task {
    
    
    if (self.isStopped)  return;
    [self performSelector:@selector(_execTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}

#pragma mark - private

- (void)_execTask:(void (^)(void))task {
    
    
    task();
}

- (void)_stop {
    
    
    self.isStopped = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}
@end

2.封装的类:CFRunloop (推荐,不需要while 循环调用)

#import "MyThreadObject.h"

@interface MyThreadObject()
@property (nonatomic, strong) NSThread *innerThread;
@end

@implementation MyThreadObject

#pragma  mark - public

- (instancetype)init {
    
    
    if (self = [super init]) {
    
    

        self.innerThread = [[NSThread alloc] initWithBlock:^{
    
    
            
            CFRunLoopSourceContext context =  {
    
    0}; //初始化为0
            CFRunLoopSourceRef sourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
            CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopDefaultMode);
            CFRelease(sourceRef);
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false /* 事件处理完成后不返回,等待runloop stop后才返回 */);
        }];
    }
    return  self;
}

//开始线程
- (void)run {
    
    
    if (!self.innerThread)  return;
    [self.innerThread start];
}

//结束线程
- (void)stop {
    
    
    if (!self.innerThread)  return;
    [self performSelector:@selector(_stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}

//运行block
- (void)execTask:(void (^)(void))task {
    
    
    if (!self.innerThread)  return;
    [self performSelector:@selector(_execTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}

#pragma mark - private

- (void)_execTask:(void (^)(void))task {
    
    
    task();
}

- (void)_stop {
    
    
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}
@end

六、Runloo 响应用户操作

  • touchesBegan:withEvent 的调用堆栈
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x0000000102d1de35 140_RunLoop_iOS`-[ViewController touchesBegan:withEvent:](self=0x00007ff21300e3b0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600002914d80) at ViewController.m:75:6
    frame #1: 0x00007fff25031c7f UIKitCore`forwardTouchMethod + 321
    frame #2: 0x00007fff250426c4 UIKitCore`-[UIWindow _sendTouchesForEvent:] + 622
    frame #3: 0x00007fff250449df UIKitCore`-[UIWindow sendEvent:] + 5295
    frame #4: 0x00007fff2501b4e8 UIKitCore`-[UIApplication sendEvent:] + 825
    frame #5: 0x00007fff250b128a UIKitCore`__dispatchPreprocessedEventFromEventQueue + 8695
    frame #6: 0x00007fff250b3a10 UIKitCore`__processEventQueue + 8579
    frame #7: 0x00007fff250aa1b6 UIKitCore`__eventFetcherSourceCallback + 240
    frame #8: 0x00007fff20369e25 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #9: 0x00007fff20369d1d CoreFoundation`__CFRunLoopDoSource0 + 180
    frame #10: 0x00007fff203691f2 CoreFoundation`__CFRunLoopDoSources0 + 242
    frame #11: 0x00007fff20363951 CoreFoundation`__CFRunLoopRun + 875
    frame #12: 0x00007fff20363103 CoreFoundation`CFRunLoopRunSpecific + 567
    frame #13: 0x00007fff2c851cd3 GraphicsServices`GSEventRunModal + 139
    frame #14: 0x00007fff24ffbe63 UIKitCore`-[UIApplication _run] + 928
    frame #15: 0x00007fff25000a53 UIKitCore`UIApplicationMain + 101
    frame #16: 0x0000000102d1e8fe 140_RunLoop_iOS`main(argc=1, argv=0x00007ff7bd1e4c68) at main.m:17:12
    frame #17: 0x0000000102f2ae1e dyld_sim`start_sim + 10
    frame #18: 0x000000010d1d24fe dyld`start + 462

在这里插入图片描述

  • 首先 source1 捕获用户事件(TODO:堆栈中并没有看到source1) *后续: 首先是由那个Source1 接收IOHIDEvent,之后在回调 __IOHIDEventSystemClientQueueCallback() 内触发的 Source0,Source0 再触发的 _UIApplicationHandleEventQueue()。所以UIButton事件看到是在 Source0 内的。你可以在 __IOHIDEventSystemClientQueueCallback 处下一个 Symbolic Breakpoint 看一下。*所以堆栈内没有source1,因为它是上一个循环的产物。
  • 然后 source0 处理事件
  • source0 和sourc1都是被动的,source1监听端口

七、线程保活的意义

  • 主线程 不需要添加任何 source 就能保活, 能够让程序不退出
  • 子线程保活,能够创建常住线程
  • 子线程常驻线程 能够监听主线程的卡顿

八、其他

  • performSelecter:afterDelay:performSelector:onThread: 都是在runloop里面的timer 驱动的,所以该线程没有runloop就会调用失败
  • runloop 中 mach port 很重要。source1 和 runloop的休眠监听都是通过mach port 实现的。
  • runloop 出了被事件唤醒外,还有可能被自己设置的time 超时唤醒。

九、多线程

https://editor.csdn.net/md?articleId=122094507

备注:部分笔记包含有MJ老师的学习资料。

猜你喜欢

转载自blog.csdn.net/goldWave01/article/details/121877941