(004)RunLoop 简单面试题

目录:

 1、RunLoop 是什么?/ RunLoop 的概念
 2、NSRunLoop 和 CFRunLoopRef?
 3、RunLoop 和线程的关系?
 4、RunLoop对外接口 / RunLoop的几个类?
 5、RunLoop内部逻辑?
 6、RunLoop应用场景?


 1、RunLoop 是什么?/ RunLoop 的概念

 RunLoop 是与线程相关的基础架构中的一部分,他是一个处理事件的循环(线程进入这个循环,运行事件处理程序来响应传入的事件)。RunLoop 的目的是当有事件需要处理时,线程是活跃的、忙碌的,当没有事件后,线程进入休眠。
 


 2、NSRunLoop 和 CFRunLoopRef?

 我们不能在一个线程中去操作另外一个线程的run loop对象,那很可能会造成意想不到的后果。不过幸运的是CoreFundation中的不透明类CFRunLoopRef是线程安全的,而且两种类型的run loop完全可以混合使用。Cocoa中的NSRunLoop类可以通过实例方法:

- (CFRunLoopRef)getCFRunLoop;

 获取对应的CFRunLoopRef类,来达到线程安全的目的。
 CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
 NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
 

 3、RunLoop 和线程的关系?

RunLoop和线程是一一对应的关系,从上篇     的底层分析可以知道。在 CFRunLoopRef 里面。创建一个字典,创建一个RunLoop,然后与线程进行绑定 dict[@"pthread_main_thread_np"] = mainLoop。通过 key cvalue 的形式将 字典 和 线程 与 创建的运行循环 RunLoop 进行绑定。默认是主线程、主运行循环,判断没有线程的时候就默认是主线程。
 
 在任何一个Cocoa程序的线程中,都可以通过:
 NSRunLoop   *runloop = [NSRunLoopcurrentRunLoop];
 来获取到当前线程的run loop。

  4、RunLoop对外接口 / RunLoop的几个类?

 在 coreFoundation 里面关于RunLoop 有5个类:
 CFRunLoopRef
 CFRunLoopModeRef
 CFRUnLoopTimerRef
 CGRunLoopObserverRef
 
 其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:
 
 一个RunLoop 包含多个mode
 一个mode 包含多个item(source、timer、Observer)
 
 每次调用RunLoop的时候,只能指定其中一个mode,需要切换mode 的时候需要先退出loop,然后从新指定一个Mode 进入。为了分割开不同item (source、timer、Observer),让其互不影响。
 
 CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。
 source0 只包含了一个回调(函数指针),它并不会主动触发事件。使用时,需要先调用 CFRunLoopSourceSignal(Source),将这个Source标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件。
 
 Source1 包含了一个mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop。
 Observer 观察者,包含一个回调(函数指针),当RunLoop 的状态发生变化时,观察者就能通过回调接受这个变化。

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


 5、RunLoop内部逻辑?

 内部是一个do while 循环。当调用CFRunLoopRun()时,线程就会一直停留在这个循环里,直到超时或者手动停止或完成。该函数才会返回。
 
 CFRunLoopRunSpecific 1、10
 CFRunLoopRun 2-9
 
 1、通知 Observer:即将进入 loop  - Observer
 2、通知 Observer:将要处理 Timer - Observer
 3、通知 Observer:将要处理 Source0 - Observer
 4、处理 Source0                  - Source0
 5、如果有 Source1,跳到第9步       - Source1
 6、通知 Observer:线程即将休眠     - Observer
 7、休眠,等待唤醒                 (source0(port)、Timer、外部h手动唤醒)
 8、通知 Observer:线程刚被唤醒     - Observer
 9、处理唤醒时收到的消息,之后跳回2   - Timer source1
 10、通知 Observer:即将推出 Loop  - Observer
 (004)RunLoop 简单面试题_第1张图片

 6、RunLoop应用场景?

 6.1、AutoreleasePool
    程序启动后系统在主线程的 RunLoop 里面注册了两个 Observer。其回调是_wrapRunLoopWithAutoreleasePoolHandler()。
    第一个 Observer 监听的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。order 是 -2147483647 优先级最高,保证创建释放池发生在其他所有回调之前
    第二个 Observer 监听两个事件:
        1、BeforWaiting(准备进入休眠)时调用 _objc_autoreleasePoolPop() _objc_autoreleasePoolPush()释放旧的池并创建新池;
        2、Exit (即将推出Loop)时调用 _objc_autoreleasePoolPop()来释放自动释放池。这个 Observer 的 order 是247483647 优先级最低,保证其释放池发生在所有回调之后。
 
 6.2、定时器
 NSTimer 。一个NSTimer 注册到RunLoop 后,RunLoop会为其重复的时间点注册好事件。NSTimer 是依赖与mode 的,如果运行循环中的mode 切换了,那么这个时候NSTimer是不会调用的,当再次切换回对应的mode的时候才会继续掉调用。这也就是NSTimer 不是实时性,不准确的原因。
 
 6.3、PerformSelecter
    当调用NSObject 的performSelector:afterDelay: 后,实际上其内部会创建一个TimerT并添加到当前 RunLoop中,如果当前线程RunLoop 没有启动,那么这个方法将失效。
    当调用perfromSelector:onThread:时,实际上其会创建一个 Timer 加到对应的线程去,同样的如果对应线程没有启动 RunLoop 该方法也会失效
 
 6.4、事件响应
    系统注册了一个 Source1(基于mach port的)用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。
    Source1 内核调用其来唤醒RunLoop。
 已经其他一些应用。
UI修改、网络、GCD等
 


 线程常驻:

 这里只说一个官方推荐的方法。
 启动RunLoop 时设定时限,RunLoop将一直运行知道事件到达或者分配的时间到期。如果时间到达,则该事件分配给处理程序进行处理,然后退出此次 RunLoop。可以通过重新启动 RunLoop 处理下一个事件。荣阳如果分配的时间到期。也可以重新启动 RunLoop 来处理。这种方式可以指定 RunLoopMode

#import "LJLRunLoopInterviewVC.h"

@interface LJLRunLoopInterviewVC ()
@property (nonatomic, strong) NSThread *longLifeThread;
@property (nonatomic, assign) CFRunLoopRef runLoopRef;
@property (nonatomic, assign) CFRunLoopSourceRef runLoopSourceRef;
@end

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.longLifeThread = [[NSThread alloc] initWithTarget:self selector:@selector(startUpLongLifeThread) object:nil];
    self.longLifeThread.name = @"Long Life Thread";
    [self.longLifeThread start];
}

 

- (void)startUpLongLifeThread
{
    NSLog(@"Long Life Thread Start Up");

    //将源或计时器添加到RunLoop中,否则RunLoop启动完成后立刻结束
    _runLoopRef = CFRunLoopGetCurrent();
    CFRunLoopSourceContext sourceContext = {};
    sourceContext.info = (__bridge void *)(self);
    sourceContext.perform = &RunLoopInputSourcePerformRoutine;
    _runLoopSourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &sourceContext);
    CFRunLoopAddSource(_runLoopRef, _runLoopSourceRef, kCFRunLoopDefaultMode);

    BOOL done = NO;
    do {
        CFRunLoopRunResult result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 5, YES);
        if (result == kCFRunLoopRunStopped || result == kCFRunLoopRunFinished) {
            done = YES;
        }

    } while (done == NO);

    CFRelease(_runLoopSourceRef);
}

static void RunLoopInputSourcePerformRoutine(void *info)
{
    LJLRunLoopInterviewVC *vc = (__bridge LJLRunLoopInterviewVC *)(info);
    [vc hello];
}

- (void)hello
{
    NSLog(@"Hello Thread %@", [[NSThread currentThread] name]);
}

- (void)dealloc
{
    if (_runLoopRef != NULL) {
        _runLoopRef = NULL;
    }
    
    if (_runLoopSourceRef != NULL) {
        CFRunLoopSourceInvalidate(_runLoopSourceRef);
        _runLoopSourceRef = NULL;
    }
    
    if (self.longLifeThread != nil) {
        [self.longLifeThread cancel];
        self.longLifeThread = nil;
    }
}

 

参考:

https://www.cnblogs.com/jiangzzz/p/5619512.html

https://www.jianshu.com/p/413811babe1e

你可能感兴趣的:(iOS)