IOS---实例化讲解RunLoop

作者: 击水湘江 | 来源:发表于2016-04-25 18:42 被阅读16781次

实例化讲解RunLoop

之前看过很多有关RunLoop的文章,其中要么是主要介绍RunLoop的基本概念,要么是主要讲解RunLoop的底层原理,很少用真正的实例来讲解RunLoop的,这其中有大部分原因是由于大家在项目中很少能用到RunLoop吧。基于这种原因,本文中将用很少的篇幅来对基础内容做以介绍,然后主要利用实例来加深大家对RunLoop的理解,本文中的代码已经上传GitHub,大家可以下载查看,有问题欢迎Issue我。本文主要分为如下几个部分:

  • RunLoop的基础知识
  • 初识RunLoop,如何让RunLoop进驻线程
  • 深入理解Perform Selector
  • 一直"活着"的后台线程
  • 深入理解NSTimer
  • 让两个后台线程有依赖性的一种方式
  • NSURLConnetction的内部实现
  • AFNetWorking中是如何使用RunLoop的?
  • 其它:利用GCD实现定时器功能
  • 延伸阅读

一、RunLoop的基本概念:

什么是RunLoop?提到RunLoop,我们一般都会提到线程,这是为什么呢?先来看下官方对RunLoop的定义:RunLoop系统中和线程相关的基础架构的组成部分(和线程相关),一个RunLoop是一个事件处理环,系统利用这个事件处理环来安排事务,协调输入的各种事件。RunLoop的目的是让你的线程在有工作的时候忙碌,没有工作的时候休眠(和线程相关)。可能这样说你还不是特别清楚RunLoop究竟是用来做什么的,打个比方来说明:我们把线程比作一辆跑车,把这辆跑车的主人比作RunLoop,那么在没有'主人'的时候,这个跑车的生命是直线型的,其启动,运行完之后就会废弃(没有人对其进行控制,'撞坏'被收回),当有了RunLoop这个主人之后,‘线程’这辆跑车的生命就有了保障,这个时候,跑车的生命是环形的,并且在主人有比赛任务的时候就会被RunLoop这个主人所唤醒,在没有任务的时候可以休眠(在IOS中,开启线程是很消耗性能的,开启主线程要消耗1M内存,开启一个后台线程需要消耗512k内存,我们应当在线程没有任务的时候休眠,来释放所占用的资源,以便CPU进行更加高效的工作),这样可以增加跑车的效率,也就是说RunLoop是为线程所服务的。这个例子有点不是很贴切,线程和RunLoop之间是以键值对的形式一一对应的,其中key是thread,value是runLoop(这点可以从苹果公开的源码中看出来)其实RunLoop是管理线程的一种机制,这种机制不仅在IOS上有,在Node.js中的EventLoop,Android中的Looper,都有类似的模式。刚才所说的比赛任务就是唤醒跑车这个线程的一个source;RunLoop Mode就是,一系列输入的source,timer以及observerRunLoop Mode包含以下几种: NSDefaultRunLoopMode,NSEventTrackingRunLoopMode,UIInitializationRunLoopMode,NSRunLoopCommonModes,NSConnectionReplyMode,NSModalPanelRunLoopMode,至于这些mode各自的含义,读者可自己查询,网上不乏这类资源;

二、初识RunLoop,如何让RunLoop进驻线程

我们在主线程中添加如下代码:

while (1) {
    NSLog(@"while begin");
    // the thread be blocked here
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    // this will not be executed
    NSLog(@"while end");
    
}

这个时候我们可以看到主线程在执行完[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 之后被阻塞而没有执行下面的NSLog(@"while end");同时,我们利用GCD,将这段代码放到一个后台线程中:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  
    while (1) {
        
        NSLog(@"while begin");
        NSRunLoop *subRunLoop = [NSRunLoop currentRunLoop];
        [subRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        NSLog(@"while end");
    }
    
    
});

这个时候我们发现这个while循环会一直在执行;这是为什么呢?我们先将这两个RunLoop分别打印出来:

主线程的RunLoop
由于这个日志比较长,我就只截取了上面的一部分。
我们再看我们新建的子线程中的RunLoop,打印出来之后:
backGroundThreadRunLoop.png
从中可以看出来:我们新建的线程中:
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),

我们看到虽然有Mode,但是我们没有给它soures,observer,timer,其实Mode中的这些source,observer,timer,统称为这个Modeitem,如果一个Mode中一个item都没有,则这个RunLoop会直接退出,不进入循环(其实线程之所以可以一直存在就是由于RunLoop将其带入了这个循环中)。下面我们为这个RunLoop添加个source:

     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  
        while (1) {
        
        NSPort *macPort = [NSPort port];
        NSLog(@"while begin");
        NSRunLoop *subRunLoop = [NSRunLoop currentRunLoop];
        [subRunLoop addPort:macPort forMode:NSDefaultRunLoopMode];
        [subRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        NSLog(@"while end");
        NSLog(@"%@",subRunLoop);
        
    }    
    
});

这样我们可以看到能够实现了和主线程中相同的效果,线程在这个地方暂停了,为什么呢?我们明天让RunLoop在distantFuture之前都一直run的啊?相信大家已经猜出出来了。这个时候线程被RunLoop带到‘坑’里去了,这个‘坑’就是一个循环,在循环中这个线程可以在没有任务的时候休眠,在有任务的时候被唤醒;当然我们只用一个while(1)也可以让这个线程一直存在,但是这个线程会一直在唤醒状态,及时它没有任务也一直处于运转状态,这对于CPU来说是非常不高效的。
小结:我们的RunLoop要想工作,必须要让它存在一个Item(source,observer或者timer),主线程之所以能够一直存在,并且随时准备被唤醒就是应为系统为其添加了很多Item

三、深入理解Perform Selector

我们先在主线程中使用下performselector:

- (void)tryPerformSelectorOnMianThread{

[self performSelector:@selector(mainThreadMethod) withObject:nil]; }

- (void)mainThreadMethod{

NSLog(@"execute %s",__func__);

// print: execute -[ViewController mainThreadMethod]
}

这样我们在ViewDidLoad中调用tryPerformSelectorOnMianThread,就会立即执行,并且输出:print: execute -[ViewController mainThreadMethod];
和上面的例子一样,我们使用GCD,让这个方法在后台线程中执行

 - (void)tryPerformSelectorOnBackGroundThread{

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

[self performSelector:@selector(backGroundThread) onThread:[NSThread currentThread] withObject:nil waitUntilDone:NO];

});
}
- (void)backGroundThread{

NSLog(@"%u",[NSThread isMainThread]);

NSLog(@"execute %s",__FUNCTION__);

}

同样的,我们调用tryPerformSelectorOnBackGroundThread这个方法,我们会发现,下面的backGroundThread不会被调用,这是什么原因呢?
这是因为,在调用performSelector:onThread: withObject: waitUntilDone的时候,系统会给我们创建一个Timer的source,加到对应的RunLoop上去,然而这个时候我们没有RunLoop,如果我们加上RunLoop:

 - (void)tryPerformSelectorOnBackGroundThread{

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

[self performSelector:@selector(backGroundThread) onThread:[NSThread currentThread] withObject:nil waitUntilDone:NO];

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];

});
}

这时就会发现我们的方法正常被调用了。那么为什么主线程中的perfom selector却能够正常调用呢?通过上面的例子相信你已经猜到了,主线程的RunLoop是一直存在的,所以我们在主线程中执行的时候,无需再添加RunLoop。从Apple的文档中我们也可以得到验证:

Each request to perform a selector is queued on the target thread’s run loop and the requests are then processed sequentially in the order in which they were received. 每个执行perform selector的请求都以队列的形式被放到目标线程的run loop中。然后目标线程会根据进入run loop的顺序来一一执行。

小结:当perform selector在后台线程中执行的时候,这个线程必须有一个开启的runLoop

四、一直"活着"的后台线程

现在有这样一个需求,每点击一下屏幕,让子线程做一个任务,然后大家一般会想到这样的方式:

@interface ViewController ()

@property(nonatomic,strong) NSThread *myThread;

@end

@implementation ViewController

 - (void)alwaysLiveBackGoundThread{

NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(myThreadRun) object:@"etund"];
self.myThread = thread;
[self.myThread start];

}
- (void)myThreadRun{

NSLog(@"my thread run");
    
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    NSLog(@"%@",self.myThread);
    [self performSelector:@selector(doBackGroundThreadWork) onThread:self.myThread withObject:nil waitUntilDone:NO];
}
- (void)doBackGroundThreadWork{

    NSLog(@"do some work %s",__FUNCTION__);
    
}
@end

这个方法中,我们利用一个强引用来获取了后台线程中的thread,然后在点击屏幕的时候,在这个线程上执行doBackGroundThreadWork这个方法,此时我们可以看到,在touchesBegin方法中,self.myThread是存在的,但是这是为是什么呢?这就要从线程的五大状态来说明了:新建状态、就绪状态、运行状态、阻塞状态、死亡状态,这个时候尽管内存中还有线程,但是这个线程在执行完任务之后已经死亡了,经过上面的论述,我们应该怎样处理呢?我们可以给这个线程的RunLoop添加一个source,那么这个线程就会检测这个source等待执行,而不至于死亡(有工作的强烈愿望而不死亡):

 - (void)myThreadRun{
 
 [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; 
 [[NSRunLoop currentRunLoop] run]

  NSLog(@"my thread run");
    
}

这个时候再次点击屏幕,我们就会发现,后台线程中执行的任务可以正常进行了。
小结:正常情况下,后台线程执行完任务之后就处于死亡状态,我们要避免这种情况的发生可以利用RunLoop,并且给它一个Source这样来保证线程依旧还在

五、深入理解NSTimer

我们平时使用NSTimer,一般是在主线程中的,代码大多如下:

 - (void)tryTimerOnMainThread{

NSTimer *myTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self       
    selector:@selector(timerAction) userInfo:nil repeats:YES];
    
[myTimer fire];

}

- (void)timerAction{

NSLog(@"timer action");

}

这个时候代码按照我们预定的结果运行,如果我们把这个Tiemr放到后台线程中呢?

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   
    NSTimer *myTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];

    [myTimer fire];
    
});

这个时候我们会发现,这个timer只执行了一次,就停止了。这是为什么呢?通过上面的讲解,想必你已经知道了,NSTimer,只有注册到RunLoop之后才会生效,这个注册是由系统自动给我们完成的,既然需要注册到RunLoop,那么我们就需要有一个RunLoop,我们在后台线程中加入如下的代码:

    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop run];

这样我们就会发现程序正常运行了。在Timer注册到RunLoop之后,RunLoop会为其重复的时间点注册好事件,比如1:10,1:20,1:30这几个时间点。有时候我们会在这个线程中执行一个耗时操作,这个时候RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer,这就造成了误差(Timer有个冗余度属性叫做tolerance,它标明了当前点到后,容许有多少最大误差),可以在执行一段循环之后调用一个耗时操作,很容易看到timer会有很大的误差,这说明在线程很闲的时候使用NSTiemr是比较傲你准确的,当线程很忙碌时候会有较大的误差。系统还有一个CADisplayLink,也可以实现定时效果,它是一个和屏幕的刷新率一样的定时器。如果在两次屏幕刷新之间执行一个耗时的任务,那其中就会有一个帧被跳过去,造成界面卡顿。另外GCD也可以实现定时器的效果,由于其和RunLoop没有关联,所以有时候使用它会更加的准确,这在最后会给予说明

六、让两个后台线程有依赖性的一种方式

给两个后台线程添加依赖可能有很多的方式,这里说明一种利用RunLoop实现的方式。原理很简单,我们先让一个线程工作,当工作完成之后唤醒另外的一线程,通过上面对RunLoop的说明,相信大家很容易能够理解这些代码:

- (void)runLoopAddDependance{

self.runLoopThreadDidFinishFlag = NO;
NSLog(@"Start a New Run Loop Thread");
NSThread *runLoopThread = [[NSThread alloc] initWithTarget:self selector:@selector(handleRunLoopThreadTask) object:nil];
[runLoopThread start];

NSLog(@"Exit handleRunLoopThreadButtonTouchUpInside");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
    
    while (!_runLoopThreadDidFinishFlag) {
        
        self.myThread = [NSThread currentThread];
        NSLog(@"Begin RunLoop");
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        NSPort *myPort = [NSPort port];
        [runLoop addPort:myPort forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        NSLog(@"End RunLoop");
        [self.myThread cancel];
        self.myThread = nil;
        
    }
});

 }
- (void)handleRunLoopThreadTask
{
NSLog(@"Enter Run Loop Thread");
for (NSInteger i = 0; i < 5; i ++) {
    NSLog(@"In Run Loop Thread, count = %ld", i);
    sleep(1);
}
#if 0
// 错误示范
_runLoopThreadDidFinishFlag = YES;
// 这个时候并不能执行线程完成之后的任务,因为Run Loop所在的线程并不知道runLoopThreadDidFinishFlag被重新赋值。Run Loop这个时候没有被任务事件源唤醒。
// 正确的做法是使用 "selector"方法唤醒Run Loop。 即如下:
#endif
NSLog(@"Exit Normal Thread");
[self performSelector:@selector(tryOnMyThread) onThread:self.myThread withObject:nil waitUntilDone:NO];

// NSLog(@"Exit Run Loop Thread");
}

七、NSURLConnection的执行过程

在使用NSURLConnection时,我们会传入一个Delegate,当我们调用了[connection start]之后,这个Delegate会不停的收到事件的回调。实际上,start这个函数的内部会获取CurrentRunloop,然后在其中的DefaultMode中添加4个source。如下图所示,CFMultiplexerSource是负责各种Delegate回调的,CFHTTPCookieStorage是处理各种Cookie的。如下图所示:

NSURLConnection的执行过程
从中可以看出,当开始网络传输是,我们可以看到NSURLConnection创建了两个新的线程:com.apple.NSURLConnectionLoader和com.apple.CFSocket.private。其中CFSocket是处理底层socket链接的。NSURLConnectionLoader这个线程内部会使用RunLoop来接收底层socket的事件,并通过之前添加的source,来通知(唤醒)上层的Delegate。这样我们就可以理解我们平时封装网络请求时候常见的下面逻辑了:
    while (!_isEndRequest)
{
    NSLog(@"entered run loop");
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

NSLog(@"main finished,task be removed");

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
 {

  _isEndRequest = YES;
 
 } 

这里我们就可以解决下面这些疑问了:

  1. 为什么这个While循环不停的执行,还需要使用一个RunLoop? 程序执行一个while循环是不会耗费很大性能的,我们这里的目的是想让子线程在有任务的时候处理任务,没有任务的时候休眠,来节约CPU的开支。
  2. 如果没有为RunLoop添加item,那么它就会立即退出,这里的item呢? 其实系统已经给我们默认添加了4个source了。
  3. 既然[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];让线程在这里停下来,那么为什么这个循环会持续的执行呢?因为这个一直在处理任务,并且接受系统对这个Delegate的回调,也就是这个回调唤醒了这个线程,让它在这里循环。

八、AFNetWorking中是如何使用RunLoop的?

在AFN中AFURLConnectionOperation是基于NSURLConnection构建的,其希望能够在后台线程来接收Delegate的回调。
为此AFN创建了一个线程,然后在里面开启了一个RunLoop,然后添加item

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
    [[NSThread currentThread] setName:@"AFNetworking"];
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run];
}

}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{ 
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });
    return _networkRequestThread;
}

这里这个NSMachPort的作用和上文中的一样,就是让线程不至于在很快死亡,然后RunLoop不至于退出(如果要使用这个MachPort的话,调用者需要持有这个NSMachPort,然后在外部线程通过这个port发送信息到这个loop内部,它这里没有这么做)。然后和上面的做法相似,在需要后台执行这个任务的时候,会通过调用:[NSObject performSelector:onThread:..]来将这个任务扔给后台线程的RunLoop中来执行。

- (void)start {
[self.lock lock];
if ([self isCancelled]) {
    [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
    self.state = AFOperationExecutingState;
    [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}

GCD定时器的实现

 - (void)gcdTimer{

// get the queue
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

// creat timer
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// config the timer (starting time,interval)
// set begining time
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
// set the interval
uint64_t interver = (uint64_t)(1.0 * NSEC_PER_SEC);

dispatch_source_set_timer(self.timer, start, interver, 0.0);

dispatch_source_set_event_handler(self.timer, ^{
    
    // the tarsk needed to be processed async
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        for (int i = 0; i < 100000; i++) {
            
            NSLog(@"gcdTimer");
            
            
        }
        
    });

    
});

dispatch_resume(self.timer);

}

九、延伸阅读

  1. http://chun.tips/blog/2014/10/20/zou-jin-run-loopde-shi-jie-%5B%3F%5D-:shi-yao-shi-run-loop%3F/
  2. https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1
  3. http://www.cocoachina.com/ios/20150601/11970.html
  4. http://www.jianshu.com/p/de2716807570
  5. http://blog.csdn.net/enuola/article/details/9163051

相关文章

  • iOS知识点(10)RunLoop

    深入理解RunLoop iOS---实例化讲解RunLoop iOS runloop iOS-RunLoop充满灵...

  • IOS---实例化讲解RunLoop

    实例化讲解RunLoop 之前看过很多有关RunLoop的文章,其中要么是主要介绍RunLoop的基本概念,要么是...

  • 实例化讲解RunLoop

    转自此处 实例化讲解RunLoop 之前看过很多有关RunLoop的文章,其中要么是主要介绍RunLoop的基本概...

  • 实例化讲解 RunLoop

    来源:击水湘江 链接:http://www.jianshu.com/p/536184bfd163 实例化讲解Run...

  • 深入理解RunLoop

    RunLoop是iOS开发中非常底层的一个概念,我们来看看runloop的实现原理,然后结合实例讲解下runloo...

  • iOS & OS X 读书笔记 -- 持续更新中

    Thread & RunLoop & NSTimer Apple文档 当实例化NSTimer对象的时候,通常会使用...

  • CNN卷积层和池化层计算图解

    李宏毅讲解CNN 卷积层操作图解池化层操作图解一次卷积+池化层操作后结果压平层操作图解处理实例讲解1处理实例讲解2...

  • RunLoop讲解

    一般主线程会自动运行runloop,我们一般情况下不用管。而在子线程中,我们需要手动去运行它。你可以把它想象成一个...

  • runloop 讲解

    sources 包括 sources0(比如点击事件、performSelector)、sources1(基于po...

  • 对象方法(实例方法) 类方法(静态方法)的区别

    参考资源 Objective-C中类方法、对象方法介绍及区别iOS---类方法(静态方法)和实例方法iOS 类方法...

网友评论

  • viveco:博主有个疑问 为什么在第二点,添加了
    [subRunLoop addPort:macPort forMode:NSDefaultRunLoopMode]; 就能阻塞呢,这里个macPort就是相当于Source,Observer,Timer吗 ? 如果不是那 这个端口是什么东西? 第二个例子的Source,Observer,Timer 有是那几个呢?
    刚刚接触不是很懂,还请博主解答一下
    击水湘江:@viveco 就是监听了这个端口如果有事件到来,线程就被唤醒,如果没有事件到来,线程就会休眠。其实每个处理器都有很多的中断口,我们每个事件都是通过这些中断口传递给处理器,然后处理器再传递给操作系统。添加macPort的意思就是告诉操作系统,如果这个口上有中断产生,就唤醒这个线程,处理该事件。(可以查下微机原理里有关中断的讲解)
  • funnythansl:博主第三条那句结尾那句 小结:当perform selector在后台线程中执行的时候,这个线程必须有一个开启的runLoop 说的太笼统了不是所有perform selector都需要开启runloop的。
  • c2fffd2b0090:你讲解 NSTimer 的时候,异步线程是需要把timer 添加runloop中的吧~ 是这样吧 [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run]; 而不是你说的只需要添加这两个 NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop run]; 你测试过吗?
  • LD_左岸:请教下大神:
    我现在有一个TabbarController 里面放了两个UIViewController
    分别为OneVc 和 TwoVc

    现在 TwoVc的- (void)viewDidLoad {
    [super viewDidLoad];
    self.tabBarController.selectedIndex = 0;
    };
    这么写 发现 并不给切换

    但是我这么写
    - (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    self.tabBarController.selectedIndex = 0;
    });


    }

    可以切换

    这个和Runloop有关吗???请大神点拨一二
    击水湘江:@左岸__ 不好意思,好久没登简书了。1. 这个应该和RunLoop无关 self performSelector: withObject:self afterDelay:以及NSTimer是基于RunLoop的,dispatch_after不是; 2. 你看下如果不加dispatch_after是不是刚开始self.tabBarController是nil的。3. 我是自定制的TabBar测试是不会出现这个问题。4. 你放到ViewWillAppear中可以吗?
  • 凌云壮志几多愁:对于第四点,做一下评论,你那个作为属性的thread只是一个oc对象,oc对象并不等同于线程。线程在执行一段代码之后就会由操作系统release掉,所以我们需要加一个runloop保证线程常驻……
    Henrya:这么理解就好的多:+1:
  • 辣枭子:楼主好 ,我现在有一个问题没搞明白 就是
    while (1) {

    NSLog(@"while begin");
    // the thread be blocked here
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    NSLog(@"%@",runLoop);


    [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    // this will not be executed

    NSLog(@"while end");
    }
    按照你说的话 放在主线程不应该打印NSLog(@"while end"); 但是demo打印,按道理不应该打印的,因为在主线程里面都有 source 的?求解啊?
    击水湘江:@sytuzhouyong 具体卡在哪里了?还记得吗?我看看哦
    sytuzhouyong:测试了下,发现打印了几次后,程序卡住了,但是为什么会打印了几次,想不明白,大神能解释下吗?
    Henrya:同问,demo是打印while end的,为什么了?
  • 194775dd6f26:很多讲runloop的资料,包括本文,说:RunLoop的目的是让你的线程在有工作的时候忙碌,没有工作的时候休眠(和线程相关)。Runloop就是一个do...while 循环
    我这里有个疑惑:执行这个循环的是哪个线程,如果是当前线程,既然一直在执行着一个do..while循环,那么又是怎么休眠的呢?它不是一直在执行这个do...while 循环么?
    FindCrt:@忆轩 source0 timer这种类型的消息处理完就休眠,然后等待唤醒,唤醒后才进入下一轮循环。do while只是形容而已。
  • Vinc:请问 五、深入理解NSTimer 中提到了『NSTimer,只有注册到RunLoop之后才会生效』,那为什么之前还会执行1次呢?
  • 鼻毛长长:说几个runLoop的使用场景,不然不太理解。就是我有个什么样的需求,我为什么要用runLoop来实现。
    PGOne爱吃饺子:@t0m1sACat 你好,你说的这个runloop使用场景,说拍摄完视频之后,在后台线程进行合并处理,就直接用线程不就可以了么,有关联到runloop么,请指教一下,谢谢
    鼻毛长长:@t0m1sACat :+1:
    620b2582d4ef:@鼻毛长长 我们app是视频处理,拍摄完多个视频后在后台线程进行合并处理,因为视频处理耗时较长,所以当用户刚拍摄完视频就点击预览时就可以根据runloop状态让用户wait,处理完之后再唤醒
  • 一缕殇流化隐半边冰霜:四,一直活着的后台线程,“这个方法中,我们利用一个强引用来获取了后天线程中的thread,然后在点击屏幕的时候”,这里有一个错别字,应该是后台线程,打错成了后天线程了:smiley:
    韦恩时代:@一缕殇流化隐半边冰霜 因为直接fire了
    一缕殇流化隐半边冰霜:@击水湘江 :kissing_closed_eyes::kissing_closed_eyes::kissing_closed_eyes:写的不错,谢谢大神分享!!
    击水湘江:@一缕殇流化隐半边冰霜 嗯,我再commit下,谢谢喽:blush:
  • __Lex: dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {[weak self] in

    let timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self!, selector: #selector(self!.timerAction), userInfo: nil, repeats: true)
    timer.fire()

    }

    不会只执行一次的啊,会一直循环执行啊
    凉风起君子意如何:dispatch_sync这是同步,还是在主线程上,用dispatch_async异步 才是后台线程
  • godgnay:四、一直"活着"的后台线程 中 myThreadRun方法启动RunLoop的为什么run而不是runMode,使用run会一直存在并且NSLog(@"my thread run");不会执行类似于线程阻塞了,run之后不会执行后面的,但是使用runMode,touch屏幕后就会执行NSLog(@"my thread run");因为myThreadRun方法本来就是使用myThread调用的,不需要重复加入RunLoop,楼主帮忙分析下呗
    击水湘江:@shengyang_yu run这个方法的官方说明是这样的:Puts the receiver into a permanent loop, during which time it processes data from all attached input sources.If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by “repeatedly” invoking “runMode:beforeDate:”. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers。从中可以看出其会进入一个"永久的RunLoop"中,并且会持续的调用“runMode:beforeDate:”这个方法,这也就是之后的NSLog不被执行的原因。再看你说的runMode方法:官方的说明是这样子的:“Runs the loop once, blocking for input in the specified mode until a given date.”所以之后的NSLog会执行一次,之后因为其调用的是doBackGroundThreadWork所以不会执行了。
    godgnay:@shengyang_yu 那么在9楼所说的理解二,其实就是在一个用GCD开启后台执行的线程里面是没有RunLoop的,我们只需要添加一次RunLoop就行了,对不?
    godgnay:@shengyang_yu 另外9楼提的问题正和我说的这个类似,当handleRunLoopThreadTask执行for循环完成后,performSelector调用了tryOnMyThread,正是因为使用的是myThread调用在执行了runMode的前提下tryOnMyThread完成后才会执行NSLog(@"End RunLoop");和是不是while语句是没有关系的,对不?
  • 一缕殇流化隐半边冰霜:分析的好透彻!!厉害!!:+1::+1::+1:
    一缕殇流化隐半边冰霜:@击水湘江 确实不错!!谢谢大神的分享!!一起学习:+1::+1::beer::beers::beers:
    击水湘江:@一缕殇流化隐半边冰霜 过奖了,过奖了
  • 扑倒的柔情:学习了
  • dyouknow:理解1:NSRunLoop本身就是个do while循环 (不知道这样理解对不对,如果这样理解,有如下问题)
    1.为什么还要把NSRunLoop放在do while里,它自身就是个do while循环?
    2.后台线程依赖那个例子,perform方法只是唤醒了runloop,为什么那个runloop结束了,执行了NSLog(@"End RunLoop"); ?

    理解二:还是runloop本身不是个do while循环,只是在do while循环里处理事件的一个对象?(如果这样理解,又会有如下问题)
    1.为什么将timer添加到runloop之后可以执行无限次(repeat=YES情况),如果runloop本身不是个循环,那么不是响应一次timer事件之后不就退出runloop了吗?,为什么还可以响应多次?
    击水湘江:@CoderJee 在OC中NSRunLoop是一个对象,其调用run这个方法之后,内部进入一个do while循环,线程进入这个循环中,当source来了就去处理,不来就休眠,以此优化性能。将它放到while循环中是让其被唤醒之后有这么一个运行的'环境',这么一个'家'(不知道怎么表述了)。我当时的注释表述有问题,其实是唤醒线程,[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];之后线程休眠了,之后被唤醒,然后接着执行了NSLog(@"End RunLoop");
  • 一剑书生:经测试发现,同步执行performSelector时并不会添加到runloop中,在延迟执行或者waitUntilDone为NO的情况才需要添加到runloop。
    liuyanhongwl:感觉waitUntilDone:NO就像是delay 0秒的定时器,在下一个事件循环(runloop)执行。
    JasonL:同步执行的话并没有开启新线程,而runloop和线程是联系在一起的
  • 大慈大悲大熊猫:同问,在线程调[self performSelector:@selector(backGroundThread) withObject:nil]也能执行到对应 selector。
    Henrya:@海阔天空_2016 对的
    207ea1179d2a:@大慈大悲大熊猫 [self performSelector:@selector(backGroundThread) withObject:nil]内部实现没有使用timer,所以并不会添加到runloop。只是子线程里面一般的方法调用,跟调用[self backGroundThread]作用一样。
  • 47e23cac8e40:后台线程不开 runloop 的情况下调用到 performSelector 也能执行到对应 selector,怎么解释?
    击水湘江:@KITTENYANG 哦,明白了,七楼的回复可以回答这个问题
    击水湘江:@KITTENYANG 在什么场景呢?我试了,不可以的
    不可数的爱:@KITTENYANG 不会执行的吧,有代码?
  • fallrainy:感觉写得很详细,通俗,有时间慢慢啃一下:smile:
    击水湘江:@fallrainy 恩,相互交流共同进步 :blush:

本文标题:IOS---实例化讲解RunLoop

本文链接:https://www.haomeiwen.com/subject/lfkqrttx.html