文章目录
  1. 1. Runloop和线程有什么关系?
  2. 2. Runloop有哪些mode,其作用是什么?
  3. 3. NSTimer、NSURLConnection
  4. 4. 参考

Runloop和线程有什么关系?

在Cocoa中,每个线程(NSThread)对象中内部都有一个run loop(NSRunLoop)对象用来循环处理输入事件,处理的事件包括两类,一是来自Input sources的异步事件,一是来自Timer sources的同步事件;
run Loop在处理输入事件时会产生通知,可以通过Core Foundation向线程中添加run-loop observers来监听特定事件,以在监听的事件发生时做附加的处理工作。

Runloop 和线程的关系:

  1. 主线程的run loop默认是启动的。

    iOS的应用程序里面,程序启动后会有一个如下的main()函数

    1
    2
    3
    4
    5
    int main(int argc, char * argv[]) {
    @autoreleasepool {
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
    }

重点是UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。

  1. 对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。
  2. 在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop 。

    NSRunLoop *runloop = [NSRunLoop currentRunLoop];

参考链接:《Objective-C之run loop详解》。

Runloop有哪些mode,其作用是什么?

每个run loop可运行在不同的模式下,一个run loop mode是一个集合,其中包含其监听的若干输入事件源,定时器,以及在事件发生时需要通知的run loop observers。运行在一种mode下的run loop只会处理其run loop mode中包含的输入源事件,定时器事件,以及通知run loop mode中包含的observers。

Cocoa中的预定义模式有:

  • Default模式

    定义:NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)

    描述:默认模式中几乎包含了所有输入源(NSConnection除外),一般情况下应使用此模式。

  • Event tracking模式

    定义:UITrackingRunLoopMode(iOS) NSEventTrackingRunLoopMode(cocoa)

    描述:在拖动loop或其他user interface tracking loops时处于此种模式下,在此模式下会限制输入事件的处理。例如,当手指按住UITableView拖动时就会处于此模式。

  • Connection模式

    定义:NSConnectionReplyMode(Cocoa)

    描述:处理NSConnection对象相关事件,系统内部使用,用户基本不会使用。

  • Common模式

    定义:NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)

    描述:这是一个伪模式,其为一组run loop mode的集合,将输入源加入此模式意味着在Common Modes中包含的所有模式下都可以处理。在Cocoa应用程序中,默认情况下Common Modes包含default modes,modal modes,event Tracking modes.可使用CFRunLoopAddCommonMode方法想Common Modes中添加自定义modes。

苹果公开提供的 Mode 有两个:

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
  • NSRunLoopCommonModes(kCFRunLoopCommonModes)

NSTimer、NSURLConnection

NSTimer与NSURLConnection默认运行在default mode下,这样当用户在拖动UITableView处于UITrackingRunLoopMode模式时,NSTimer不能fire,NSURLConnection的数据也无法处理。

NSTimer的例子:

在一个UITableViewController中启动一个0.2s的循环定时器,在定时器到期时更新一个计数器,并显示在label上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-(void)viewDidLoad
{
label =[[[UILabel alloc]initWithFrame:CGRectMake(10, 100, 100, 50)]autorelease];
[self.view addSubview:label];
count = 0;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: 1
target: self
selector: @selector(incrementCounter:)
userInfo: nil
repeats: YES];
}
- (void)incrementCounter:(NSTimer *)theTimer
{
count++;
label.text = [NSString stringWithFormat:@"%d",count];
}

在正常情况下,可看到每隔0.2s,label上显示的数字+1,但当你拖动或按住tableView时,label上的数字不再更新,当你手指离开时,label上的数字继续更新。当你拖动UItableView时,当前线程run loop处于UITrackingRunLoopMode模式,在这种模式下,不处理定时器事件,即定时器无法fire,label上的数字也就无法更新。
解决方法,

  • 一种方法是在另外的线程中处理定时器事件,可把Timer加入到NSOperation中在另一个线程中调度
  • 还有一种方法时修改Timer运行的run loop模式,将其加入到 UITrackingRunLoopMode 模式或 NSRunLoopCommonModes 模式中。
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

NSURLConnection也是如此,见SDWebImage中的描述,以及SDWebImageDownloader.m代码中的实现。修改NSURLConnection的运行模式可使用scheduleInRunLoop:forMode:方法。

1
2
3
4
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15];
NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]autorelease];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];

参考

文章目录
  1. 1. Runloop和线程有什么关系?
  2. 2. Runloop有哪些mode,其作用是什么?
  3. 3. NSTimer、NSURLConnection
  4. 4. 参考