转载自: iOS RunLoop 初识
深入理解RunLoop
iOS多线程编程指南(三)Run Loop
1.Runloop实现后台子线程常驻,并在不使用时睡眠,使用时唤醒
2.使用runloop阻塞线程的正确写法
3.线程间通信之performSelector:
1.Runloop实现后台子线程常驻,并在不使用时睡眠,使用时唤醒
创建一个单例类,在单例类里面创建一个子线程,实现子线程可以常驻后台,在不使用的时候休眠,使用的时候可以被激活;
(1)创建单例类
+(NSThread*)shareThread {
static NSThread *thread = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
thread = [[NSThread alloc]initWithTarget:self selector:@selector(testShareThread) object:nil];
[thread setName:@"WT share thread test"];
[thread start];
});
return thread;
}
+(void)testShareThread {
NSLog(@"%@ - %@ start", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
}
(2)调用这个单例类,并在里面的子线程中执行方法:
[self performSelector:@selector(testVC) onThread:[WTRunloopTest shareThread] withObject:nil waitUntilDone:NO];
-(void)testVC {
NSLog(@"%@ - %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
}
(3)运行代码结果发现,这个单例的子线程在运行完后,就直接退出了,导致我们调用这个线程执行方法的时候,没有调用到。
怎么样才能是这个单例子线程常驻后台,在不使用的时候休眠,使用的时候唤醒呢;while循环可以使线程常驻后台,runloop可以做到不使用的时候休眠,使用的时候唤醒;
于是我们修改单例类的子线程方法:
+(void)testShareThread {
static BOOL flag = NO;
while (!flag) {
[[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
搞定。
可以有更高级点的写法:
+ (void)threadTest
{
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
给runloop添加事件源,更多runloop和runloop端口、源的只知识可以参考这里iOS多线程编程指南(三)Run Loop
2.使用runloop阻塞线程的正确写法
Runloop可以阻塞线程,等待其他线程执行后再执行。
比如:
@implementation ViewController{
BOOL end;
}
– (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@”start new thread …”);
[NSThread detachNewThreadSelector:@selector(runOnNewThread) toTarget:self withObject:nil];
while (!end) {
NSLog(@”runloop…”);
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
NSLog(@”runloop end.”);
}
NSLog(@”ok”);
}
-(void)runOnNewThread{
NSLog(@”run for new thread …”);
sleep(1);
end=YES;
NSLog(@”end.”);
}
但是这样做,运行时会发现,while循环后执行的语句会在很长时间后才被执行。
那是不是可以这样:
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
缩短runloop的休眠时间,看起来解决了上面出现的问题。
不过这样也又问题,runloop对象被经常性的唤醒,这违背了runloop的设计初衷。runloop的作用就是要减少cpu做无谓的空转,cpu可在空闲的时候休眠,以节约电量。
那么怎么做呢?正确的写法是:
-(void)runOnNewThread{
NSLog(@”run for new thread …”);
sleep(1);
[self performSelectorOnMainThread:@selector(setEnd) withObject:nil waitUntilDone:NO];
NSLog(@”end.”);
}
-(void)setEnd{
end=YES;
}
见黑体斜体字部分,要将直接设置变量,改为向主线程发送消息,执行方法。问题得到解决。
这里要说一下,造成while循环后语句延缓执行的原因是,runloop未被唤醒。因为,改变变量的值,runloop对象根本不知道。延缓的时长总是不定的,这是因为,有其他事件在某个时点唤醒了主线程,这才结束了while循环。那么,向主线程发送消息,将唤醒runloop,因此问题就解决了。
3.线程间通信之performSelector:
参考原文:2019 iOS面试题-----进程、线程、多进程、多线程、任务、队列、NSThread、GCD、NSOprationQueue...
performSelector...只要是NSObject的子类或者对象都可以通过调用方法进入子线程和主线程,其实这些方法所开辟的子线程也是NSThread的另一种体现方式。
在编译阶段并不会去检查方法是否有效存在,如果不存在只会给出警告
//在当前线程。延迟1s执行。响应了OC语言的动态性:延迟到运行时才绑定方法
[self performSelector:@selector(aaa) withObject:nil afterDelay:1];
// 回到主线程。waitUntilDone:是否将该回调方法执行完在执行后面的代码,如果为YES:就必须等回调方法执行完成之后才能执行后面的代码,说白了就是阻塞当前的线程;如果是NO:就是不等回调方法结束,不会阻塞当前线程
[self performSelectorOnMainThread:@selector(aaa) withObject:nil waitUntilDone:YES];
//开辟子线程
[self performSelectorInBackground:@selector(aaa) withObject:nil];
//在指定线程执行
[self performSelector:@selector(aaa) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES]
需要注意的是:如果是带afterDelay的延时函数,会在内部创建一个 NSTimer,然后添加到当前线程的Runloop中。也就是如果当前线程没有开启runloop,该方法会失效。在子线程中,需要启动runloop(注意调用顺序)
[self performSelector:@selector(aaa) withObject:nil afterDelay:1];
[[NSRunLoop currentRunLoop] run];
而performSelector:withObject:只是一个单纯的消息发送,和时间没有一点关系。所以不需要添加到子线程的Runloop中也能执行
网友评论