performSelector系列的函數(shù)我們都不陌生,但是對于它不同的變種以及底層原理在很多時候還是容易分不清楚,所以筆者希望通過runtime源碼以及GUNStep源碼來一個個抽絲剝繭,把不同變種的performSelector理順,并搞清楚每個方法的底層實現(xiàn),如有錯誤,歡迎指正。本文的代碼已放在 Github ,歡迎自取
一、NSObject 下的 PerformSelector
1.1 performSelector:(SEL)aSelector
performSelector 方法是最簡單的一個 api,使用方法如下
- (void)jh_performSelector
{
[self performSelector:@selector(task)];
}
- (void)task
{
NSLog(@"%s", __func__);
}
// 輸出
2020-03-12 11:13:26.321254+0800 PerformSelectorIndepth[61807:828757] -[ViewController task]
performSelector: 方法只需要傳入一個 SEL,在 runtime 底層實現(xiàn)為:
- (id)performSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL))objc_msgSend)(self, sel);
}
1.2 performSelector:(SEL)aSelector withObject:(id)object
performSelector:withObject: 方法相比于上一個方法多了一個參數(shù),使用起來如下:
- (void)jh_performSelectorWithObj
{
[self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"}];
}
- (void)taskWithParam:(NSDictionary *)param
{
NSLog(@"%s", __func__);
NSLog(@"%@", param);
}
// 輸出
2020-03-12 11:12:34.473153+0800 PerformSelectorIndepth[61790:827408] -[ViewController taskWithParam:]
2020-03-12 11:12:34.473381+0800 PerformSelectorIndepth[61790:827408] {
param = leejunhui;
}
performSelector:withObject: 方法底層實現(xiàn)如下:
- (id)performSelector:(SEL)sel withObject:(id)obj {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}
1.3 performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2
這個方法相比上一個方法又多了一個參數(shù):
- (void)jh_performSelectorWithObj1AndObj2
{
[self performSelector:@selector(taskWithParam1:param2:) withObject:@{@"param1": @"lee"} withObject:@{@"param2": @"junhui"}];
}
- (void)taskWithParam1:(NSDictionary *)param1 param2:(NSDictionary *)param2
{
NSLog(@"%s", __func__);
NSLog(@"%@", param1);
NSLog(@"%@", param2);
}
// 輸出
2020-03-12 11:17:52.889731+0800 PerformSelectorIndepth[61859:833076] -[ViewController taskWithParam1:param2:]
2020-03-12 11:17:52.889921+0800 PerformSelectorIndepth[61859:833076] {
param1 = lee;
}
2020-03-12 11:17:52.890009+0800 PerformSelectorIndepth[61859:833076] {
param2 = junhui;
}
performSelector:withObject:withObject: 方法底層實現(xiàn)如下:
- (id)performSelector:(SEL)sel withObject:(id)obj1 withObject:(id)obj2 {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL, id, id))objc_msgSend)(self, sel, obj1, obj2);
}
1.4 小結(jié)
| 方法 | 底層實現(xiàn) |
|---|---|
| performSelector: | ((id(*)(id, SEL))objc_msgSend)(self, sel) |
| performSelector:withObject: | ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj) |
| performSelector:withObject:withObject: | ((id(*)(id, SEL, id, id))objc_msgSend)(self, sel, obj1, obj2) |
這三個方法應(yīng)該是使用頻率很高的 performSelector 系列方法了,我們只需要記住這三個方法在底層都是執(zhí)行的消息發(fā)送即可。
二、Runloop 相關(guān)的 PerformSelector
如上圖所示,在 NSRunLoop 頭文件中,定義了兩個的分類,分別是
-
NSDelayedPerforming對應(yīng)于NSObject -
NSOrderedPerform對應(yīng)于NSRunLoop
2.1 NSObject 分類 NSDelayedPerforming
2.1.1 performSelector:WithObject:afterDelay:
- (void)jh_performSelectorwithObjectafterDelay
{
[self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:1.f];
}
- (void)taskWithParam:(NSDictionary *)param
{
NSLog(@"%s", __func__);
NSLog(@"%@", param);
}
// 輸出
2020-03-12 11:25:01.475634+0800 PerformSelectorIndepth[61898:838345] -[ViewController taskWithParam:]
2020-03-12 11:25:01.475837+0800 PerformSelectorIndepth[61898:838345] {
param = leejunhui;
}
This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.
這個方法會在當(dāng)前線程所對應(yīng)的 runloop 中設(shè)置一個定時器來執(zhí)行傳入的 SEL。定時器需要在 NSDefaultRunLoopMode 模式下才會被觸發(fā)。當(dāng)定時器啟動后,線程會嘗試從 runloop 中取出 SEL 然后執(zhí)行。
如果 runloop 已經(jīng)啟動并且處于 NSDefaultRunLoopMode 的話,SEL 執(zhí)行成功。否則,直到 runloop 處于 NSDefaultRunLoopMode 前,timer 都會一直等待
通過斷點調(diào)試如下圖所示,runloop 底層最終是通過 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ ()來觸發(fā)任務(wù)的執(zhí)行。
因為 NSRunLoop 并沒有開源,所以我們只能通過 GNUStep 來窺探底層實現(xiàn)細節(jié),如下所示:
- (void) performSelector: (SEL)aSelector
withObject: (id)argument
afterDelay: (NSTimeInterval)seconds
{
NSRunLoop *loop = [NSRunLoop currentRunLoop];
GSTimedPerformer *item;
item = [[GSTimedPerformer alloc] initWithSelector: aSelector
target: self
argument: argument
delay: seconds];
[[loop _timedPerformers] addObject: item];
RELEASE(item);
[loop addTimer: item->timer forMode: NSDefaultRunLoopMode];
}
/*
* The GSTimedPerformer class is used to hold information about
* messages which are due to be sent to objects at a particular time.
*/
@interface GSTimedPerformer: NSObject
{
@public
SEL selector;
id target;
id argument;
NSTimer *timer;
}
- (void) fire;
- (id) initWithSelector: (SEL)aSelector
target: (id)target
argument: (id)argument
delay: (NSTimeInterval)delay;
- (void) invalidate;
@end
我們可以看到,在 performSelector:WithObject:afterDelay: 底層
- 獲取當(dāng)前線程的
NSRunLoop對象。 - 通過傳入的
SEL、argument和delay初始化一個GSTimedPerformer實例對象,GSTimedPerformer類型里面封裝了NSTimer對象。 - 然后把
GSTimedPerformer實例加入到RunLoop對象的_timedPerformers成員變量中 - 釋放掉
GSTimedPerformer對象 - 以
default mode將timer對象加入到runloop中
2.1.2 performSelector:WithObject:afterDelay:inModes
performSelector:WithObject:afterDelay:inModes 方法相比上個方法多了一個 modes 參數(shù),根據(jù)官方文檔的定義,只有當(dāng) runloop 處于 modes 中的任意一個 mode 時,才會執(zhí)行任務(wù),如果 modes 為空,那么將不會執(zhí)行任務(wù)。
- (void)jh_performSelectorwithObjectafterDelayInModes
{
[self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:1.f inModes:@[NSRunLoopCommonModes]];
}
- (void)taskWithParam:(NSDictionary *)param
{
NSLog(@"%s", __func__);
NSLog(@"%@", param);
}
// 打印如下
2020-03-12 11:38:58.479152+0800 PerformSelectorIndepth[62006:851520] -[ViewController taskWithParam:]
2020-03-12 11:38:58.479350+0800 PerformSelectorIndepth[62006:851520] {
param = leejunhui;
}
這里我們?nèi)绻?
modes參數(shù)改為UITrackingRunLoopMode,那么就只有在scrollView發(fā)生滾動的時候才會觸發(fā) timer
我們再看一下 GNUStep 對應(yīng)的實現(xiàn):
- (void) performSelector: (SEL)aSelector
withObject: (id)argument
afterDelay: (NSTimeInterval)seconds
inModes: (NSArray*)modes
{
unsigned count = [modes count];
if (count > 0)
{
NSRunLoop *loop = [NSRunLoop currentRunLoop];
NSString *marray[count];
GSTimedPerformer *item;
unsigned i;
item = [[GSTimedPerformer alloc] initWithSelector: aSelector
target: self
argument: argument
delay: seconds];
[[loop _timedPerformers] addObject: item];
RELEASE(item);
if ([modes isProxy])
{
for (i = 0; i < count; i++)
{
marray[i] = [modes objectAtIndex: i];
}
}
else
{
[modes getObjects: marray];
}
for (i = 0; i < count; i++)
{
[loop addTimer: item->timer forMode: marray[i]];
}
}
}
@end
可以看到,相比于上一個方法的底層實現(xiàn)不同的是,這里會循環(huán)添加不同 mode 的 timer 對象到 runloop 中。
2.1.3 cancelPreviousPerformRequestsWithTarget: 和 cancelPreviousPerformRequestsWithTarget:selector:object:
cancelPreviousPerformRequestsWithTarget: 方法和 cancelPreviousPerformRequestsWithTarget:selector:object: 方法是兩個類方法,它們的作用是取消執(zhí)行之前通過 performSelector:WithObject:afterDelay: 方法注冊的任務(wù)。使用起來如下所示:
- (void)jh_performSelectorwithObjectafterDelayInModes
{
// 只有當(dāng) scrollView 發(fā)生滾動時,才會觸發(fā)timer
// [self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:1.f inModes:@[UITrackingRunLoopMode]];
[self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:5.f inModes:@[NSRunLoopCommonModes]];
}
- (IBAction)cancelTask {
NSLog(@"%s", __func__);
[ViewController cancelPreviousPerformRequestsWithTarget:self selector:@selector(taskWithParam:) object:@{@"param": @"leejunhui"}];
// [ViewController cancelPreviousPerformRequestsWithTarget:self];
}
// 輸出
2020-03-12 11:52:33.549213+0800 PerformSelectorIndepth[62172:865289] -[ViewController cancelTask]
這里有一個區(qū)別,就是 cancelPreviousPerformRequestsWithTarget: 類方法會取消掉 target 上所有的通過 performSelector:WithObject:afterDelay: 實例方法注冊的定時任務(wù),而 cancelPreviousPerformRequestsWithTarget:selector:object: 只會通過傳入的 SEL 取消匹配到的定時任務(wù)
在 GNUStep 中 cancelPreviousPerformRequestsWithTarget: 方法底層實現(xiàn)如下:
/*
* Cancels any perform operations set up for the specified target
* in the current run loop.
*/
+ (void) cancelPreviousPerformRequestsWithTarget: (id)target
{
NSMutableArray *perf = [[NSRunLoop currentRunLoop] _timedPerformers];
unsigned count = [perf count];
if (count > 0)
{
GSTimedPerformer *array[count];
IF_NO_GC(RETAIN(target));
[perf getObjects: array];
while (count-- > 0)
{
GSTimedPerformer *p = array[count];
if (p->target == target)
{
[p invalidate];
[perf removeObjectAtIndex: count];
}
}
RELEASE(target);
}
}
// GSTimedPerformer 實例方法
- (void) invalidate
{
if (timer != nil)
{
[timer invalidate];
DESTROY(timer);
}
}
這里的邏輯其實很清晰:
- 取出當(dāng)前 runloop 對象的成員變量
_timedPerformers - 判斷定時任務(wù)數(shù)組是否為空,不為空才會繼續(xù)往下走
- 初始化一個局部的空的任務(wù)數(shù)組,然后通過
getObjects從成員變量中取出任務(wù) - 通過 while 循環(huán)遍歷所有的任務(wù),如果匹配到了對應(yīng)的
target,則調(diào)用任務(wù)的 invalidate 方法,在這個方法內(nèi)部會把定時器停掉然后銷毀。接著還需要把成員變量_timedPerformers中對應(yīng)的任務(wù)移除掉
另一個取消任務(wù)的方法底層實現(xiàn)如下:
/*
* Cancels any perform operations set up for the specified target
* in the current loop, but only if the value of aSelector and argument
* with which the performs were set up match those supplied.<br />
* Matching of the argument may be either by pointer equality or by
* use of the [NSObject-isEqual:] method.
*/
+ (void) cancelPreviousPerformRequestsWithTarget: (id)target
selector: (SEL)aSelector
object: (id)arg
{
NSMutableArray *perf = [[NSRunLoop currentRunLoop] _timedPerformers];
unsigned count = [perf count];
if (count > 0)
{
GSTimedPerformer *array[count];
IF_NO_GC(RETAIN(target));
IF_NO_GC(RETAIN(arg));
[perf getObjects: array];
while (count-- > 0)
{
GSTimedPerformer *p = array[count];
if (p->target == target && sel_isEqual(p->selector, aSelector)
&& (p->argument == arg || [p->argument isEqual: arg]))
{
[p invalidate];
[perf removeObjectAtIndex: count];
}
}
RELEASE(arg);
RELEASE(target);
}
}
這里的實現(xiàn)不一樣的地方就是除了判斷 target 是否匹配外,還會判斷 SEL 是否匹配,以及參數(shù)是否匹配。
2.1.4 小結(jié)
-
performSelector:WithObject:afterDelay:- 在該方法所在線程的 runloop 處于 default mode 時,根據(jù)給定的時間觸發(fā)給定的任務(wù)。底層原理是把一個 timer 對象以 default mode 加入到 runloop 對象中,等待喚醒。
-
performSelector:WithObject:afterDelay:inModes:- 在該方法所在線程的 runloop 處于給定的任一 mode 時,根據(jù)給定的時間觸發(fā)給定的任務(wù)。底層原理是循環(huán)把一個 timer 對象以給定的 mode 加入到 runloop 對象中,等待喚醒。
-
cancelPreviousPerformRequestsWithTarget:- 取消
target對象通過performSelector:WithObject:afterDelay:方法或performSelector:WithObject:afterDelay:inModes:方法注冊的所有定時任務(wù)
- 取消
-
cancelPreviousPerformRequestsWithTarget:selector:object:- 取消
target對象通過performSelector:WithObject:afterDelay:方法或performSelector:WithObject:afterDelay:inModes:方法注冊的指定的定時任務(wù)
- 取消
這四個方法是作為 NSObject 的 NSDelayedPerforming 分類存在于 NSRunLoop 源代碼中,所以我們在使用的時候要注意一個細節(jié),那就是執(zhí)行這些方法的線程是否是主線程,如果是主線程,那么執(zhí)行起來是沒有問題的,但是,如果是在子線程中執(zhí)行這些方法,則需要開啟子線程對應(yīng)的 runloop 才能保證執(zhí)行成功。
- (void)jh_performSelectorwithObjectafterDelay
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:1.f];
});
// [self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:1.f];
}
- (void)taskWithParam:(NSDictionary *)param
{
NSLog(@"%s", __func__);
NSLog(@"%@", param);
}
// 沒有輸出
如上所示的代碼,通過 GCD 的異步執(zhí)行函數(shù)在全局并發(fā)隊列上執(zhí)行任務(wù),并沒有任何打印輸出,我們加入 runloop 的啟動代碼后結(jié)果將完全不一樣:
對于 performSelector:WithObject:afterDelay:inModes 方法,如果遇到這樣的情況,也是一樣的解決方案。
2.2 NSRunLoop 的分類 NSOrderedPerform
2.2.1 performSelector:target:argument:order:modes:
performSelector:target:argument:order:modes: 方法的調(diào)用者是 NSRunLoop 實例,然后需要傳入要執(zhí)行的 SEL,以及 SEL 對應(yīng)的 target,和 SEL 要接收的參數(shù) argument,最后是此次任務(wù)的優(yōu)先級 order,以及一個 運行模式集合 modes,目的是當(dāng) runloop 的 currentMode 處于這個運行模式集合中的其中任意一個 mode 時,就會按照優(yōu)先級 order 來觸發(fā) SEL 的執(zhí)行。具體使用如下:
- (void)jh_performSelectorTargetArgumentOrderModes
{
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop performSelector:@selector(runloopTask5) target:self argument:nil order:5 modes:@[NSRunLoopCommonModes]];
[runloop performSelector:@selector(runloopTask1) target:self argument:nil order:1 modes:@[NSRunLoopCommonModes]];
[runloop performSelector:@selector(runloopTask3) target:self argument:nil order:3 modes:@[NSRunLoopCommonModes]];
[runloop performSelector:@selector(runloopTask2) target:self argument:nil order:2 modes:@[NSRunLoopCommonModes]];
[runloop performSelector:@selector(runloopTask4) target:self argument:nil order:4 modes:@[NSRunLoopCommonModes]];
}
- (void)runloopTask1
{
NSLog(@"runloop 任務(wù)1");
}
- (void)runloopTask2
{
NSLog(@"runloop 任務(wù)2");
}
- (void)runloopTask3
{
NSLog(@"runloop 任務(wù)3");
}
- (void)runloopTask4
{
NSLog(@"runloop 任務(wù)4");
}
- (void)runloopTask5
{
NSLog(@"runloop 任務(wù)5");
}
// 輸出
2020-03-12 14:23:27.088636+0800 PerformSelectorIndepth[62976:972980] runloop 任務(wù)1
2020-03-12 14:23:27.088760+0800 PerformSelectorIndepth[62976:972980] runloop 任務(wù)2
2020-03-12 14:23:27.088868+0800 PerformSelectorIndepth[62976:972980] runloop 任務(wù)3
2020-03-12 14:23:27.088964+0800 PerformSelectorIndepth[62976:972980] runloop 任務(wù)4
2020-03-12 14:23:27.089048+0800 PerformSelectorIndepth[62976:972980] runloop 任務(wù)5
可以看到輸出結(jié)果就是按照我們傳入的 order 參數(shù)作為任務(wù)執(zhí)行的順序。
GUNStep 中這個底層的底層實現(xiàn)如下:
- (void) performSelector: (SEL)aSelector
target: (id)target
argument: (id)argument
order: (NSUInteger)order
modes: (NSArray*)modes
{
unsigned count = [modes count];
if (count > 0)
{
NSString *array[count];
GSRunLoopPerformer *item;
item = [[GSRunLoopPerformer alloc] initWithSelector: aSelector
target: target
argument: argument
order: order];
if ([modes isProxy])
{
unsigned i;
for (i = 0; i < count; i++)
{
array[i] = [modes objectAtIndex: i];
}
}
else
{
[modes getObjects: array];
}
while (count-- > 0)
{
NSString *mode = array[count];
unsigned end;
unsigned i;
GSRunLoopCtxt *context;
GSIArray performers;
context = NSMapGet(_contextMap, mode);
if (context == nil)
{
context = [[GSRunLoopCtxt alloc] initWithMode: mode
extra: _extra];
NSMapInsert(_contextMap, context->mode, context);
RELEASE(context);
}
performers = context->performers;
end = GSIArrayCount(performers);
for (i = 0; i < end; i++)
{
GSRunLoopPerformer *p;
p = GSIArrayItemAtIndex(performers, i).obj;
if (p->order > order)
{
GSIArrayInsertItem(performers, (GSIArrayItem)((id)item), i);
break;
}
}
if (i == end)
{
GSIArrayInsertItem(performers, (GSIArrayItem)((id)item), i);
}
i = GSIArrayCount(performers);
if (i % 1000 == 0 && i > context->maxPerformers)
{
context->maxPerformers = i;
NSLog(@"WARNING ... there are %u performers scheduled"
@" in mode %@ of %@\n(Latest: [%@ %@])",
i, mode, self, NSStringFromClass([target class]),
NSStringFromSelector(aSelector));
}
}
RELEASE(item);
}
}
@interface GSRunLoopPerformer: NSObject
{
@public
SEL selector;
id target;
id argument;
unsigned order;
}
我們已經(jīng)知道了 performSelector:WithObject:afterDelay: 方法底層實現(xiàn)使用一個包裹 timer 對象的數(shù)據(jù)結(jié)構(gòu)的方式,而這里是使用了一個包裹了 selector、target、argument 以及優(yōu)先級 order 的數(shù)據(jù)結(jié)構(gòu)的方式來實現(xiàn)。同時在 context 上下文的成員變量 performers 中存儲了要執(zhí)行的任務(wù)隊列,所以這里實際上就是一個簡單的插入排序的過程。
2.2.2 cancelPerformSelector:target:argument: 和 cancelPerformSelectorsWithTarget:
cancelPerformSelector:target:argument: 和 cancelPerformSelectorsWithTarget: 使用起來比較簡單,一個需要傳入 selector、target 和 argument,另一個只需要傳入 target。它們的作用分別是根據(jù)給定的三個參數(shù)或 target 去 runloop 底層的 performers 任務(wù)隊列中查找任務(wù),找到了就從隊列中移除掉。
而底層具體實現(xiàn)具體如下:
/**
* Cancels any perform operations set up for the specified target
* in the receiver.
*/
- (void) cancelPerformSelectorsWithTarget: (id) target
{
NSMapEnumerator enumerator;
GSRunLoopCtxt *context;
void *mode;
enumerator = NSEnumerateMapTable(_contextMap);
while (NSNextMapEnumeratorPair(&enumerator, &mode, (void**)&context))
{
if (context != nil)
{
GSIArray performers = context->performers;
unsigned count = GSIArrayCount(performers);
while (count--)
{
GSRunLoopPerformer *p;
p = GSIArrayItemAtIndex(performers, count).obj;
if (p->target == target)
{
GSIArrayRemoveItemAtIndex(performers, count);
}
}
}
}
NSEndMapTableEnumeration(&enumerator);
}
/**
* Cancels any perform operations set up for the specified target
* in the receiver, but only if the value of aSelector and argument
* with which the performs were set up match those supplied.<br />
* Matching of the argument may be either by pointer equality or by
* use of the [NSObject-isEqual:] method.
*/
- (void) cancelPerformSelector: (SEL)aSelector
target: (id) target
argument: (id) argument
{
NSMapEnumerator enumerator;
GSRunLoopCtxt *context;
void *mode;
enumerator = NSEnumerateMapTable(_contextMap);
while (NSNextMapEnumeratorPair(&enumerator, &mode, (void**)&context))
{
if (context != nil)
{
GSIArray performers = context->performers;
unsigned count = GSIArrayCount(performers);
while (count--)
{
GSRunLoopPerformer *p;
p = GSIArrayItemAtIndex(performers, count).obj;
if (p->target == target && sel_isEqual(p->selector, aSelector)
&& (p->argument == argument || [p->argument isEqual: argument]))
{
GSIArrayRemoveItemAtIndex(performers, count);
}
}
}
}
NSEndMapTableEnumeration(&enumerator);
}
2.2.3 小結(jié)
-
performSelector:target:argument:order:modes:- 在該方法所在線程的 runloop 處于給定的任一 mode 時,且處于下一次 runloop 消息循環(huán)的開頭的時候觸發(fā)給定的任務(wù)。底層原理是循環(huán)把一個類似于 timer 的對象加入到 runloop 的上下文的任務(wù)隊列中,等待喚醒
-
cancelPerformSelector:target:argument:- 取消 target 對象通過
performSelector:target:argument:order:modes:方法方法注冊的指定的任務(wù)
- 取消 target 對象通過
-
cancelPerformSelectorsWithTarget:- 取消 target 對象通過
performSelector:target:argument:order:modes:方法方法注冊的所有任務(wù)
- 取消 target 對象通過
這里同樣的也需要注意,如果是在子線程中執(zhí)行這些方法,則需要開啟子線程對應(yīng)的 runloop 才能保證執(zhí)行成功。
三、Thread 相關(guān)的 performSelector
如上圖所示,在 NSThread 中定義了 NSObject 的分類 NSThreadPerformAdditions,其中定義了 5 個 performSelector 的方法。
3.1 performSelector:onThread:withObject:waitUntilDone: 和 performSelector:onThread:withObject:waitUntilDone:modes:
根據(jù)官方文檔的解釋,第一個方法相當(dāng)于調(diào)用了第二個方法,然后 mode 傳入的是 kCFRunLoopCommonModes。我們這里只研究第一個方法。
這個方法需要相比于 performSeletor:withObject: 多了兩個參數(shù),分別是要哪個線程執(zhí)行任務(wù)以及是否阻塞當(dāng)前線程。但是使用這個方法一定要小心,如下圖所示是一個常見的錯誤用法:
這里報的錯是 target thread exited while waiting for the perform,就是說已經(jīng)退出的線程無法執(zhí)行定時任務(wù)。
熟悉 iOS 多線程的同學(xué)都知道 NSThread 實例化之后的線程對象在 start 之后就會被系統(tǒng)回收,而之后調(diào)用的 performSelector:onThread:withObject:waitUntilDone: 方法又在一個已經(jīng)回收的線程上執(zhí)行任務(wù),顯然就會崩潰。這里的解決方案就是給這個子線程對應(yīng)的 runloop 啟動起來,讓線程具有 『有事來就干活,沒事干就睡覺』 的功能,具體代碼如下:
對于 waitUntilDone 參數(shù),如果我們設(shè)置為 YES:
如果設(shè)置為 NO:
所以這里的 waitUntilDone 可以簡單的理解為控制同步或異步執(zhí)行。
在探索 GNUStep 對應(yīng)實現(xiàn)之前,我們先熟悉一下 GSRunLoopThreadInfo
/* Used to handle events performed in one thread from another.
*/
@interface GSRunLoopThreadInfo : NSObject
{
@public
NSRunLoop *loop;
NSLock *lock;
NSMutableArray *performers;
#ifdef _WIN32
HANDLE event;
#else
int inputFd;
int outputFd;
#endif
}
GSRunLoopThreadInfo 是每個線程特有的一個屬性,存儲了線程和 runloop 之間的一些信息,可以通過下面的方式獲取:
GSRunLoopThreadInfo *
GSRunLoopInfoForThread(NSThread *aThread)
{
GSRunLoopThreadInfo *info;
if (aThread == nil)
{
aThread = GSCurrentThread();
}
if (aThread->_runLoopInfo == nil)
{
[gnustep_global_lock lock];
if (aThread->_runLoopInfo == nil)
{
aThread->_runLoopInfo = [GSRunLoopThreadInfo new];
}
[gnustep_global_lock unlock];
}
info = aThread->_runLoopInfo;
return info;
}
然后是另一個 GSPerformHolder:
/**
* This class performs a dual function ...
* <p>
* As a class, it is responsible for handling incoming events from
* the main runloop on a special inputFd. This consumes any bytes
* written to wake the main runloop.<br />
* During initialisation, the default runloop is set up to watch
* for data arriving on inputFd.
* </p>
* <p>
* As instances, each instance retains perform receiver and argument
* values as long as they are needed, and handles locking to support
* methods which want to block until an action has been performed.
* </p>
* <p>
* The initialize method of this class is called before any new threads
* run.
* </p>
*/
@interface GSPerformHolder : NSObject
{
id receiver;
id argument;
SEL selector;
NSConditionLock *lock; // Not retained.
NSArray *modes;
BOOL invalidated;
@public
NSException *exception;
}
+ (GSPerformHolder*) newForReceiver: (id)r
argument: (id)a
selector: (SEL)s
modes: (NSArray*)m
lock: (NSConditionLock*)l;
- (void) fire;
- (void) invalidate;
- (BOOL) isInvalidated;
- (NSArray*) modes;
@end
GSPerformHolder 封裝了任務(wù)的細節(jié)(receiver, argument, selector)以及運行模式(mode)和一把條件鎖( NSConditionLock )。
接著我們目光聚焦到源碼 performSelector:onThread:withObject:waitUntilDone:modes: 具體實現(xiàn)上:
- (void) performSelector: (SEL)aSelector
onThread: (NSThread*)aThread
withObject: (id)anObject
waitUntilDone: (BOOL)aFlag
modes: (NSArray*)anArray
{
GSRunLoopThreadInfo *info;
NSThread *t;
if ([anArray count] == 0)
{
return;
}
t = GSCurrentThread();
if (aThread == nil)
{
aThread = t;
}
info = GSRunLoopInfoForThread(aThread);
if (t == aThread)
{
/* Perform in current thread.
*/
if (aFlag == YES || info->loop == nil)
{
/* Wait until done or no run loop.
*/
[self performSelector: aSelector withObject: anObject];
}
else
{
/* Don't wait ... schedule operation in run loop.
*/
[info->loop performSelector: aSelector
target: self
argument: anObject
order: 0
modes: anArray];
}
}
else
{
GSPerformHolder *h;
NSConditionLock *l = nil;
if ([aThread isFinished] == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"perform [%@-%@] attempted on finished thread (%@)",
NSStringFromClass([self class]),
NSStringFromSelector(aSelector),
aThread];
}
if (aFlag == YES)
{
l = [[NSConditionLock alloc] init];
}
h = [GSPerformHolder newForReceiver: self
argument: anObject
selector: aSelector
modes: anArray
lock: l];
[info addPerformer: h];
if (l != nil)
{
[l lockWhenCondition: 1];
[l unlock];
RELEASE(l);
if ([h isInvalidated] == NO)
{
/* If we have an exception passed back from the remote thread,
* re-raise it.
*/
if (nil != h->exception)
{
NSException *e = AUTORELEASE(RETAIN(h->exception));
RELEASE(h);
[e raise];
}
}
}
RELEASE(h);
}
}
- 聲明一個
GSRunLoopThreadInfo對象和一條NSThread線程 - 判斷運行模式數(shù)組參數(shù)是否為空
- 獲取當(dāng)前線程,將結(jié)果賦值于第一步聲明的局部線程變量
- 判斷如果傳入的要執(zhí)行任務(wù)的線程 aThread 如果為空,那么就把當(dāng)前線程賦值于到 aThread 上
- 確保 aThread 不為空之后獲取該線程對應(yīng)的
GSRunLoopThreadInfo對象并賦值于第一步聲明的局部 info 變量 - 確保 info 有值后,判斷是否是在當(dāng)前線程上執(zhí)行任務(wù)
- 如果是在當(dāng)前線程上執(zhí)行任務(wù),接著判斷是否要阻塞當(dāng)前線程,或當(dāng)前線程的 runloop 為空。
- 如果是的話,則直接調(diào)用
performSelector:withObject來執(zhí)行任務(wù) - 如果不是的話,則通過線程對應(yīng)的 runloop 對象調(diào)用
performSelector:target:argument:order:modes:來執(zhí)行任務(wù)
- 如果是的話,則直接調(diào)用
- 如果不是在當(dāng)前線程上執(zhí)行任務(wù),聲明一個
GSPerformHolder局部變量,聲明一把空的條件鎖NSConditionLock- 判斷要執(zhí)行任務(wù)的線程是否已經(jīng)被回收,如果已被回收,則拋出異常
- 如果未被回收
- 判斷是否要阻塞當(dāng)前線程,如果傳入的參數(shù)需要阻塞,則初始化條件鎖
- 根據(jù)傳入的參數(shù)及條件鎖初始化
GSPerformHolder實例 - 然后在 info 中加入
GSPerformHolder實例 - 然后判斷條件鎖如果不為空,賦予條件鎖何時加鎖的條件,然后解鎖條件鎖,然后釋放條件鎖
- 判斷
GSPerformHolder局部變量是否已經(jīng)被釋放,如果沒有被釋放,拋出異常
3.2 performSelectorOnMainThread:withObject:waitUntilDone: 和 performSelectorOnMainThread:withObject:waitUntilDone:modes:
顧名思義,這兩個方法其實就是在主線程上執(zhí)行任務(wù),根據(jù)傳入的參數(shù)決定是否阻塞主線程,以及在哪些運行模式下執(zhí)行任務(wù)。使用方法如下:
- (void)jh_performSelectorOnMainThreadwithObjectwaitUntilDone
{
[self performSelectorOnMainThread:@selector(threadTask:) withObject:@{@"param": @"leejunhui"} waitUntilDone:NO];
// [self performSelectorOnMainThread:@selector(threadTask:) withObject:@{@"param": @"leejunhui"} waitUntilDone:NO modes:@[NSRunLoopCommonModes]];
}
- (void)threadTask:(NSDictionary *)param
{
NSLog(@"%s", __func__);
NSLog(@"%@", [NSThread currentThread]);
}
// 輸出
2020-03-12 16:14:31.783962+0800 PerformSelectorIndepth[63614:1057033] -[ViewController threadTask:]
2020-03-12 16:14:31.784126+0800 PerformSelectorIndepth[63614:1057033] <NSThread: 0x600002e76dc0>{number = 1, name = main}
因為是在主線程上執(zhí)行,所以并不需要手動開啟 runloop。我們來看下這兩個方法在 GNUStep 中底層實現(xiàn):
- (void) performSelectorOnMainThread: (SEL)aSelector
withObject: (id)anObject
waitUntilDone: (BOOL)aFlag
modes: (NSArray*)anArray
{
/* It's possible that this method could be called before the NSThread
* class is initialised, so we check and make sure it's initiailised
* if necessary.
*/
if (defaultThread == nil)
{
[NSThread currentThread];
}
[self performSelector: aSelector
onThread: defaultThread
withObject: anObject
waitUntilDone: aFlag
modes: anArray];
}
- (void) performSelectorOnMainThread: (SEL)aSelector
withObject: (id)anObject
waitUntilDone: (BOOL)aFlag
{
[self performSelectorOnMainThread: aSelector
withObject: anObject
waitUntilDone: aFlag
modes: commonModes()];
}
不難看出,這里其實就是調(diào)用的 performSelector:onThread:withObject:waitUntilDone:modes 方法,但是有一個細節(jié)需要注意,就是有可能在 NSThread 類被初始化之前,就調(diào)用了 performSelectorOnMainThread 方法,所以需要手動調(diào)用一下 [NSThread currentThread]。
3.3 performSelectorInBackground:withObject:
最后要探索的是 performSelectorInBackground:withObject: 方法,這個方法用法如下:
- (void)jh_performSelectorOnBackground
{
[self performSelectorInBackground:@selector(threadTask:) withObject:@{@"param": @"leejunhui"}];
}
- (void)threadTask:(NSDictionary *)param
{
NSLog(@"%s", __func__);
NSLog(@"%@", [NSThread currentThread]);
}
// 輸出
2020-03-12 16:19:36.751675+0800 PerformSelectorIndepth[63660:1061569] -[ViewController threadTask:]
2020-03-12 16:19:36.751990+0800 PerformSelectorIndepth[63660:1061569] <NSThread: 0x6000027a0ac0>{number = 6, name = (null)}
根據(jù)輸出我們可知,這里顯然是開了一條子線程來執(zhí)行任務(wù),我們看一下 GNUStep 的底層實現(xiàn):
- (void) performSelectorInBackground: (SEL)aSelector
withObject: (id)anObject
{
[NSThread detachNewThreadSelector: aSelector
toTarget: self
withObject: anObject];
}
可以看到在底層其實是調(diào)用的 NSThread 的類方法來執(zhí)行傳入的任務(wù)。關(guān)于 NSThread 細節(jié)我們后面會進行探索。
3.4 小結(jié)
-
performSelector:onThread:withObject:waitUntilDone:和performSelector:onThread:withObject:waitUntilDone:modes:- 在該方法所在線程的 runloop 處于給定的任一 mode 時,判斷是否阻塞當(dāng)前線程,并且處于下一次 runloop 消息循環(huán)的開頭的時候觸發(fā)給定的任務(wù)。
-
performSelectorOnMainThread:withObject:waitUntilDone:和performSelectorOnMainThread:withObject:waitUntilDone:modes:- 當(dāng)主線程的 runloop 處于給定的任一 mode 時,判斷是否阻塞主線程,并且處于下一次 runloop 消息循環(huán)的開頭的時候觸發(fā)給定的任務(wù)。
-
performSelectorInBackground:withObject:- 在子線程上執(zhí)行給定的任務(wù)。底層是通過
NSThread的detachNewThread實現(xiàn)。
- 在子線程上執(zhí)行給定的任務(wù)。底層是通過