RunLoop 案例代码测试

RunLoop编程官方文档翻译地址:http://www.jianshu.com/p/4c38d16a29f1
下面是里面提到的使用RunLoop的案例.官方提到下面的场景应该考虑使用RunLoop.(具体见翻译文档5.什么时候会用一个run loop)


继承NSThread创建一个MyThread类,重写dealloc方法,打印线程结束的信息。如果程序存在下面的代码,startNewThread方法里面的代码执行完后就会提示线程退出了。没有办法控制线程的运行时间。仅仅能开启辅助线程执行代码,一般情况下这样也足够了,所以苹果官方文档说明run loop的开启是运用在需要和线程有更多交互的场合上的。

   - (void)viewDidLoad {
      [super viewDidLoad];
      MyThread * thread = [[MyThread alloc]initWithTarget:self       
       selector:@selector(startNewThread) object:nil];
      [thread start];
    }
    -(void)startNewThread{
      NSLog(@"辅助线程执行的代码");
    }

1. 使用端口或者自定义的输入源和其他线程通信

这种类型的没有遇到过,文档后面有一些相应地代码,但是感觉比较复杂。不知道什么时候用这种场景,以后用到了补充。

2. 线程上使用定时器

一般情况下,自己使用定时器都是在主线程上,主线程的runloop默认是开启的。定时器提供了一个可以直接将定时器添加到当前线程的run loop的NSDefaultRunLoopMode构造方法,所以很多时候什么都不需要做,就能正常使用。但是如果在非主线程上添加定时器就需要手动开启run loop了。如果不开启的话和上面的代码没有什么区别,线程执行完startNewThread的方法后就退出了,会导致周期性执行定时器任务根本实现不了。那如果我在startNewThread方法里面加上一个do {;} while(1);虽然虽然能防止线程退出,但是线程会进入死循环同样无法执行定时器任务。所以run loop开启的意义可以使得和线程有更多的交互,让线程在忙碌的时候忙碌,不忙碌的时候休眠。对比主线程是一样的,如果主线程有任务主线程会执行任务,没有任务的话就不耗费资源。但是并没有退出(否则app就退出了);

-(void)startNewThread{
    //获取当前线程的runloop对象
    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
    //结构体
    CFRunLoopObserverContext  context = {0, (__bridge void *)(self), NULL, NULL, NULL};
    //创建runloop观察者,绑定run loop。
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                    kCFRunLoopAllActivities, YES, 0,myRunLoopObserver, &context);
    if (observer)
    {
        CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];//获取Core Foundation形式的runloop引用
        //为run loop的默认的模式添加观察者
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }
    // Create and schedule the timer.
    [NSTimer scheduledTimerWithTimeInterval:1 target:self
                                   selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
    
   // NSTimer * timer = [NSTimer timerWithTimeInterval:0.1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        
   //     NSLog(@"纯净的定时器");
    //}];
    //[myRunLoop addTimer:timer forMode:NSDefaultRunLoopMode];
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
}
�定时器.png

上面的代码在辅助线程中,开启了run loop。并为run loop添加了观察者和定时器源,循环里面让run loop运行3秒钟。[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];。RunLoop运行3秒,然后线程退出,在这3秒期间,正确的在该线程上处理了定时器的事件,并且观察者在回调函数中观察到了run loop运行过程需要通知给观察者的状态信息。该案例中如果没有为run loop添加定时器和观察者,线程会如文档描述的那样立刻退出。

run loop运行过程.png

中文翻译可以看翻译文档4. run loop一些列的事件,fire这个单词一直想不出好的中文词对应。下面的枚举观察者可以观察到的run loop的状态。

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),//进入runloop,对应步骤1
    kCFRunLoopBeforeTimers = (1UL << 1),//将要处理一个定时器,对应步骤2
    kCFRunLoopBeforeSources = (1UL << 2),//将要处理一个输入源,对应步骤3
    kCFRunLoopBeforeWaiting = (1UL << 5),//将要进入睡眠,对应步骤6
    kCFRunLoopAfterWaiting = (1UL << 6),//已经唤醒,但是还没有处理唤醒run loop的事件,对应步骤8
    kCFRunLoopExit = (1UL << 7),//将要退出,对应步骤10
    kCFRunLoopAllActivities = 0x0FFFFFFFU //表示监控所有的活动
};

3. 在cocoa应用中使用任意一个performSelector…方法

比较常见的是performSelectorOnMainThread,非主线程中不能更新UI,开启额外线程进行网络请求后有时可以调用这个方法在主线程中更新UI。因为主线程中runloop已经开启了,所以很自然就成功了。但是如果在非主线程执行某个方法。就需要开启线程的runloop了。系统提供了一系列类似的方法。

performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:

执行特定的selector在主线程的下一个run loop回路。这两个方法给你提供了选项来阻断当前线程直到selector被执行完毕。

performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:

执行特定的selector在任意线程上,这些线程通过NSThread对象表示。同样提供了阻断当前线程直到selector被执行。

performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:

在当前线程上下一个run loop回路中执行selector,并附加了延迟选项。因为它等待下一个run loop回路到来才执行selector,这些方法从当前执行的代码中提供了一个自动的微小延迟。多个排队的selector会按照顺序一个一个的执行。

cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:

让你取消一个通过performSelector:withObject:afterDelay: or performSelector:withObject:afterDelay:inModes: method方法发送到当前线程的消息。

*** 这段代码让图片在默认的模式下加载,如果用户在进行滑动操作,不会进入这个模式,可以解决部分滑动卡顿问题 ***

[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];

4. 使得线程不被杀死去做周期性任务

可以根据业务需求,设置runloop运行时间。可以类似于上面的案例。周期性的在新线程上执行runmethod方法。


下面对应了3和4两种情况.

- (void)viewDidLoad {
    [super viewDidLoad];
    MyThread * thread = [[MyThread alloc]initWithTarget:self selector:@selector(longrun) object:nil];
    [thread start];
    [NSTimer scheduledTimerWithTimeInterval:0.5 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [self performSelector:@selector(runmethod) onThread:thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
    }];
}
-(void)longrun{
    NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
   //为了防止runloop退出,添加一个端口。
    [runLoop addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
    [runLoop runUntilDate:[NSDate distantFuture]];
}
-(void)runmethod
{
    NSLog(@"%@ 辅助线程上执行的代码",[NSThread currentThread]);
}

如果需要某个线程一直执行,等待某个条件具备时触发可以参考苹果提供的案例

- (void)skeletonThreadMain
{
    // Set up an autorelease pool here if not using garbage collection.
    BOOL done = NO;
    // Add your sources or timers to the run loop and do any other setup.
    do
    {
        // Start the run loop but return after each source is handled.
        SInt32    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
        // If a source explicitly stopped the run loop, or if there are no
        // sources or timers, go ahead and exit.
        if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
            done = YES;
        // Check for any other exit conditions here and set the
        // done variable as needed.
    }
    while (!done);

    // Clean up code here. Be sure to release any allocated autorelease pools.
}

CFRunLoopRunInMode在这里表示在kCFRunLoopDefaultMode模式下启动,时间长度为10秒,如果源的事件被处理runloop应该退出,而不是非要等到10秒耗尽。返回结果如下

  • 时间耗尽(kCFRunLoopRunTimedOut)
  • CFRunLoopStop函数调用导致runloop(kCFRunLoopRunStopped)
  • 源事件被处理了(kCFRunLoopRunHandledSource)
  • 没有了任何源或定时器(kCFRunLoopRunFinished)

代码设定了一个标志位。并且开启runloop事件为10秒,当然也可以设定为其他的值。在每个源得到处理后返回结果,根据结果,状态设定标志位状态。最后可以进行一些线程退出前的操作。在这里可以依然像上面那些方法一样为runloop添加定时器和其他输入源和观察者。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,757评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,478评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,540评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,593评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,903评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,329评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,659评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,383评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,055评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,337评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,864评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,227评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,820评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,999评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,750评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,365评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,260评论 2 258

推荐阅读更多精彩内容