最早接触runloop的概念,是第一次用NSTimer的时候。一个最简单的例子:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(printMessage:)
userInfo:nil
repeats:YES];
// [[NSRunLoop currentRunLoop] addTimer:timer
// forMode:NSRunLoopCommonModes];
}
如果我们同时在界面上滚动一个scrollview,那么我们会发现在滚动停止之前,控制台是不会有输出的,就好像scrollView在滚动的时候将timer暂停了一样。通过了解后发现,其实是Cocoa的RunLoop Mode在作怪。
我把runloop理解为一种cocoa下的一种消息循环的机制,用来处理各种消息事件。我们在开发的时候一般并不需要手动去创建一个runloop,因为在程序进入mainThread之后其实就为我们创建了默认的的mainRunLoop,通过[NSRunloop currentRunLoop]我们就可以得到当前线程对应的RunLoop对象,而我们需要留意的是在多个runloop之间消息的通知方式。
接上面说到的,开启一个NSTimer实质上是开启了一个新的线程(Runloop)在当前Runloop中注册了一个新的事件源,也就是说除了MainRunloop之外还有一个Runloop存在。而当scrollView在滚动的时候,当前MainRunLoop是处于UITrackingRunLoopMode,在该模式下,不会处理 NSDefaultRunLoopMode的消息(因为Runloop Model不一致),而NSTimer在创建后的RunLoop(B)默认会以NSDefaultRunLoopMode与当前context的Runloop(A)发送消息进行通信。要想在scrollView滚动的同时也接受其他runloop的消息,则需要改变两者之间的RunLoopMode
[[NSRunLoop currentRunLoop] addTimer:timer
forMode:NSRunLoopCommonModes];
类似的问题在前几天修改一个http异步通信模块的时候也碰到了,简单地说是向服务器异步获取图片数据后通知主线程刷新tableView中的图片,但在tableView滚动还没有停止或用户手指还停留在屏幕上的时候,图片一直不会出来。后来发现请求数据的时候用到了NSURLConnection的
- (id)initWithRequest:(NSURLRequest *)request
delegate:(id)delegate;
了解后发现该方法创建的异步请求线程和NSTimer一样,也是NSDefaultRunLoopMode的,与
- (id)initWithRequest:(NSURLRequest *)request
delegate:(id)delegate
startImmediately:(BOOL)startImmediately
不同的是,上面的方法默认创建后默认直接发起请求,并以NSDefaultRunLoopMode与runloop进行消息传递,因此我们需要和NSTimer一样更改他的RunLoopMode。
NSURLConnection *connection = [[NSURLConnection alloc]
initWithRequest:request
delegate:self
startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
[connection start];
一、类定义
- + (NSRunLoop *)currentRunLoop
- 如果调用的线程中没有runloop,那么将会创建一个并返回
- + (NSRunLoop *)mainRunLoop
- 返回主线程的runloop
- - (void)acceptInputForMode:(NSString *)mode beforeDate:(NSDate *)limitDate
运行loop一次或者直到limitDate。如果没有input sources加入到这个loop,那么马上返回;否则一直运行到limitDate,或者接口到一个input source然后返回。
- - (void)addPort:(NSPort *)aPort forMode:(NSString *)mode
- - (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
- port和timer都可以添加到多个mode中
- - (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(id)anArgument
- 取消所有mode中的perform select,argument必须跟指定调用时候的一样
- - (void)cancelPerformSelectorsWithTarget:(id)target
- - (NSString *)currentMode
- 如果run loop没有运行,那么返回nil
- - (CFRunLoopRef)getCFRunLoop
- - (NSDate *)limitDateForMode:(NSString *)mode
- 下一次运行的时间,如果没有指定的mode上没有input source,返回nil
- - (void)performSelector:(SEL)aSelector target:(id)target argument:(id)anArgument order:(NSUInteger)order modes:(NSArray *)modes
- order值越低优先级越高
- - (void)removePort:(NSPort *)aPort forMode:(NSString *)mode
- - (void)run
- 在default mode下无限运行loop,但是如果没有任何input source,会立即返回。手动移除所有已知的inout source并不能保证run loop停止运行,因为系统可能会添加一些input source。
- - (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate
- 运行input source一次,为指定mode的input阻塞直到date的时间。如过没有input source,立即返回并返回NO。
- - (void)runUntilDate:(NSDate *)limitDate
如果没有input source,立即返回。否则在limitDate到来之前,不停的循环。
再详细的就看文档吧
二、RunLoopMode
NSDefaultRunLoopMode 这是大多数操作中使用的模式。
NSConnectionReplyMode 该模式用来监控NSConnection对象。你通常不需要在你的代码中使用该模式。
NSModalPanelRunLoopMode Cocoa使用该模式来标识用于modal panel(模态面板)的事件。
NSEventTracking(UITrackingRunLoopMode) Cocoa使用该模式来处理用户界面相关的事件。
NSRunLoopCommonModes 这是一组可配置的通用模式。将input sources与该模式关联则同时也将input sources与该组中的其它模式进行了关联。对于Cocoa应用,该模式缺省的包含了default,modal以及event tracking模式。
一个常见的问题就是,主线程中一个NSTimer添加在default mode中,当界面上有一些scroll view的滚动频繁发生导致run loop运行在UItraking mode中,从而这个timer没能如期望那般的运行。所以,我们就可以把这个timer加到NSRunLoopCommonModes中来解决(iOS中)。
出处:http://blog.csdn.net/xiazailushang/article/details/11930599
http://bbs.9ria.com/thread-208769-1-1.html