NSRunLoop 运行循环的粗浅理解;

1.iOS RunLoop 运行循环

   NSRunLoop  在 main.m 中  UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));  也就是程序启动的时候开启主运行循环;

iOS 系统核心共享这个主运行循环 , 可以把它理解成为一个大的 do/while 循环, 它运行在某个线程中,从各种事件队列中取得事件(每次一个),然后将他分配给合适的监听器

  常常在玩 iphone 手机的时候, 会有这样一种情况, 尤其妈蛋,最近我的iphone5 ,装着 iOS9系统,即在某种情况下, 应用的 UI 会被挂起,主运行循环阻塞,滚动视图无法滚动,按钮无法高亮,定时器也无法触发,直到事件处理完成;

   在调用- (void)applicationWillResignActive:(UIApplication *)application {} 失去焦点的情况下 , 或者 IBAction 或程序的其他入口;

所以在涉及多线程的 问题上尤其需要注意这些;

   同时 NSRunloop 是用来规划事件处理的,当有任务的时候Runloop会让线程处理任务,当没有任务的时候Runloop会让线程处于休眠状态。

       Runloop的管理不完全是自动的,我们必须在合适的时候开启Runloop和处理到达的事件,Cocoa和Core Foundation都提供了Runloop对象来配置和管理线程的Runloop。我们的应用程序不需要显示的创建这些对象,包括应用主线程在内的每一个线程都有一个相关的Runloop对象。而且只有第二线程是需要显示地运行Runloop,主线程是不需要的,APP把主线程Runloop的配置和运行作为了应用程序启动的一部分。

2. 使用场景

   只有创建一个附属线程的时候,才需要明确去运行一个Run Loop.而且是在确定需要的时候才去设置和启动它,否则就必要了。例如,开一个线程去执行一个明确的长时间的任务,就没有必要。起用Run Loop主要还是为了跟创建的线程可以有更多的交互。

而使用Run Loop一个明显的好处就是:节约计算资源,同时也就节约用电了。因为在没有触发的时候线程是处于休眠状态的,不会消耗CPU资源。

3. Runloop 结构图

  Run Loops是线程的基础部份,任何线程,包括主结程,都包含了一个run loop对象,Cocoa和CoreFoundation层都有对应的Run Loop实现。

本质上Run Loop是一个While死循环。不停地监听事件以及处理事件,有不同的运行模式、不同的接收源和定时源,不工作的时候休息等。

 RunLoop 的主要结构:

NSRunLoop 运行循环的粗浅理解;_第1张图片


接下来分析 一下:  输入源的概念:( Input source)  定时源:(Timer Source)

                            Input  Source  一般异步分发 其他线程 或者应用直接传递的消息事件

                            Timer Source  一般同步分发在设定好的时间发生或者循环间断地发生的事件。

                           这两种事件源都是使用应用指定的事件处理方法来处理到达的事件。

从图中可以看出,Input  输入源的3种形式;

  • 基于端口的输入源
  • 自定义输入源
  • selector源

其中selector源就是常用的“performSelector...”方法。

定时源是常用的NSTimer,定时器类;基机制也是基于Run Loop运行的,只是在指定的间隔时间发送消息给需要处理的回调方法。


这里要明确的一点是: 虽然每个线程都有各自一个运行循环 , 但并不是每个线程都会处理自己的运行循环; 也可以从图上,运行循环需要调用 runUntilDate: 等命令做出响应时,才会执行他们的 do/while 循环


接下来 需要 将这个 源 和 运行模式关联起来 ;  因为 这个相当于要在 NSRunloop 运行 期间选择在什么模式下,进行消息的分发和处理;

  关于运行模式:

一种Runloop运行模式就是一个要监控的Input和Timer事件源的集合或者是一个要通知的Runloop观察者的集合。每次运行Runloop,都要指定一个运行模式(显示地或者隐式地)

相似的,只有和当前运行模式相关的观察者才会被通知Runloop的行为。和其他模式相关的源会保留新的事件直到Runloop运行在了合适的模式才会分发。

NSDefaultRunLoopMode:默认的运行模式,用于大部分操作,除了NSConnection对象事件。

NSConnectionReplyMode:用来监控NSConnection对象的回复的,很少能够用到。

NSModalPanelRunLoopMode:用于标明和Mode Panel相关的事件。

NSEventTrackingRunLoopMode:用于跟踪触摸事件触发的模式(例如UIScrollView上下滚动)。

NSRunLoopCommonModes:是一个模式集合,当绑定一个事件源到这个模式集合的时候就相当于绑定到了集合内的每一个模式。Cocoa应用默认包含Default、Panel、Event Tracking模式,Core Foundation只包含Default模式,我们可以通过CFRunLoopAddCommonMode添加模式。


注意:运行模式不是根据事件类型划分的,而是根据事件源划分的。我们不能通过模式来匹配鼠标按下事件或者键盘事件,但是我们可以用运行模式来监听一组不同的Port、暂时挂起Timers或者改变当前被监控的事件源和Runloop观察者。

举个例子: 你当前滚动视图 : 是在 eventTrackingModel 下 , 那么你的 selector 源 如果是关联在NSDefaultRunloopModel 这个默认的运行模式下的;

那么在滚动视图的时候, 那个在滚动视图的时候,你这个 selector 源中的时间就不会被触发

  关于输入源的部分可以参考http://www.2cto.com/kf/201403/285714.html


定时源的例子

定时源adpujiu是常用的NSTimer,定时器类;基机制也是基于Run Loop运行的,只是在指定的间隔时间发送消息给需要处理的回调方法。 两种方法:

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:4.0 targer:self selector:@selector(fireTimer:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

以及

[NSTimer scheduledTimerWithTimerInterval:10
                                   terget:self
                                   selector:@selector(fireTimer:)
                                   userInfo:nil
                                   repeats:YES];

不过需要注意的是,如果Run Loop没有监视跟定时器相关模式,那么定时器将不会运行。如果定时器开始时,Run Loop正在处理前面的事件,那么它会等Run Loop处理完了才开始。如果Run Loop不再运行,那么定时器也永远不再启动了。



启动方式

  • 无条件启动;
  • 设置超时时间启动,如RunUntilDate方法;
  • 指定某种模式下启动,如RunMode:beforDate:方法;

能出方法

  • 启动时设定好设定超时时间;
  • 显式的停止Run Loop(调用CFRunLoopStop函数);

启动模式

可用模式有 5 种,一般常用的都是default;

  • default
  • connection
  • modal
  • event tracking
  • common modes

例子

在新线程中,注册一个Run Loop的观察者,监听每次循环过程的所有事件;同时启动一个定时器;从日记中可以看到整个Run Loop 的过程,包括启动和结束、每次定时器唤醒时前后的事件、没有任何任务进入休眠的状态。由此可以看出,Run Loop能更加精细地跟整个线程的运行过程交互。

一个 CF 下添加观察者的例子

- (void)viewDidLoad {
    [super viewDidLoad];
    // 在新线程中运行:
    [self performSelectorInBackground:@selector(testRunLoop) withObject:ni;];
}
- (void)testRunloop {
   // 获取当前线程的Run Loop
    NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
    // 创建一个Run Loop 观察者对象;观察事件为每次循环的所有活动;
    CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
    kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
    if (observer)
    {
        // 将Cocoa的NSRunLoop类型转换成Core Foundation的CFRunLoopRef类型
        CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
        // 添加观察才对象到该Run Loop 上
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }
    // 创建定时器,每0.1秒触发
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
    selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
    // 重复启动Run Loop 5次
    NSInteger loopCount = 5;
    do {
    //启动 Run Loop 开始循环,直到指定的时间才结束,这里就是持续1秒;
    //当调用RunUnitDate方法时,观察者检测到循环已经启动,开始根据循环的各个阶段的事件,调用上面注册的myRunLoopObserver回调函数。
    [myRunLoop runUntiDate:[NSDate dateWithTimIntervalSinceNow:1]];
    // 运行完之后,会再一次调用回调函数,状态是KFRunLoopExit,表示循环结束。
    loopCount--;
    } while(loopCount);
    NSlog(@"The End.");
}
- (void)doFireTimer:(NSTimer *)timer
{
    NSLog(@"fire timer");
}
// Run loop观察者的回调函数:
void myRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"run loop entry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"run loop before timers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"run loop before sources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"run loop before waiting");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"run loop after waiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"run loop exit");
            break;
        default:
            break;
    }
}

传送门 转载部分:http://www.jianshu.com/p/327dc62194e5

                          http://www.2cto.com/kf/201403/285714.html

你可能感兴趣的:(ios,定时器,事件循环,运行循环)