美文网首页
RunLoop-深入理解(进阶篇)

RunLoop-深入理解(进阶篇)

作者: Zombie_Coder | 来源:发表于2018-01-27 17:42 被阅读21次

    本篇内容较深入理解RunLoop的机制、Perform Selector、以及如何创建一个一直活着的后台线程。
    本篇为进阶篇,承接基础篇的内容,希望大家在阅读前,能够掌握RunLoop的基本概念,这样更能有助于我们理解本篇内容。

    基础概念就不在陈述了,不知道的读者可以移步“初识篇”。

    一、深入理解Perform Selector

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

    - (void)performSelectorOnMianThread{
        [self performSelector:@selector(mainThreadMethod) withObject:nil];
    }
    
    - (void)mainThreadMethod{
        NSLog(@"execute %s",__func__);
    }
    

    这样我们在ViewDidLoad中调用performSelectorOnMianThread,就会立即执行,并且输出:print: execute -[ViewController mainThreadMethod];

    和上面的例子一样,我们使用GCD,让这个方法在后台线程中执行:

    - (void)performSelectorOnBackGroundThread{
        
        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__);
    }
    

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

    - (void)performSelectorOnBackGroundThread{
        
        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的请求都以队列的形式被放到目标线程的RunLoop中。然后目标线程会根据进入RunLoop的顺序来一一执行。
    小结:当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这样来保证线程依旧还在

    扩展

    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];
    }
    

    全篇总结

    通过RunLoop的学习,大家一定要知道它和线程、缓存池之间的关系,屡清楚他们的机制,这样才能真正将RunLoop运用到实际开发中,OK就说这么多。

    相关文章

      网友评论

          本文标题:RunLoop-深入理解(进阶篇)

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