本文主要内容

一.RunLoop的概念
二.RunLoop的数据结构
三.工作循环的完成机制
四.RunLoop与NSTimer
五.RunLoop与多线程

全方位剖析iOS高级技术问题(七)之RunLoop相关问题

一.RunLoop的概念

RunLoop是经过内部维护的工作循环来对工作/音讯进行办理的一个对象。 问题1:什么是工作循环?

  • 没有音讯需求处理时,休眠以防止资源占用;

全方位剖析iOS高级技术问题(七)之RunLoop相关问题

  • 有音讯需求处理时,立刻被唤醒。

全方位剖析iOS高级技术问题(七)之RunLoop相关问题

`扩展`
内核态:在一个进程中,假如有体系调用,此刻进程处于内核态,履行文件操作、网络数据发送等任务,此刻特权等级比较高,为0级。
用户态:当一个进程履行用户自己的代码时,处于用户态,此刻特权等级比较低,为3级。
所有用户程序都是运转在用户态的,可是有时分程序的确需求做一些内核态的工作,例如履行文件操作、网络数据发送等操作;而唯一能够做这些工作的便是操作体系,所以此刻程序就需求操作体系恳求以程序的名义来履行这些操作。
以下是操作体系内存空间分布图:

全方位剖析iOS高级技术问题(七)之RunLoop相关问题

问题2:为什么程序中的main函数能够确保不退出?

  • 在main函数中,调用了UIApplicationMain函数,此函数中会发动主线程的runloop 。runloop是对工作循环的维护机制,能够做到”有事做的时分去做事,没事做的时分从用户态切换到内核态,防止资源的占用,使当时线程处于一个休眠状况”。

二.RunLoop的数据结构

NSRunLoop是CFRunLoop的封装,供给了面向对象的API。

NSRunLoop坐落Foundation中,坐落Core Foundation。CFRunLoop的源码是开源的!

全方位剖析iOS高级技术问题(七)之RunLoop相关问题

2.1 RunLoop的数据结构

  • CFRunLoop
  • CFRunLoopMode
  • Source/Timer/Observer

CFRunLoop

  • pthread:一一对应,RunLoop和线程的联系;
  • currentMode:CFRunLoopMode数据结构;
  • modes:NSMutableSet调集<CFRunLoopMode*>;
  • commonModes:NSMutableSet调集<NSString*>;
  • cmomonModeltems:多个Observer、多个Timer、多个Source组成。

全方位剖析iOS高级技术问题(七)之RunLoop相关问题

问题3:RunLoop和线程之间的联系
1.线程和RunLoop之间的联系是一一对应的;
2.主线程的RunLoop是默许敞开的,子线程的RunLoop默许是不敞开的;
3.子线程的RunLoop在主动获取的状况下,才会创立;
4.子线程的RunLoop在线程结束时,才会毁掉。

问题4:commonModes的效果
1.在iOS上对应的是NSRunLoopCommonModes; 2.commonMode不是实际存在的一种Mode; 3.是同步Source/Timer/Observer到多个Model中的一种技能方案;

CFRunLoopMode

  • name:NSDefaultRunLoopMode
  • sources0:MutableSet
  • source1:MutableSet
  • observers:MutableArray
  • timers:MutableArray

全方位剖析iOS高级技术问题(七)之RunLoop相关问题

CFRunLoopSource

  • source0:需求手动唤醒线程;
  • source1:具备唤醒线程的能力。

CFRunLoopTimer
基于工作的定时器,和NSTimer是toll-free birdged(免费桥转换)的。

CFRunLoopObserver
观测时间点:

  • kCFRunLoopEntry:进口机遇;
  • kCFRunLoopBeforeTimers:告诉观察者RunLoop将对timer相关工作进行处理;
  • kCFRunLoopBeforeSources:行将处理source工作;
  • kCFRunLoopBeforeWaiting:行将进入休眠状况,从用户态切换到内核态;
  • kCFRunLoopAfterWaiting:从用户态切换到内核态不久后
  • kCFRunLoopExit:RunLoop退出告诉

2.2 各个数据结构之间的联系

全方位剖析iOS高级技术问题(七)之RunLoop相关问题

问题5:RunLoop、Model、Source/Timer/Observer联系

  • RunLoop和Model是一对多的联系(从CFRunLoop的数据结构modes)
  • Model和Source/Timer/Observer是一对多的联系。

问题6:RunLoop为什么有多个Model
这样设计的原因是为了起到工作屏蔽的效果。

全方位剖析iOS高级技术问题(七)之RunLoop相关问题

当RunLoop运转在Mode1时,只能接收处理Mode1上的source1、observer、timers工作回调,不能接收其他Mode上source/observer/timer工作回调,起到了工作屏蔽的效果。

三.工作循环的完成机制

无论是NSRunLoop仍是CFRunLoop终究都会调用到CFRunLoopRun()。

3.1 工作循环机制的完成流程

  • 1.在RunLoop发动后,先发送告诉告知观察者当时RunLoop行将发动;
  • 2.之后RunLoop行将处理Timer/Source0工作的告诉发送;
  • 3.接着进入Source0工作的处理;
  • 4.此刻假如有Source1要处理,会经过goto句子完成来进行代码逻辑的调转,处理唤醒时收到的音讯(第8步);
  • 5.假如没有Source1需求处理,线程行将休眠(第6步),一起发送告诉到Observer;
  • 6.从用户态到内核态的切换,线程休眠,等候唤醒;
    • 唤醒条件:Source1、Timer工作、外部手动唤醒
  • 7.线程刚被唤醒,发送告诉给观察者;
  • 8.处理唤醒时收到的音讯;
  • 9.再次回到第2步。

全方位剖析iOS高级技术问题(七)之RunLoop相关问题

问题7:一个处于休眠状况的RunLoop经过哪些工作能唤醒它?

  • Source1回调;
  • Timer工作
  • 外部手动唤醒

问题8:点击APP图标,从程序发动、运转、退出这个过程傍边,体系都发生了什么?

  • 程序发动后,调用main函数后,会调用UIApplicationmain函数,此函数内部会发动主线程的RunLoop,经过一系列处理,终究主线程RunLoop处于休眠状况;
  • 假如此刻点击了屏幕,会产生一个mach_port,基于mach_port终究转成Source1,唤醒主线程,运转处理;
  • 当把程序杀身后,RunLoop退出,而且发送告诉给观察者。RunLoop退出后线程立刻毁掉。

3.2 RunLoop的核心

  • 在main函数中,经过一些列处理,会调用体系函数mach_msg(),就发生了体系调用,此刻会从用户态转为核心态;
  • 在内核态下,一定条件时(Source1/Timer工作/外部手动唤醒),mach_msg()会回来给调用方,此刻程序会从核心态转为用户态。
    全方位剖析iOS高级技术问题(七)之RunLoop相关问题

四.RunLoop与NSTimer

问题9:滑动TableView的时分定时器还会收效吗?
不会收效

  • 1.TableView正常状况是运转在kCFRunLoopDefaultMode形式下,当滑动时会发生Mode的切换,切换到UITrackingRunLoopMode形式。
  • 2.当把Timer/Source/Observer增加到某一个Mode(kCFRunLoopDefaultMode)上面后,假如当时RunLoop运转在另一个Mode(UITrackingRunLoopMode)上,对应的Timer/Source/Observer无法进行处理和回调。

全方位剖析iOS高级技术问题(七)之RunLoop相关问题

解决方案:使用如下函数将Timer/Source/Observer同步到多个Mode中
void CFRunLoopAddTimer(runLoop, timer, commonMode)

五.RunLoop与多线程

  • 线程和RunLopp一一对应
  • 自己创立的线程默许没有RunLoop

问题10:怎样完成一个常驻线程?

  • 为当时线程敞开一个RunLoop;
  • 向该RunLoop中增加一个Port/Source等保持RunLoop的工作循环;
  • 发动该RunLoop。

关键代码逻辑
HGObject.h

#import "HGObject.h"
#implementation HGObject
// 自定义线程
static NSThread *thread = nil;
// 符号是否要继续工作循环
static BOOL runAlways = YES;
+ (NSThread *)threadForDispatch {
    if (thread == nil) {
        // 创立线程:线程安全 
        @synchronized(self) {
            if (thread == nil) {
                thread = [[NSThread alloc] initWithTarget: self selector: @selector(runRequest) object: nil];
                [thread setName: @"com.tonlyele.xxx"];
                // 发动
                [thread start];
            }
        }
    }
    return thread;
}
// 常驻线程完成
+ (void)runRequest {
    // 创立一个Source
    CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
    // 创立RunLoop,一起向RunLoop的DefaultMode下面增加Source
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    // 假如能够运转
    while(runAlways) {
        @autoreleasepool {
            // 令当时RunLoop运转在DefaultMode下面
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
        }
    }
    // 某一机遇静态变量runAlways = NO时,能够确保跳出RunLoop,线程退出
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    CGRelease(source);
}
@end

完成常驻线程代码

// 创立线程
NSThread *subThread = [[NSThread alloc] initWithTarget: self selector: @selector(subThreadEntryPoint) object: nil];
[subThread setName: @"HGThread"];
[subThread start];
// 敞开RunLoop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 假如注释如下行,子线程中的任务无法履行
[runLoop addPort: [NSMachPort port] forMode: NSRunLoopCommonModes];
[runLoop run];

本文总结

问题1:什么是RunLoop,它是怎样做到有事做事,没事休息的?

  • RunLoop是一个经过内部循环对工作或音讯进行办理的对象;
  • 程序运转会调用main函数,在main函数中调用UIApplicationMain,在此函数中会发动主线程的RunLoop;
  • RunLoop运转后,会调用体系mac_msg()方法,发生体系调用,会使程序从用户态转为核心态,此刻线程处于休眠状况;
  • 当外界条件(source回调/Timer工作/Observer)变化时,mach_msg()函数会使得程序从核心态转为用户态,此刻线程处于唤醒状况。

问题2:RunLoop与线程的联系

  • RunLoop与线程是一一对应的;
  • 一个线程默许是没有RunLoop的,需求手动创立(主线程在外)。

问题3:如何完成一个常驻线程?

  • 1.自定义线程,并为线程敞开一个RunLoop;
  • 2.向RunLoop中增加一个Port/Source等保持RunLoop的工作循环;
  • 3.发动该RunLoop

问题4:怎样确保子线程数据回来更新UI的时分,不打断用户的滑动操作?

  • 1.用户滑动时,当时的RunLoop运转在UITrackingRunLoopMode形式下;
  • 2.而网络恳求一般放在子线程进行,子线程回来给主线程的数据要抛给主线程用来更新UI。能够把子线程恳求数据抛回给主线程进行UI更新的逻辑,包装起来提交到主线程的NSDefaultRunLoopMode形式下;
  • 3.当当时用户正在滑动操作时处于UITrackingRunLoopMode形式下,分派到NSDefaultRunLoopMode形式下的任务不会履行;
  • 4.当停止滑动后,当时线程会切换到NSDefaultRunLoopMode形式下,处理子线程上抛给主线程的UI更新的任务,这样就不会打断用户的滑动操作。

有任何问题,欢迎各位谈论指出!觉得博主写的还不错的费事点个赞喽