本篇内容较深入理解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
的时候,系统会给我们创建一个Timer
的source
,加到对应的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就说这么多。
网友评论