上一次倉促面試糕簿,給了一份面試題讓我去做疏咐,題目的主要內(nèi)容就是多線程的相關(guān)知識。其中有一題是這樣的:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"這是1");
[self performSelector:@selector(test) withObject:nil afterDelay:0];
NSLog(@"這是3");
});
}
-(void)test{
NSLog(@"這是2");
}
讓我寫出程序的打印順序兼蕊。我寫了1,3,2,結(jié)果不用說是錯了砌函,應(yīng)該是1,3。然后回來就立馬去查詢相關(guān)資料讹俊,才知道錯誤原因垦沉。
屏幕快照 2019-06-25 下午6.10.40.png
API上面說的很清楚,該方法會在當前線程的runloop中添加定時器仍劈,但是我們使用的是異步執(zhí)行+全局并發(fā)厕倍,就會開啟子線程執(zhí)行block中的任務(wù),這個要是不知道贩疙,那就去補一下GCD相關(guān)知識绑青。
performSelector:withObject:afterDelay: 的底層
- 不在 NSObject 中,而是在NSRunLoop 類中
- 帶有 afterDelay 的方法屋群,都是在 NSRunLoop 類中定義的
為何在 主線程就可以調(diào)用 test 方法闸婴,在GCD 中卻不能調(diào)用 test 方法?
- 這是因為 performSelector:withObject:afterDelay: 類的底層調(diào)用了 NSTimer 定時器芍躏。
- 定時器是要添加到 runloop 中去的邪乍。
- 這句話的底層就是 往 runloop 中添加了一個定時器。
- 主線程 默認有 runloop对竣,所以 在主線程 可以調(diào)用 test方法庇楞。
- 而子線程中沒有 runloop ,所以 不會調(diào)用 test 方法否纬。如果想要在 GCD 中調(diào)用 test 方法吕晌,需要自己開啟 runloop 。
那么問題來了临燃,怎么讓它執(zhí)行呢睛驳,有幾個方法,下面一一介紹:
方法一:
[self performSelector:@selector(test) withObject:nil afterDelay:0];
既然我們是afterDelay是0秒之后膜廊,那么我們就稍微修改一下乏沸,用跟它很相近的方法:
[self performSelector:@selector(test) withObject:nil];
該方法跟上面方法最大的區(qū)別就是不再使用定時器,而是直接執(zhí)行爪瓜,這樣就不存在Runloop啟動不啟動的問題了蹬跃。修改后如下:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"這是1");
[self performSelector:@selector(test) withObject:nil];
NSLog(@"這是3");
});
}
-(void)test{
NSLog(@"這是2");
}
執(zhí)行結(jié)果是:
2019-06-25 18:32:20.066710+0800 GCDTest[19808:1137360] 這是1
2019-06-25 18:32:20.066870+0800 GCDTest[19808:1137360] 這是2
2019-06-25 18:32:20.066973+0800 GCDTest[19808:1137360] 這是3
是不是很方便。
方法二
既然上面說到子線程中Runloop沒有啟動铆铆,那就給它一個Runloop讓它啟動蝶缀。具體實現(xiàn)是:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"這是1");
[self performSelector:@selector(test) withObject:nil afterDelay:0];
NSLog(@"這是3");
//子線程開啟Runloop,注意該方法要寫在performSelector方法之后才有效
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
}
-(void)test{
//子線程關(guān)閉Runloop
CFRunLoopStop([NSRunLoop currentRunLoop].getCFRunLoop);
NSLog(@"這是2");
}
打印結(jié)果是:
2019-06-26 11:28:31.507646+0800 GCDTest[6848:233750] 這是1
2019-06-26 11:28:31.507919+0800 GCDTest[6848:233750] 這是3
2019-06-26 11:28:31.508048+0800 GCDTest[6848:233750] 這是2
如果我們此時在test方法中再執(zhí)行一個gcd方法薄货,就不會執(zhí)行翁都,如:
-(void)test{
//子線程關(guān)閉Runloop
CFRunLoopStop([NSRunLoop currentRunLoop].getCFRunLoop);
NSLog(@"這是2");
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(test2) withObject:nil afterDelay:1];
});
}
-(void)test2{
NSLog(@"不會執(zhí)行");
}
打印結(jié)果還是:
2019-06-26 11:29:26.430061+0800 GCDTest[6864:237962] 這是1
2019-06-26 11:29:26.430325+0800 GCDTest[6864:237962] 這是3
2019-06-26 11:29:26.430482+0800 GCDTest[6864:237962] 這是2
這是因為子線程中的runloop已經(jīng)被關(guān)閉了。
方法三
既然我們說主線程的runloop是默認開啟的菲驴,子線程的沒有開啟荐吵,那么我們就把該方法放到主隊列中執(zhí)行就可以了。代碼如下:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"這是1");
[self performSelector:@selector(test) withObject:nil afterDelay:0];
NSLog(@"這是3");
});
}
-(void)test{
NSLog(@"這是2");
}
-(void)test2{
NSLog(@"不會執(zhí)行");
}
執(zhí)行結(jié)果如下:
2019-06-26 11:33:29.554860+0800 GCDTest[6896:247259] 這是1
2019-06-26 11:33:29.555022+0800 GCDTest[6896:247259] 這是3
2019-06-26 11:33:29.555248+0800 GCDTest[6896:247259] 這是2
是不是完美解決了這個問題。
好了先煎,覺得對你有幫助的話記得動手點個贊呦贼涩,關(guān)注我,會給你帶來更多關(guān)于iOS底層的相關(guān)知識薯蝎。