简单的说run loop是事件驱动的一个大循环,如下代码所示
int main(int argc, char * argv[]) {
     //程序一直运行状态
     while (AppIsRunning) {
          //睡眠状态,等待唤醒事件
          id whoWakesMe = SleepForWakingUp();
          //得到唤醒事件
          id event = GetEvent(whoWakesMe);
          //开始处理事件
          HandleEvent(event);
     }
     return 0;
}

Cocoa会涉及到Run Loops的

系统级:GCD,mach kernel,block,pthread
应用层:NSTimer,UIEvent,Autorelease,NSObject(NSDelayedPerforming),
NSObject(NSThreadPerformAddition),CADisplayLink,CATransition,CAAnimation,dispatch_get_main_queue()(GCD中dispatch到main queue的block会被dispatch到main RunLoop执行),NSPort,NSURLConnection,AFNetworking(这个第三方网络请求框架使用在开启新线程中添加自己的run loop监听事件)

RunLoop原理(执行顺序的伪代码)

SetupThisRunLoopRunTimeoutTimer(); // by GCD timer
do {
     __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
     __CFRunLoopDoObservers(kCFRunLoopBeforeSources);

__CFRunLoopDoBlocks();
     __CFRunLoopDoSource0();

CheckIfExistMessagesInMainDispatchQueue(); // GCD

__CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
     var wakeUpPort = SleepAndWaitForWakingUpPorts();
     // mach_msg_trap
     // Zzz...
     // Received mach_msg, wake up
     __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
     // Handle msgs
     if (wakeUpPort == timerPort) {
          __CFRunLoopDoTimers();
     } else if (wakeUpPort == mainDispatchQueuePort) {
          // GCD
          __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
     } else {
          __CFRunLoopDoSource1();
     }
     __CFRunLoopDoBlocks();
} while (!stop && !timeout);

构成

Thread包含一个CFRunLoop,一个CFRunLoop包含一种CFRunLoopMode,mode包含CFRunLoopSource,CFRunLoopTimer和CFRunLoopObserver。

CFRunLoopMode

RunLoop只能运行在一种mode下,如果要换mode当前的loop也需要停下重启成新的。利用这个机制,ScrollView过程中 NSDefaultRunLoopMode的mode会切换UITrackingRunLoopMode来保证ScrollView的流畅滑动不受只能在 NSDefaultRunLoopMode时处理的事件影响滑动。同时mode还是可定制的。

  • NSDefaultRunLoopMode:默认,空闲状态
  • UITrackingRunLoopMode:ScrollView滑动时
  • UIInitializationRunLoopMode:启动时
  • NSRunLoopCommonModes:Mode集合 Timer计时会被scrollView的滑动影响的问题可以通过将timer添加到NSRunLoopCommonModes来解决

//将timer添加到NSDefaultRunLoopMode中
[NSTimer scheduledTimerWithTimeInterval:1.0
     target:self
     selector:@selector(timerTick:)
     userInfo:nil
     repeats:YES];
//然后再添加到NSRunLoopCommonModes里
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
     target:self
     selector:@selector(timerTick:)
     userInfo:nil
     repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

CFRunLoopTimer

NSTimer是对RunLoopTimer的封装

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;

+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;

CFRunLoopSource

  • source0:处理如UIEvent,CFSocket这样的事件
  • source1:Mach port驱动,CFMachport,CFMessagePort

CFRunLoopObserver

Cocoa框架中很多机制比如CAAnimation等都是由RunLoopObserver触发的。observer到当前状态的变化进行通知。

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
     kCFRunLoopEntry = (1UL << 0),
     kCFRunLoopBeforeTimers = (1UL << 1),
     kCFRunLoopBeforeSources = (1UL << 2),
     kCFRunLoopBeforeWaiting = (1UL << 5),
     kCFRunLoopAfterWaiting = (1UL << 6),
     kCFRunLoopExit = (1UL << 7),
     kCFRunLoopAllActivities = 0x0FFFFFFFU
};

使用RunLoop的案例

AFNetworking

使用NSOperation+NSURLConnection并发模型都会面临NSURLConnection下载完成前线程退出导致NSOperation对象接收不到回调的问题。AFNetWorking解决这个问题的方法是按照官方的guidhttps://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html#//apple_ref/occ/instm/NSURLConnection/initWithRequest:delegate:startImmediately:上写的NSURLConnection的delegate方法需要在connection发起的线程runloop中调用,于是AFNetWorking直接借鉴了Apple自己的一个Demohttps://developer.apple.com/LIBRARY/IOS/samplecode/MVCNetworking/Introduction/Intro.html的实现方法单独起一个global thread,内置一个runloop,所有的connection都由这个runloop发起,回调也是它接收,不占用主线程,也不耗CPU资源。

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

类似的可以用这个方法创建一个常驻服务的线程。

TableView中实现平滑滚动延迟加载图片

利用CFRunLoopMode的特性,可以将图片的加载放到NSDefaultRunLoopMode的mode里,这样在滚动UITrackingRunLoopMode这个mode时不会被加载而影响到。

UIImage *downloadedImage = ...;
[self.avatarImageView performSelector:@selector(setImage:)
     withObject:downloadedImage
     afterDelay:0
     inModes:@[NSDefaultRunLoopMode]];

接到程序崩溃时的信号进行自主处理例如弹出提示等

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
while (1) {
     for (NSString *mode in allModes) {
          CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
     }
}

异步测试

- (BOOL)runUntilBlock:(BOOL(^)())block timeout:(NSTimeInterval)timeout
{
     __block Boolean fulfilled = NO;
     void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) =
     ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
          fulfilled = block();
          if (fulfilled) {
               CFRunLoopStop(CFRunLoopGetCurrent());
          }
     };

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting);
     CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

// Run!
     CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);

CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
     CFRelease(observer);

return fulfilled;
}

//NSRunLoop与NSTimer的联系

在Windows时代,大家肯定对SendMessage,PostMessage,GetMessage有所了解,这些都是windows中的消息处理函数,那对应在ios中是什么呢,其实就是NSRunloop这个东西。在ios中,所有消息都会被添加到NSRunloop中,分为‘input source’跟'timer source'种,并在循环中检查是不是有事件需要发生,如果需要那么就调用相应的函数处理。

我们在使用NSTimer的时候,可能会接触到runloop的概念,下面是一个简单的例子:

 1 - (void)viewDidLoad
2 {
3 [super viewDidLoad];
4 // Do any additional setup after loading the view, typically from a nib.
5 NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1
6 target:self
7 selector:@selector(printMessage:)
8 userInfo:nil
9 repeats:YES];
10 }

这个时候如果我们在界面上滚动一个scrollview,那么我们会发现在停止滚动前,控制台不会有任何输出,就好像scrollView在滚动的时候将timer暂停了一样,在查看相应文档后发现,这其实就是runloop的mode在做怪。
runloop可以理解为cocoa下的一种消息循环机制,用来处理各种消息事件,我们在开发的时候并不需要手动去创建一个runloop,因为框架为我们创建了一个默认的runloop,通过[NSRunloop currentRunloop]我们可以得到一个当前线程下面对应的runloop对象,不过我们需要注意的是不同的runloop之间消息的通知方式。

接着上面的话题,在开启一个NSTimer实质上是在当前的runloop中注册了一个新的事件源,而当scrollView滚动的时候,当前的MainRunLoop是处于UITrackingRunLoopMode的模式下,在这个模式下,是不会处理NSDefaultRunLoopMode的消息(因为RunLoop Mode不一样),要想在scrollView滚动的同时也接受其它runloop的消息,我们需要改变两者之间的runloopmode.

1 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

简单的说就是NSTimer不会开启新的进程,只是在Runloop里注册了一下,Runloop每次loop时都会检测这个timer,看是否可以触发。当Runloop在A mode,而timer注册在B mode时就无法去检测这个timer,所以需要把NSTimer也注册到A mode,这样就可以被检测到。

说到这里,在http异步通信的模块中也有可能碰到这样的问题,就是在向服务器异步获取图片数据通知主线程刷新tableView中的图片时,在tableView滚动没有停止或用户手指停留在屏幕上的时候,图片一直不会出来,可能背后也是这个runloop的mode在做怪,嘿嘿。

iOS-RunLoop的更多相关文章

  1. iOS runloop 资源汇总-b

    RunLoop 是 iOS 和 OSX 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理.之后会介绍一下在 iOS 中,苹果是如何利 ...

  2. iOS Runloop理解

    一.RunLoop的定义 当有持续的异步任务需求时,我们会创建一个独立的生命周期可控的线程.RunLoop就是控制线程生命周期并接收事件进行处理的机制. RunLoop是iOS事件响应与任务处理最核心 ...

  3. iOS RunLoop详解

    1. RunLoop简介 1.1 什么是RUnLoop 可以理解为字面的意思:Run表示运行,Loop表示循环.结合在一起就是运行的循环.通常叫做运行循环. RunLoop实际上是一个对象,这个对象在 ...

  4. iOS runLoop 理解

    目录 概述 run loop modes 一.概述 run loop叫事件处理循环,就是循环地接受各种各样的事件.run loop是oc用来管理线程里异步事件的工具.一个线程通过run loop可以监 ...

  5. iOS Runloop 消息循环

    介绍 Runloop是一种事件监听循环,可以理解成一个while死循环,监听到事件就起来,没有就休息. Runloop可以在不同模式下进行切换,iOS有五种模式,其中UIInitializationR ...

  6. ios runloop学习

    今天突然才之间才意识到NSTimer这样的运行方式,是在多线程中实现的循环还是在主线程中去实现的呢.当然不可能是在主线程中的while那么简单,那样什么都干不了,简单看了下NSTimer是以同步方式运 ...

  7. iOS runLoop 原理多线程 总结 NSTimer优化

    可以理解为字面意思:Run 表示运行,Loop 表示循环.结合在一起就是运行的循环的意思.哈哈,我更愿意翻译为『跑圈』.直观理解就像是不停的跑圈. RunLoop 实际上是一个对象,这个对象在循环中用 ...

  8. iOS RunLoop了解和使用

    RunLoop介绍和使用 上次讲了runtime,这次是runloop,虽然两者都是run开头的名词术语,但是在OC中,这两个东西压根没啥联系.这篇文章主要讲讲runloop的一些概念和用法.其中包含 ...

  9. iOS RunLoop简介

    一.什么是RunLoop? RunLoop是运行循环,每个Cocoa应用程序都由一个处于阻塞状态的do/while循环驱动,当有事件发生时,就把事件分派给合适的监听器,如此反复直到循环停止.处理分派的 ...

  10. IOS RunLoop面试题

    一 什么是RunLoop? 从字面意思看就是运行循环,其实内部就是do-while循环,这个循环内部不断地处理各种任务(比 如Source,Timer,Observer) 一个线程对应一个RunLoo ...

随机推荐

  1. spring/spring boot/spring cloud开发总结

    背景        针对RPC远程调用,都在使用dubbo.dubbox等,我们也是如此.由于社区暂停维护.应对未来发展,我们准备尝试新技术(或许这时候也不算什么新技术了吧),选择使用了spring ...

  2. CodeSmith7 系列 破解教程

    CodeSmith[点此下载] 学过三层的人应该认识CodeSmith Generator吧,今天我就跟大家一起探讨下CodeSmith Generator 7.0.2的激活,这最新版本破解的难度也是 ...

  3. 【干货分享】前端面试知识点锦集04(Others篇)——附答案

    四.Others部分 技术类 1.http状态码有哪些?分别代表是什么意思? (1).成功2×× 成功处理了请求的状态码.200 服务器已成功处理了请求并提供了请求的网页.204 服务器成功处理了请求 ...

  4. 封装 用canvas绘制直线的函数--面向对象

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. call_user_func()的参数不能为引用传递 自定义替代方法

    php手册 中关于 请注意,传入call_user_func()的参数不能为引用传递. 关于这个情况的解释,可自己搜索.我们可以自己定义一个函数解决这样的问题,实例如下: <?php ini_s ...

  6. JS函数声明的问题

    三个例子 var a = 10; 2 function test(){ 3 a = 100; 4 console.log(a); 5 console.log(this.a); 6 var a; 7 c ...

  7. 关于jni编译32位、64位动态库(Android.mk和Application.mk文件)

    最近新项目需要编译64位的动态库,这里记录如何配置. 在jni目录下加入Android.mk和Application.mk文件. Application.mk APP_ABI := armeabi a ...

  8. Android开发案例 - 自定义虚拟键盘

    所有包含IM功能的App(如微信, 微博, QQ, 支付宝等)都提供了Emoji表情之类的虚拟键盘,  如下图:    本文只着重介绍如何实现输入法键盘和自定义虚拟键盘的流畅切换, 而不介绍如何实现虚 ...

  9. 实用的圆形图片控件ImageView

    1.用法直接在布局中引用即可 import android.content.Context;import android.content.res.TypedArray;import android.g ...

  10. 谨慎使用Sql server data tool 架构比对排除

      现象:某个架构始终不能更新     VS中使用SSDT很方便,进行架构比对时,可以选择性的更新.但在排除操作上,要相当谨慎.往往排除的并不是一个差异项. 如下图,表或视图的差异,如果有多处,可以选 ...