返回主线程的runloop
[[NSRunLoop currentRunLoop] runMode:UITrackingRunLoopMode beforeDate:[NSDate distantFuture]];直到接受到当前Mode的输入源就开始运行,或者到时间运行,所以,如果既没有接受到输入源又没有到时间,那么该环路就休眠了【或者说阻塞】
列举两个不错的博客:
博客一:
一、类定义
+ (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中)。
三.疑团重重
来看看这张经典的图片
其中Input source是一些异步的事件,比如port,selector等,这个会让runUntilDate:跳出(当然指的是非主线程中的runloop)。Timer source是同步的,一个timer结束后,在重复时间后或者手动fire后才会再一次调用。
在来看看这张图片
它说明了用户对ui的操作实际上是一种port,会放到一个队列中传到loop,然后由loop交给主线程处理。loop就是一个循环,接受event,传递,继续。主线程是另一个循环,负责事件的处理与界面的显示。当然这两者关系复杂。
在看下面的代码
BOOL pageStillLoading = YES; -(void)press:(id)sender { [(UIButton*)sender setSelected:YES]; NSLog(@"begin"); // 1 [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]]; // 2 NSLog(@"end"); // 3 pageStillLoading = YES; [NSThread detachNewThreadSelector:@selector(loadPageInBackground:)toTarget:self withObject:nil]; // 4 while (pageStillLoading) { [[NSRunLoop currentRunLoop] runMode:UITrackingRunLoopMode beforeDate:[NSDate distantFuture]]; // 5 NSLog(@"while end"); } NSLog(@"over"); } -(void)loadPageInBackground:(id)sender { sleep(3); // 6 NSLog(@"timer"); // 7 pageStillLoading = NO; // 8 }
我在viewcontroller的view上加了一个uibutton,并且事件是press。touch up inside,然后,不在碰触界面(A)。发生了什么?
看看log:
2013-01-06 00:57:21.167 runloop[10146:c07] begin 2013-01-06 00:57:31.171 runloop[10146:c07] end 2013-01-06 00:57:34.173 runloop[10146:3703] timer 2013-01-06 00:58:00.001 runloop[10146:c07] while end 2013-01-06 00:58:00.002 runloop[10146:c07] over
pageStillLoading设置成NO之后过了近30s,while才结束。
如果我把5的mode改成NSRunLoopCommonModes或者在界面上在加一个按钮,然后不停的点击那个按钮(B),结果如下
2013-01-06 01:01:23.944 runloop[10174:c07] begin 2013-01-06 01:01:33.948 runloop[10174:c07] end 2013-01-06 01:01:34.023 runloop[10174:c07] while end . . . 2013-01-06 01:01:36.943 runloop[10174:c07] while end 2013-01-06 01:01:36.950 runloop[10174:370b] timer 2013-01-06 01:01:37.016 runloop[10174:c07] while end 2013-01-06 01:01:37.016 runloop[10174:c07] over
为什么?我们看下流程
当2执行的时候,主线程的事情就是运行runloop 10s,而这个runloop本来就在运行的,所以相当于主线程什么都不用做,空闲状态的主线程当然也可以继续响应界面上的ui事件哦。
当5执行的时候,也没有主线程什么事情,runMode:beforeDate:是要么接受到一个mode上的event,要么到date这个时间。所以,A中要等待很久,这句才会返回;而B中,却是不停的给他event,所以while语句执行了多次。
而且B说明,有非default mode的event一直往run loop中发消息。
博客一:
runloop可以阻塞线程,等待其他线程执行后再执行。
比如:
@implementation ViewController{
BOOL end;
}
…
– (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@”start new thread …”);
[NSThread detachNewThreadSelector:@selector(runOnNewThread) toTarget:self withObject:nil];
while (!end) {
NSLog(@”runloop…”);
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
NSLog(@”runloop end.”);
}
NSLog(@”ok.”);
}
-(void)runOnNewThread{
NSLog(@”run for new thread …”);
sleep(1);
end=YES;
NSLog(@”end.”);
}
但是这样做,运行时会发现,while循环后执行的语句会在很长时间后才被执行。
那是不是可以这样:
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
缩短runloop的休眠时间,看起来解决了上面出现的问题。
不过这样也又问题,runloop对象被经常性的唤醒,这违背了runloop的设计初衷。runloop的作用就是要减少cpu做无谓的空转,cpu可在空闲的时候休眠,以节约电量。
那么怎么做呢?正确的写法是:
-(void)runOnNewThread{
NSLog(@”run for new thread …”);
sleep(1);
[self performSelectorOnMainThread:@selector(setEnd) withObject:nil waitUntilDone:NO];
NSLog(@”end.”);
}
-(void)setEnd{
end=YES;
}
见黑体斜体字部分,要将直接设置变量,改为向主线程发送消息,执行方法。问题得到解决。
这里要说一下,造成while循环后语句延缓执行的原因是,runloop未被唤醒。因为,改变变量的值,runloop对象根本不知道。延缓的时长总是不定的,这是因为,有其他事件在某个时点唤醒了主线程,这才结束了while循环。那么,向主线程发送消息,将唤醒runloop,因此问题就解决了。