Runloop详解

Runloop基础

字面意思

  • 运行循环、跑圈

作用(重大)

  • 保持程序的持续运行 (iOS程序为什么能一直活着不会死)
  • 处理app中的各种事件(比如触摸事件、定时器事件【NSTimer】、selector事件【选择器·performSelector···】)
  • 有事情就做事情,没事情就休息,节省CPU资源,提高程序性能

苹果使用RunLoop实现的功能

  • AutoreleasePool
  • 事件响应
  • 手势识别
  • 界面更新
  • 定时器
  • PerformSelector

Runloop参考资料

(1)苹果官方文档 https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

(2)CFRunLoopRef开源代码下载地址: http://opensource.apple.com/source/CF/CF-1151.16/

Runloop和线程的关系

  • Run loop,正如其名,loop表示某种循环,和run放在一起就表示一直在运行着的循环。实际上,run loop和线程是紧密相连的,可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。Run loops是线程的基础架构部分, Cocoa 和 CoreFoundation 都提供了 run loop 对象方便配置和管理线程的 run loop (以下都以 Cocoa 为例)。每个线程,包括程序的主线程( main thread )都有与之相应的 run loop 对象。
  • 主线程和分线程中的RunLoop
    • 主线程中的RunLoop默认是启动的
    • 对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动

Runloop相关类

五个相关的类
  • CFRunloopRef
  • CFRunloopModeRef【Runloop的运行模式】
  • CFRunloopSourceRef【Runloop要处理的事件源】
  • CFRunloopTimerRef【Timer事件】
  • CFRunloopObserverRef【Runloop的观察者(监听者)】
CFRunloopModeRef

输入图片说明

  • CFRunloopModeRef代表着Runloop的运行模式
  • 一个Runloop中可以有多个mode,一个mode里面又可以有多个source\observer\timer等等
  • 每次runloop启动的时候,只能指定一个mode,这个mode被称为该Runloop的当前mode
  • 如果需要切换mode,只能先退出当前Runloop,再重新指定一个mode进入
  • 这样做主要是为了分割不同组的定时器,源,观察者等,让他们相互之间不受影响
  • 系统默认注册了5个mode
    • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
    • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
    • UIInitializationRunLoopMode: 在刚启动 App 时进入的第一个 Mode,启动完成后就不再使用
    • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
    • kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
CFRunloopTimerRef

是基于时间的触发器,它和 NSTimer 是toll-free bridged 的(无缝接入),可以混用。其包含一个时间间隔和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

CFRunloopSourceRef
  • Source有两个版本:Source0 和 Source1。
    • Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
    • Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。
CFRunLoopObserverRef

是观察者,每个 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
};

上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

Runloop的运行逻辑

  • 文字版流程

    • 输入图片说明
  • 网友图文版流程图

    • 输入图片说明
  • 官方版流程图

    • 输入图片说明

    • The Run Loop Sequence of Events

    • 1. Notify observers that the run loop has been entered.
      2. Notify observers that any ready timers are about to fire.
      3. Notify observers that any input sources that are not port based are about to fire.
      4. Fire any non‐port‐based input sources that are ready to fire.
      5. If a port‐based input source is ready and waiting to fire, process the event immediately. Go to step 9. 
      6. Notify observers that the thread is about to sleep.
      7. Put the thread to sleep until one of the following events occurs:
      An event arrives for a port‐based input source. A timer fires.
      The timeout value set for the run loop expires. The run loop is explicitly woken up.
      8. Notify observers that the thread just woke up. 
      9. Process the pending event.
      If a user‐defined timer fired, process the timer event and restart the loop. Go to step 2.
      If an input source fired, deliver the event.
      If the run loop was explicitly woken up but has not yet timed out, restart the loop. Go to step 2.
      10. Notify observers that the run loop has exited.	
      

RunLoop应用案例

1. RunLoop模拟 和 基本使用
int main(int argc, char * argv[]) {

  do {
	//处理相关任务
    ...
    } while (有事干);
  
     return 0;
}

RunLoop相关API可以使用Foundation和CoreFoundation两种形式访问:

  • Foundation
  • NSRunLoop * runloop1 = [NSRunLoop currentRunLoop];
    • NSRunLoop * mainLoop = [NSRunLoop mainRunLoop];
  • CoreFoundation
    • CFRunLoopGetCurrent();
    • CFRunLoopGetMain();
  • 获取mainLoop的CFRunLoopRef,证明获取的几个runloop其实是一个
  • 主线程中的RunLoop系统默认自动开启
2. Timer&&RunLoop
  • 开启一个Timer scheduled方式开启
  • 分线程中开启定时器
  • 滑动scrollview时,定时器停止
3. Observer && Source
  • 监测Runloop的状态

    • CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

    • 如何监听

    • //创建一个runloop监听者
          CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(),kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
      
              NSLog(@"监听runloop状态改变---%zd",activity);
          });
      
          //为runloop添加一个监听者
          CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
      
          CFRelease(observer);
      
    • 监听的状态

    • typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
          kCFRunLoopEntry = (1UL << 0),   //即将进入Runloop
          kCFRunLoopBeforeTimers = (1UL << 1),    //即将处理NSTimer
          kCFRunLoopBeforeSources = (1UL << 2),   //即将处理Sources
          kCFRunLoopBeforeWaiting = (1UL << 5),   //即将进入休眠
          kCFRunLoopAfterWaiting = (1UL << 6),    //刚从休眠中唤醒
          kCFRunLoopExit = (1UL << 7),            //即将退出runloop
          kCFRunLoopAllActivities = 0x0FFFFFFFU   //所有状态改变
      };
      
  • 添加定时器

  • 添加按钮点击

4. 开辟一个常驻内存的线程

开辟一个常驻内存的线程这种用法在早期版本的AF中有用过,因为NSURLConnection要求发请求的线程和调用回调的线程是一个线程,所以需要指定一条常驻内存的线程来处理回调

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}
 
+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });
    return _networkRequestThread;
}
		
  • 开辟一个线程,绑定方法, 发现只调用一次
  • 线程内部开启 while循环
  • 启动RunLoop,但是RunLoop开启后就退出了, 使用while循环
  • 添加NSMachPort 防止RunLoop退出
5. GCD开启定时器
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s",__func__);
    
    //1.创建GCD中的定时器
    /*
     第一个参数:source的类型DISPATCH_SOURCE_TYPE_TIMER 表示是定时器
     第二个参数:描述信息,线程ID
     第三个参数:更详细的描述信息
     第四个参数:队列,决定GCD定时器中的任务在哪个线程中执行
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    
    //2.设置定时器(起始时间|间隔时间|精准度)
    /*
     第一个参数:定时器对象
     第二个参数:起始时间,DISPATCH_TIME_NOW 从现在开始计时
     第三个参数:间隔时间 2.0 GCD中时间单位为纳秒
     第四个参数:精准度 绝对精准0
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    
    //3.设置定时器执行的任务
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"GCD---%@",[NSThread currentThread]);
    });
    
    //4.启动执行
    dispatch_resume(timer);
    
    self.timer = timer;
}
6. 自动释放池和RunLoop

Autorelease对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并会在当前的 runloop 迭代结束时释放。释放的时机总结起来,可以用下图来表示:

objc_autoreleasePoolPush

objc_autoreleasePoolPop

autoreleasepool与 runloop 的关系图

7. RunLoop和其他
  1. 指定图片的下载时机,滑动单元格时不下载图片,

RunLoop网络相关资料

面试题

  • 什么是RunLoop
    • 从字面意思来看就是运行循环,跑圈,其实它内部相当于是开启了一个do-while循环,在这个循环内部不断的处理各种任务(比如Source、Timer、Observer)
    • 一个线程对应一个RunLoop,主线程的RunLoop默认已经启动,子线程的RunLoop需要手动启动(调用run方法)
    • RunLoop在执行时会对应之有一个Mode,如果当前Mode中没有任何任务,RunLoop会直接退出
  • 在开发中怎么使用RunLoop
    • 开启一个常驻线程,处理任务()
    • 可以控制定时器在特定模式下执行
    • 监听RunLoop状态(进行一些操作,例如不在滑动状态下下载图片)
  • 自动释放池什么时候释放
    • 当即将进入RunLoop中时,创建自动释放池
    • 当前RunLoop处理任务,在处理任务过程中所有延迟释放的对象都会被添加至自动释放池
    • 当RunLoop处理完本次任务,RunLoop即将进入睡眠时,会倾倒自动释放池,向池中的对象所有对象发送release消息

转载于:https://my.oschina.net/KeepDoing/blog/1058147

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值