NSRunLoop基本原理

NSRunLoop基本原理

  runloop 虽然是与线程相关的重要概念,但 cocoa 中的 runloop 终是用得不多,观相关博文却也未得入门其“why”。在这里通过几个问题简单说明一下run loop的来龙去脉,以简单的代码为主。有错误或不易理解的地方还望指正。

run loop解惑

  线程在执行完后,会被销毁。为了使线程能一直运行,咱们可以在线程里边弄个运行循环(run loop),让线程一直执行:

- (void)myThread:(id)sender
{
  while (TRUE) {
    @autoreleasepool {
      //do some jobs
      //break in some condition
      usleep(10000);
    }
  }
}

  好了,现在线程能够一直运行了。新任务来了:在这个线程运行的同时,还可以从其它线程里往它里面随意增加或去掉不同的计算任务。这就是 NSRunloop 强大的地方了。
  咱们现在来简单地进化一下:

NSMutableArray *targetQueue;
NSMutableArray *actionQueue;
- (void)myThread:(id)sender
{
  while (TRUE) {
    @autoreleasepool {
      //do some jobs
      //break in some condition
      NSUInteger targetCount = [targetQueue count];
      for(NSUInteger index = 0; index < targetCount; ++index){
        id target = targetQueue[index];
        SEL action = NSSelectorFromString(actionQueue[index]);
        if ([target respondsToSelector:action]) {
          [target performSelector:action withObject:nil];
        }
      }
      usleep(10000);
    }
  }
}

  从这里,我们可以在其它线程中向 targetQueue和 actionQueue同时加入对象和方法时,这线程可以执行动态添加的代码了。
  所谓runloop,就是下面这个结构:

while (TRUE) {
    //break in some condition
}

  这个结构就是线程的 runloop,它和 NSRunloop这个类的名字很像,但实际上完全不是一个 东西。那 NSRunloop是个啥东西呢?咱们再看以下代码:

@interface MyNSTimer : NSObject
{
    id target;
    SEL action;
    float interval;
    CFAbsoluteTime lasttime;
}
- (void)invoke;
@end

@implementation MyNSTimer
- (void)invoke;
{
    if ([target respondsToSelector:action]) {
        [target performSelector:action withObject:nil];
    }
}
@end


#!objc
@interface MyNSRunloop : NSObject
{
    NSMutableArray *timerQueue;
}
- (void)addTimer:(MyNSTimer*)t;
- (void)executeOnce;
@end

@implementation MyNSRunloop
- (void)addTimer:(MyNSTimer*)t;
{
    @synchronized(timerQueue){
        [timerQueue addObject:t];
    }
}
- (void)executeOnce;
{
    CFAbsoluteTime currentTime=CFAbsoluteTimeGetCurrent();
    @synchronized(timerQueue){
        for(MyNSTimer *t in timerQueue){
            if(currentTime-t.lasttime>t.interval){
                t.lasttime=currentTime;
                [t invoke];
            }
        }
    }
}
@end

@interface MyNSThread : NSObject
{
    MyNSRunloop *runloop;
}
- (void)main:(id)sender;
@end

@implementation MyNSThread
- (void)main:(id)sender
{
  while (TRUE) {
    @autoreleasepool {
      //do some jobs
      //break in some condition
      [runloop executeOnce];
      usleep(10000);
    }
  }
}
@end

  走到这里,我们就算是基本把Runloop结构抽象出来了。例如我有一个MyNSThread实例,myThread1。我可以给这个实例的线程添加需要的任务,而myThread1内部的MyNSRunloop对象会管理好这些任务。

MyNSTimer *timer1=[MyNSTimer scheduledTimerWithTimeInterval:1 target:obj1 selector:@selector(download1:)];
[myThread1.runloop addTimer:timer1];

MyNSTimer *timer2=[MyNSTimer scheduledTimerWithTimeInterval:2 target:obj2 selector:@selector(download2:)];
[myThread1.runloop addTimer:timer2];

run loop model解惑

  咱们知道,在 iOS中,用户体验是极其重要的。比如 UITableView在滑动时极为顺畅,界面的更新是由主线程负责的,但即使咱们以默认的方式加再多的 NSTimer定时任务到主线程中,即也不会对 UITableView的滑动造成影响。主线程是怎么做到这点的?这就跟 run loop model相关了。
  咱们再来改进一下代码:

@interface MyNSRunloopMode : NSObject {
  NSMutableArray *_timerQueue;
  NSString *_name;
}

- (void)addTimer:(MyNSTimer *)timer;
- (void)executeOnce;
@end

@interface MyNSRunloop : NSObject
{
  NSMutableSet *_modes;
  MyNSRunloopMode *_currentMode;
}
- (void)addTimer:(MyNSTimer*)t;
- (void)addTimer:(MyNSTimer *)t forMode:(NSString *)mode;
- (void)executeOnce;
@end


// 实现文件
@implementation MyNSRunloopMode

- (void)executeOnce {
  CFAbsoluteTime currentTime=CFAbsoluteTimeGetCurrent();
  @synchronized(timerQueue){
    for(MyNSTimer *t in timerQueue){
      if(currentTime-t.lasttime>t.interval){
        t.lasttime=currentTime;
        [t invoke];
      }
    }
  }
}

@end

@implementation MyNSRunloop

- (void)addTimer:(MyNSTimer *)timer {
  [self addTimer:timer forMode:@"NSDefaultRunLoopMode"];
}

- (void)addTimer:(MyNSTimer *)timer forMode:(NSString *)modeName {
  MyNSRunloopMode *mode = nil;
  for (mode in _modes) {
    if ([mode.name isEqualToString:modeName]) {
      break;
    }
  }

  [mode addTimer:timer];
}

- (void)executeOnce {
  [_currentMode executeOnce];
}

@end

  咱们又添加了一个类:MyNSRunloopMode,把原本在 NSRunloop的执行任务放到这个类的对象里面去了。而 NSRunloop则有一个mode的集合,并有一个 currentMode。runloop在任何时候,都只可能运行在currentMode下,也就是说,它此时只会执行该 mode下的任务。咱们可以指定当前 NSRunloop的 mode:[NSRunLoop runMode:beforeDate:]
  系统给定义了好几个mode,而每个 model都有自己的名字,其中有一个就是NSDefaultRunLoopMode。咱们在添加任务到run loop中时,只需要指定相应model的名字,就会把任务添加到相应的model中去了。
  我想你也猜到了吧,正是因为 NSTimer默认会加到NSDefaultRunLoopMode中,而在 UITableView滑动时,主线程却不在这个 mode之下(而是切到NSEventTrackingRunLoopMode中),所以 NSTimer的任务压根儿就不会被执行,当然也就不会对滑动有丝毫影响啦。在滑动完后,主线程又会切到NSDefaultRunLoopMode中,此时 NSTimer的任务又可以执行了。当然咱们还是可以将 NSTimer加到任何 mode之中:[NSRunLoop addTimer:forModel:]
  怎么样,这个 mode的引入还是蛮给力的吧!
  
  本文介绍了 run loop的基本的原理,runloop的真实情况自然是复杂得多,便万变不离其宗,你要有兴趣,也可以研究研究它的实现。

参考:
1、http://lianxu.me/2012/11/10-cocoa-objc-newbie-problems/
2、https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
  

你可能感兴趣的:(ios,线程,runloop)