RunLoop-深入理解(进阶篇)

本篇内容较深入理解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 *)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-深入理解(进阶篇))