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

2020-04-05  本文已影响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

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

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

注意: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:

- (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当前的状态

/* 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的地址;

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);

上一篇 下一篇

猜你喜欢

热点阅读