ios runloop学习

时间:2022-01-24 00:00:44

今天突然才之间才意识到NSTimer这样的运行方式,是在多线程中实现的循环还是在主线程中去实现的呢。当然不可能是在主线程中的while那么简单,那样什么都干不了,简单看了下NSTimer是以同步方式运行的。时间到了,消息发出后,ontimer的函数是在主线程上调用的。

我们会经常看到这样的代码:

  1. - (IBAction)start:(id)sender
  2. {
  3. pageStillLoading = YES;
  4. [NSThread detachNewThreadSelector:@selector(loadPageInBackground:)toTarget:self withObject:nil];
  5. [progress setHidden:NO];
  6. while (pageStillLoading) {
  7. [NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
  8. }
  9. [progress setHidden:YES];
  10. }

复制代码

这段代码很神奇的,因为他会“暂停”代码运行,而且程序运行不会因为这里有一个while循环而受到影响。在[progress setHidden:NO]执行之后,整个函数想暂停了一样停在循环里面,等loadPageInBackground里面的操作都完成了以后才让[progress setHidden:YES]运行。这样做就显得简介,而且逻辑很清晰。如果你不这样做,你就需要在loadPageInBackground里面表示load完成的地方调用[progress setHidden:YES],显得代码不紧凑而且容易出错。

Run loop就像它的名字一样,是你thread中的一个循环并对收到的事件进行处理。你的代码提供控制语句用来对run loop进行执行——换句话说:你的代码提供while或for循环来驱动run loop。在你的循环中,你使用run loop对象来“运行”事件处理代码。事件处理代码主要进行接收事件并调用事件处理函数。

Run loop从两个不同的事件源中接收消息。Input sources(CFRunLoopSource)投递异步消息,通常来自于另一个thread或另一个应用程序。Timer sources(CFRunLoopTimer)当在计划的时间或重复的时间间隔内投递同步消息。两种事件源都使用应用程序指定的处理方式对到达的事件进行处理。下图展示了run loop和不同的事件源结构。ios runloop学习

如果我们要写多线程的程序,可能就需要自己来管理Run Loop。
下面说一下楼主提出的方法中的参数:
RunMode: NSDefaultRunLoopMode,可以把这个理解为一个”过滤器“,我们可以只对自己关心的事件进行监视。一般NSDefaultRunLoopMode是最常用的。

启动run loop的方法就是lz写的这个,它的说明如下:
Runs the loop once, blocking for input in the specified mode until a given date.
启动run loop一次,在特定的run loop mode下等待输入。

如果没有附加input source或是timer,或是过limitDate,run loop就会退出,并且方法返回NO。

下来是Run Loop的使用场合:
1. 使用port或是自定义的input source来和其他线程进行通信
2. 在线程(非主线程)中使用timer
3. 使用 performSelector…系列(如performSelectorOnThread, …)
4. 使用线程执行周期性工作

run loop不需要创建,在线程中只需要调用[NSRunLoop currentRunLoop]就可以得到
假设我们想要等待某个异步方法的回调。比如connection。如果我们的线程中没有启动run loop,是不会有效果的(因为线程已经运行完毕,正常退出了)。

何时使用 Run Loop

仅当在为你的程序创建辅助线程的时候,你才需要显式运行一个 run loop。Run loop 是程序主线程基础设施的关键部分。所以,Cocoa 和 Carbon 程序提供了代码运 行主程序的循环并自动启动 run loop。IOS 程序中 UIApplication 的 run 方法(或 Mac OS X 中的 NSApplication)作为程序启动步骤的一部分,它在程序正常启动的时 候就会启动程序的主循环。类似的,RunApplicationEventLoop 函数为 Carbon 程序 启动主循环。如果你使用 xcode 提供的模板创建你的程序,那你永远不需要自己去显 式的调用这些例程。

对于辅助线程,你需要判断一个 run loop 是否是必须的。如果是必须的,那么 你要自己配置并启动它。你不需要在任何情况下都去启动一个线程的 run loop。比 如,你使用线程来处理一个预先定义的长时间运行的任务时,你应该避免启动 run loop。Run loop 在你要和线程有更多的交互时才需要,比如以下情况:

 使用端口或自定义输入源来和其他线程通信  使用线程的定时器  Cocoa 中使用任何 performSelector...的方法  使线程周期性工作

如果你决定在程序中使用 run loop,那么它的配置和启动都很简单。和所有线程 编程一样,你需要计划好在辅助线程退出线程的情形。让线程自然退出往往比强制关 闭它更好。关于更多介绍如何配置和退出一个 run loop,参阅”使用 Run Loop 对象” 的介绍。

上代码:

使用runloop阻塞线程的正确写法

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,因此问题就解决了。