美文网首页
iOS 底层 - runloop内部的数据结构

iOS 底层 - runloop内部的数据结构

作者: 水中的蓝天 | 来源:发表于2020-04-05 12:36 被阅读0次

本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !

RunLoop 相关的C语言类:
CFRunLoopRef
CFRunLoopModeRef --> 运行模式
CFRunLoopSourceRef -- >事件处理: 点击 、PerformSelector(触摸事件)
CFRunLoopTimeRef --> 定时器
CFRunLoopObserverRef --> 监听器

typedef struct   __CFRunLoop  *CFRunLoopRef;
struct   __CFRunLoop  {
            pthread_t _pthread;
            CFMutableSetRef   _commonModes;  //通用Mode集合
            CFMutableSetRef   _commonModeItems; //存放着在通用模式下工作的timer或其他
            CFRunLoopModeRef   _currentMode;  //当前运行模式
            CFMutableSetRef        _modes;  //内部存储着若干个CFRunLoopModeRef
}

CFRunLoopMode

RunLoop在同一时段内只能并且必须在一种特定的Moderun,这个Mode被称为currentMode
如果想更换currentMode,需要暂停当前的loop,然后重启新的loop,这样可以分离开不同的Source/Timer/Observer,互不影响
可以定义自己的mode,但是几乎没有这么做的;

关于CFRunLoopModeRef

  • CFRunLoopModeRef代表RunLoop的运行模式

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

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

  • 如果需要切换Mode,只能退出当前的loop(但并不会退出程序,不要混淆),再重新选择一个Mode进入;
    不同组的Source0/Source1/Timer/Observer能分割开来,互不影响

  • 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立刻退出

  • 常见的几种Mode:

    • kCFRunLoopDefaultMode == NSDefaultRunLoopMode : App的默认Mode,通常主线程是在这个Mode下运行
    • UITrackingRunLoopMode: 界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
      -kCFRunLoopCommonModes :默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
      kCFRunLoopCommonModes并不是一个真的模式,它只是一个标记,如:被标记的 Timer可以在kCFRunLoopDefaultMode模式和UITrackingRunLoopMode模式下运行。
  • 基本用不到的Mode:

    • UIInitializationRunLoopMode :私有的mode,App启动的时候的状态,加载出第一个页面后,就转成了Default
    • GSEventReceiveRunLoopMode接受系统事件的内部 Mode,通常用不到

CFRunLoopMode中各成员的作用


typedef struct   __CFRunLoopMode  *CFRunLoopModeRef;
struct   __CFRunLoopMode {
    CFStringRef   _name;            //模式名
    CFMutableSetref   _sources0;    //CFRunLoopSourceRef,事件处理: 点击 、PerformSelector(触摸事件)
    CFMutableSetref   _sources1;    //基于Prot(端口)的线程间通信,系统时间捕捉
    CFMutableArrayRef   _observers; //CFRunLoopObserverRef   监听器
    CFMutableArrayRef   _timers;    //CFRunLoopTimeRef  定时器
}

CFRunLoopSource


CFRunLoopSource是RunLoop的数据源抽象类,类似Objective-C中的协议protocol,实现了这个protocol就可以充当RunLoop的数据源(几乎没有这么做的),RunLoop自己定义了两个Source:Source0Source1

  • Source0: 处理App内部事件; 比如:
    - 屏幕响应UIEvent, CFSocket(套接字),我们点击屏幕(touchesBegan:withEvent:)就是Source0事件
    - 开发者主动实现的线程间通信:performSelector:onThread:
aSelector: 方法名
onThread: 需要访问的线程
withObject: 实现SEL方法时传递的参数
waitUntilDone:   
- 设置为YES: 表示需要等当前方法选择器中的方法执行完再执行下面的代码,防止还没停止当前实例对象就先释放掉了。
- 设置为NO:表示不需要等当前方法选择器中的方法执行完,可以继续执行下面的代码
modes: 一个字符串数组,它标识允许在其中执行指定选择器的模式。此数组必须包含至少一个字符串。如果为该参数指定nil或空数组,则此方法将在不执行指定的选择器的情况下返回。

//给某个线程发送消息
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array

//给主线程发消息
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array
  • Source1: 由内核管理,比如: 基于mach_port端口的线程或进程间通信、系统事件捕捉
    事件是通过Source1来捕捉,然后再分发到Source0来处理的

注意:mach_port是iOS系统中进程间通信的一种方式,如果进程1往一个port中发送一个消息,此时进程2监听了这个port,就会拿到这个消息
NSPort是对CoreFoundation中的CFMachPortCFMessagePort的封装
来看一下对source事件的定义

RunLoop中对于source事件的定义.png

CFRunLoopSource中用union确保这个source要么是source0,要么是source1
看一下source0和source1的具体定义:

source0的定义.png source1的定义.png

souce0中定义的都是函数指针
source1中除了函数指针,还有一个mach_port

Timers

NSTimer
performSelector:withObject:afterDelay:

  • 该方法是拿到RunLoop并且把Timer添加到RunLoop里面,但不会启动RunLoop;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

CFRunLoopObserver


CFRunLoopObserver相当于观察者模式的观察者,用来向观察者报告RunLoop当前的状态

  • 用于监听RunLoop的状态
  • UI刷新(BeforeWaiting
  • Autorelease pool(BeforeWaiting)自动释放池
    AutoreleasePool在RunLoop的两次sleep之间对AutoreleasePool进行pop和push,将这次loop中产生的Autorelease对象进行释放

    下面是RunLoop中定义的状态:
/* 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         // 所有状态
};

看以下代码:

self.view.backgroundColor = [UIColor redColor];

view的背景颜色什么时候被修改为红色 ?

执行到到这行代码时,会先把代码保存下来;Observers(监听器)监听到将要休眠之前会刷新UI,在这时候改变了背景颜色为红色。

RunLoop、Mode、[Source、Timer、Observer]之间的关系@2x.png

疑问:以下输出结果相同吗?

    NSLog(@"%p %p", [NSRunLoop currentRunLoop], [NSRunLoop mainRunLoop]);
    NSLog(@"%p %p", CFRunLoopGetCurrent(), CFRunLoopGetMain());

肯定不相同,因为NSRunLoop是对CFRunLoop的封装,会把CFRunLoopGetCurrent()的地址封装在NSRunLoop的地址中;可以通过输出对象NSLog(@"%@", [NSRunLoop mainRunLoop]);看出

    NSLog(@"NSRunLoop地址 %p %p", [NSRunLoop currentRunLoop], [NSRunLoop mainRunLoop]);
    NSLog(@"CFRunLoop地址  %p %p", CFRunLoopGetCurrent(), CFRunLoopGetMain());
    NSLog(@"NSRunLoop 对象 %@", [NSRunLoop mainRunLoop]);

输出结果:

NSRunLoop地址 0x6000018e1fe0 0x6000018e1fe0
CFRunLoop地址 0x6000000e4200 0x6000000e4200
NSRunLoop 对象 <CFRunLoop 0x6000000e4200 [0x110d44ae8]>
很明显可以看出:NSRunLoop的结构中存储着0x6000000e4200,0x6000000e4200就是CFRunLoop的地址;

  • 0x6000000e4200是真正的runloop
  • 0x6000018e1fe0代表的是OC对象NSRunLoop的地址

RunLoop监听状态监听逻辑

// 创建Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
//监听到状态的block回调
        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的本质

    iOS底层原理总结 - RunLoop 面试题 讲讲 RunLoop,项目中有用到吗? RunLoop内部实现逻辑...

  • iOS 底层 - runloop内部的数据结构

    本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗...

  • RunLoop

    RunLoop思考 讲讲RunLoop,项目中实际应用? RunLoop内部实现逻辑以及数据结构? RunLoop...

  • iOS底层面试题--RunLoop

    什么是RunLoop? iOS底层面试题--RunLoop RunLoop面试题分析

  • Runloop

    Runloop 实现原理及应用iOS - RunLoop 底层源码详解及具体运用

  • RunLoop数据结构、RunLoop的实现机制、RunLoop

    推荐阅读:备战2020——iOS全新面试题总结 RunLoop概念 RunLoop的数据结构 RunLoop的Mo...

  • RunLoop

    详细文章 xx_cc - iOS底层原理总结 - RunLoop 意一ineyee - RunLoop RunLo...

  • iOS底层原理——浅谈RunLoop

    RunLoop应用:线程保活 线程保活、控制销毁 iOS-浅谈RunLoop8iOS底层原理总结 - RunLoo...

  • RunLoop相关

    iOS底层原理总结 - RunLoop解密 Runloop Runloop是一种在当前线程,持续调度各种任务的运行...

  • iOS RunLoop

    概念 数据结构 事件循环机制 Runloop 与 NSTimer 什么是RunLoop 是通过内部维护事件循环来对...

网友评论

      本文标题:iOS 底层 - runloop内部的数据结构

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