iOS 查漏补缺 - PerformSelector
performSelector
系列的函数我们都不陌生,但是对于它不同的变种以及底层原理在很多时候还是容易分不清楚,所以笔者希望通过runtime
源码以及GUNStep
源码来一个个抽丝剥茧,把不同变种的performSelector
理顺,并搞清楚每个方法的底层实现,如有错误,欢迎指正。本文的代码已放在 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
底层实现为:
- (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:
方法相比于上一个方法多了一个参数,使用起来如下:
- (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:
方法底层实现如下:
- (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
这个方法相比上一个方法又多了一个参数:
- (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:
方法底层实现如下:
- (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 小结
方法 | 底层实现 |
---|---|
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) |
这三个方法应该是使用频率很高的 performSelector
系列方法了,我们只需要记住这三个方法在底层都是执行的消息发送即可。
二、Runloop 相关的 PerformSelector
如上图所示,在 NSRunLoop
头文件中,定义了两个的分类,分别是
NSDelayedPerforming
对应于NSObject
NSOrderedPerform
对应于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.
这个方法会在当前线程所对应的 runloop 中设置一个定时器来执行传入的 SEL。定时器需要在 NSDefaultRunLoopMode 模式下才会被触发。当定时器启动后,线程会尝试从 runloop 中取出 SEL 然后执行。
如果 runloop 已经启动并且处于 NSDefaultRunLoopMode 的话,SEL 执行成功。否则,直到 runloop 处于 NSDefaultRunLoopMode 前,timer 都会一直等待
通过断点调试如下图所示,runloop 底层最终是通过 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ ()
来触发任务的执行。
因为 NSRunLoop
并没有开源,所以我们只能通过 GNUStep
来窥探底层实现细节,如下所示:
- (void) performSelector: (SEL)aSelectorwithObject: (id)argumentafterDelay: (NSTimeInterval)seconds
{NSRunLoop *loop = [NSRunLoop currentRunLoop];GSTimedPerformer *item;item = [[GSTimedPerformer alloc] initWithSelector: aSelectortarget: selfargument: argumentdelay: 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
{@publicSEL selector;id target;id argument;NSTimer *timer;
}- (void) fire;
- (id) initWithSelector: (SEL)aSelectortarget: (id)targetargument: (id)argumentdelay: (NSTimeInterval)delay;
- (void) invalidate;
@end
我们可以看到,在 performSelector:WithObject:afterDelay:
底层
- 获取当前线程的
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
参数,根据官方文档的定义,只有当 runloop
处于 modes
中的任意一个 mode
时,才会执行任务,如果 modes
为空,那么将不会执行任务。
- (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;
}
这里我们如果把
modes
参数改为UITrackingRunLoopMode
,那么就只有在scrollView
发生滚动的时候才会触发 timer
我们再看一下 GNUStep
对应的实现:
- (void) performSelector: (SEL)aSelectorwithObject: (id)argumentafterDelay: (NSTimeInterval)secondsinModes: (NSArray*)modes
{unsigned count = [modes count];if (count > 0){NSRunLoop *loop = [NSRunLoop currentRunLoop];NSString *marray[count];GSTimedPerformer *item;unsigned i;item = [[GSTimedPerformer alloc] initWithSelector: aSelectortarget: selfargument: argumentdelay: 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
可以看到,相比于上一个方法的底层实现不同的是,这里会循环添加不同 mode
的 timer
对象到 runloop
中。
2.1.3 cancelPreviousPerformRequestsWithTarget:
和 cancelPreviousPerformRequestsWithTarget:selector:object:
cancelPreviousPerformRequestsWithTarget:
方法和 cancelPreviousPerformRequestsWithTarget:selector:object:
方法是两个类方法,它们的作用是取消执行之前通过 performSelector:WithObject:afterDelay:
方法注册的任务。使用起来如下所示:
- (void)jh_performSelectorwithObjectafterDelayInModes
{// 只有当 scrollView 发生滚动时,才会触发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]
这里有一个区别,就是 cancelPreviousPerformRequestsWithTarget:
类方法会取消掉 target
上所有的通过 performSelector:WithObject:afterDelay:
实例方法注册的定时任务,而 cancelPreviousPerformRequestsWithTarget:selector:object:
只会通过传入的 SEL
取消匹配到的定时任务
在 GNUStep
中 cancelPreviousPerformRequestsWithTarget:
方法底层实现如下:
/** 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);}
}
这里的逻辑其实很清晰:
- 取出当前 runloop 对象的成员变量
_timedPerformers
- 判断定时任务数组是否为空,不为空才会继续往下走
- 初始化一个局部的空的任务数组,然后通过
getObjects
从成员变量中取出任务 - 通过 while 循环遍历所有的任务,如果匹配到了对应的
target
,则调用任务的 invalidate 方法,在这个方法内部会把定时器停掉然后销毁。接着还需要把成员变量_timedPerformers
中对应的任务移除掉
另一个取消任务的方法底层实现如下:
/** 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)targetselector: (SEL)aSelectorobject: (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);}
}
这里的实现不一样的地方就是除了判断 target
是否匹配外,还会判断 SEL
是否匹配,以及参数是否匹配。
2.1.4 小结
performSelector:WithObject:afterDelay:
- 在该方法所在线程的 runloop 处于 default mode 时,根据给定的时间触发给定的任务。底层原理是把一个 timer 对象以 default mode 加入到 runloop 对象中,等待唤醒。
performSelector:WithObject:afterDelay:inModes:
- 在该方法所在线程的 runloop 处于给定的任一 mode 时,根据给定的时间触发给定的任务。底层原理是循环把一个 timer 对象以给定的 mode 加入到 runloop 对象中,等待唤醒。
cancelPreviousPerformRequestsWithTarget:
- 取消
target
对象通过performSelector:WithObject:afterDelay:
方法或performSelector:WithObject:afterDelay:inModes:
方法注册的所有定时任务
- 取消
cancelPreviousPerformRequestsWithTarget:selector:object:
- 取消
target
对象通过performSelector:WithObject:afterDelay:
方法或performSelector:WithObject:afterDelay:inModes:
方法注册的指定的定时任务
- 取消
这四个方法是作为 NSObject
的 NSDelayedPerforming
分类存在于 NSRunLoop
源代码中,所以我们在使用的时候要注意一个细节,那就是执行这些方法的线程是否是主线程,如果是主线程,那么执行起来是没有问题的,但是,如果是在子线程中执行这些方法,则需要开启子线程对应的 runloop 才能保证执行成功。
- (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
的异步执行函数在全局并发队列上执行任务,并没有任何打印输出,我们加入 runloop 的启动代码后结果将完全不一样:
对于 performSelector:WithObject:afterDelay:inModes
方法,如果遇到这样的情况,也是一样的解决方案。
2.2 NSRunLoop 的分类 NSOrderedPerform
2.2.1 performSelector:target:argument:order:modes:
performSelector:target:argument:order:modes:
方法的调用者是 NSRunLoop
实例,然后需要传入要执行的 SEL
,以及 SEL
对应的 target
,和 SEL
要接收的参数 argument
,最后是此次任务的优先级 order
,以及一个 运行模式集合 modes
,目的是当 runloop
的 currentMode
处于这个运行模式集合中的其中任意一个 mode 时,就会按照优先级 order
来触发 SEL
的执行。具体使用如下:
- (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 任务1");
}- (void)runloopTask2
{NSLog(@"runloop 任务2");
}- (void)runloopTask3
{NSLog(@"runloop 任务3");
}- (void)runloopTask4
{NSLog(@"runloop 任务4");
}- (void)runloopTask5
{NSLog(@"runloop 任务5");
}// 输出
2020-03-12 14:23:27.088636+0800 PerformSelectorIndepth[62976:972980] runloop 任务1
2020-03-12 14:23:27.088760+0800 PerformSelectorIndepth[62976:972980] runloop 任务2
2020-03-12 14:23:27.088868+0800 PerformSelectorIndepth[62976:972980] runloop 任务3
2020-03-12 14:23:27.088964+0800 PerformSelectorIndepth[62976:972980] runloop 任务4
2020-03-12 14:23:27.089048+0800 PerformSelectorIndepth[62976:972980] runloop 任务5
可以看到输出结果就是按照我们传入的 order
参数作为任务执行的顺序。
GUNStep
中这个底层的底层实现如下:
- (void) performSelector: (SEL)aSelectortarget: (id)targetargument: (id)argumentorder: (NSUInteger)ordermodes: (NSArray*)modes
{unsigned count = [modes count];if (count > 0){NSString *array[count];GSRunLoopPerformer *item;item = [[GSRunLoopPerformer alloc] initWithSelector: aSelectortarget: targetargument: argumentorder: 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: modeextra: _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
{@publicSEL selector;id target;id argument;unsigned order;
}
我们已经知道了 performSelector:WithObject:afterDelay:
方法底层实现使用一个包裹 timer 对象的数据结构的方式,而这里是使用了一个包裹了 selector
、target
、argument
以及优先级 order
的数据结构的方式来实现。同时在 context 上下文的成员变量 performers
中存储了要执行的任务队列,所以这里实际上就是一个简单的插入排序的过程。
2.2.2 cancelPerformSelector:target:argument:
和 cancelPerformSelectorsWithTarget:
cancelPerformSelector:target:argument:
和 cancelPerformSelectorsWithTarget:
使用起来比较简单,一个需要传入 selector
、target
和 argument
,另一个只需要传入 target
。它们的作用分别是根据给定的三个参数或 target
去 runloop 底层的 performers
任务队列中查找任务,找到了就从队列中移除掉。
而底层具体实现具体如下:
/*** 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)aSelectortarget: (id) targetargument: (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 小结
performSelector:target:argument:order:modes:
- 在该方法所在线程的 runloop 处于给定的任一 mode 时,且处于下一次 runloop 消息循环的开头的时候触发给定的任务。底层原理是循环把一个类似于 timer 的对象加入到 runloop 的上下文的任务队列中,等待唤醒
cancelPerformSelector:target:argument:
- 取消 target 对象通过
performSelector:target:argument:order:modes:
方法方法注册的指定的任务
- 取消 target 对象通过
cancelPerformSelectorsWithTarget:
- 取消 target 对象通过
performSelector:target:argument:order:modes:
方法方法注册的所有任务
- 取消 target 对象通过
这里同样的也需要注意,如果是在子线程中执行这些方法,则需要开启子线程对应的 runloop 才能保证执行成功。
三、Thread 相关的 performSelector
如上图所示,在 NSThread
中定义了 NSObject
的分类 NSThreadPerformAdditions
,其中定义了 5 个 performSelector
的方法。
3.1 performSelector:onThread:withObject:waitUntilDone:
和 performSelector:onThread:withObject:waitUntilDone:modes:
根据官方文档的解释,第一个方法相当于调用了第二个方法,然后 mode 传入的是 kCFRunLoopCommonModes
。我们这里只研究第一个方法。
这个方法需要相比于 performSeletor:withObject:
多了两个参数,分别是要哪个线程执行任务以及是否阻塞当前线程。但是使用这个方法一定要小心,如下图所示是一个常见的错误用法:
这里报的错是 target thread exited while waiting for the perform
,就是说已经退出的线程无法执行定时任务。
熟悉 iOS
多线程的同学都知道 NSThread
实例化之后的线程对象在 start
之后就会被系统回收,而之后调用的 performSelector:onThread:withObject:waitUntilDone:
方法又在一个已经回收的线程上执行任务,显然就会崩溃。这里的解决方案就是给这个子线程对应的 runloop 启动起来,让线程具有 『有事来就干活,没事干就睡觉』 的功能,具体代码如下:
对于 waitUntilDone
参数,如果我们设置为 YES
:
如果设置为 NO
:
所以这里的 waitUntilDone
可以简单的理解为控制同步或异步执行。
在探索 GNUStep
对应实现之前,我们先熟悉一下 GSRunLoopThreadInfo
/* Used to handle events performed in one thread from another.*/
@interface GSRunLoopThreadInfo : NSObject
{@publicNSRunLoop *loop;NSLock *lock;NSMutableArray *performers;
#ifdef _WIN32HANDLE event;
#elseint 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;
@publicNSException *exception;
}
+ (GSPerformHolder*) newForReceiver: (id)rargument: (id)aselector: (SEL)smodes: (NSArray*)mlock: (NSConditionLock*)l;
- (void) fire;
- (void) invalidate;
- (BOOL) isInvalidated;
- (NSArray*) modes;
@end
GSPerformHolder
封装了任务的细节(receiver
, argument
, selector
)以及运行模式(mode
)和一把条件锁( NSConditionLock
)。
接着我们目光聚焦到源码 performSelector:onThread:withObject:waitUntilDone:modes:
具体实现上:
- (void) performSelector: (SEL)aSelectoronThread: (NSThread*)aThreadwithObject: (id)anObjectwaitUntilDone: (BOOL)aFlagmodes: (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: aSelectortarget: selfargument: anObjectorder: 0modes: anArray];}}else{GSPerformHolder *h;NSConditionLock *l = nil;if ([aThread isFinished] == YES){[NSException raise: NSInternalInconsistencyExceptionformat: @"perform [%@-%@] attempted on finished thread (%@)",NSStringFromClass([self class]),NSStringFromSelector(aSelector),aThread];}if (aFlag == YES){l = [[NSConditionLock alloc] init];}h = [GSPerformHolder newForReceiver: selfargument: anObjectselector: aSelectormodes: anArraylock: 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
线程 - 判断运行模式数组参数是否为空
- 获取当前线程,将结果赋值于第一步声明的局部线程变量
- 判断如果传入的要执行任务的线程 aThread 如果为空,那么就把当前线程赋值于到 aThread 上
- 确保 aThread 不为空之后获取该线程对应的
GSRunLoopThreadInfo
对象并赋值于第一步声明的局部 info 变量 - 确保 info 有值后,判断是否是在当前线程上执行任务
- 如果是在当前线程上执行任务,接着判断是否要阻塞当前线程,或当前线程的 runloop 为空。
- 如果是的话,则直接调用
performSelector:withObject
来执行任务 - 如果不是的话,则通过线程对应的 runloop 对象调用
performSelector:target:argument:order:modes:
来执行任务
- 如果是的话,则直接调用
- 如果不是在当前线程上执行任务,声明一个
GSPerformHolder
局部变量,声明一把空的条件锁NSConditionLock
- 判断要执行任务的线程是否已经被回收,如果已被回收,则抛出异常
- 如果未被回收
- 判断是否要阻塞当前线程,如果传入的参数需要阻塞,则初始化条件锁
- 根据传入的参数及条件锁初始化
GSPerformHolder
实例 - 然后在 info 中加入
GSPerformHolder
实例 - 然后判断条件锁如果不为空,赋予条件锁何时加锁的条件,然后解锁条件锁,然后释放条件锁
- 判断
GSPerformHolder
局部变量是否已经被释放,如果没有被释放,抛出异常
3.2 performSelectorOnMainThread:withObject:waitUntilDone:
和 performSelectorOnMainThread:withObject:waitUntilDone:modes:
顾名思义,这两个方法其实就是在主线程上执行任务,根据传入的参数决定是否阻塞主线程,以及在哪些运行模式下执行任务。使用方法如下:
- (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}
因为是在主线程上执行,所以并不需要手动开启 runloop。我们来看下这两个方法在 GNUStep
中底层实现:
- (void) performSelectorOnMainThread: (SEL)aSelectorwithObject: (id)anObjectwaitUntilDone: (BOOL)aFlagmodes: (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: aSelectoronThread: defaultThreadwithObject: anObjectwaitUntilDone: aFlagmodes: anArray];
}- (void) performSelectorOnMainThread: (SEL)aSelectorwithObject: (id)anObjectwaitUntilDone: (BOOL)aFlag
{[self performSelectorOnMainThread: aSelectorwithObject: anObjectwaitUntilDone: aFlagmodes: commonModes()];
}
不难看出,这里其实就是调用的 performSelector:onThread:withObject:waitUntilDone:modes
方法,但是有一个细节需要注意,就是有可能在 NSThread
类被初始化之前,就调用了 performSelectorOnMainThread
方法,所以需要手动调用一下 [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)}
根据输出我们可知,这里显然是开了一条子线程来执行任务,我们看一下 GNUStep
的底层实现:
- (void) performSelectorInBackground: (SEL)aSelectorwithObject: (id)anObject
{[NSThread detachNewThreadSelector: aSelectortoTarget: selfwithObject: anObject];
}
可以看到在底层其实是调用的 NSThread
的类方法来执行传入的任务。关于 NSThread
细节我们后面会进行探索。
3.4 小结
performSelector:onThread:withObject:waitUntilDone:
和performSelector:onThread:withObject:waitUntilDone:modes:
- 在该方法所在线程的 runloop 处于给定的任一 mode 时,判断是否阻塞当前线程,并且处于下一次 runloop 消息循环的开头的时候触发给定的任务。
performSelectorOnMainThread:withObject:waitUntilDone:
和performSelectorOnMainThread:withObject:waitUntilDone:modes:
- 当主线程的 runloop 处于给定的任一 mode 时,判断是否阻塞主线程,并且处于下一次 runloop 消息循环的开头的时候触发给定的任务。
performSelectorInBackground:withObject:
- 在子线程上执行给定的任务。底层是通过
NSThread
的detachNewThread
实现。
- 在子线程上执行给定的任务。底层是通过
四、总结
iOS 查漏补缺 - PerformSelector相关推荐
- iOS 查漏补缺 - RunLoop
RunLoop 在 0202 年的今天其实已经不是个新鲜的话题了,关于这方面的文章网上有很多大神总结得非常精辟. 作为 iOS 查漏补缺系列,这篇文章是笔者探索 RunLoop 底层的一些知识点总结, ...
- iOS 查漏补缺 - LLVM Clang
LLVM 是一个自由软件项目,它是一种编译器基础设施,以 C++ 写成,包含一系列模块化的编译器组件和工具链,用来开发编译器前端和后端.它是为了任意一种编程语言而写成的程序,利用虚拟技术创造出编译时期 ...
- 104道 CSS 面试题,助你查漏补缺(下)
作者:CavsZhouyou https://github.com/CavsZhouyou/Front-End-Interview-Notebook/blob/master/Css/Css.md 本部 ...
- 【面试题】104道 CSS 面试题,助你查漏补缺(下)
作者:CavsZhouyou https://github.com/CavsZhouyou/Front-End-Interview-Notebook/blob/master/Css/Css.md 本部 ...
- css 图片自适应_104道 CSS 面试题,助你查漏补缺(下)
(给前端大全加星标,提升前端技能) 作者:CavsZhouyou https://github.com/CavsZhouyou/Front-End-Interview-Notebook/blob/ma ...
- 算法岗面经整理!查漏补缺
↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:阿毛冲冲冲,来源:NewBeeNLP(牛客网) 写在前面 三月面试 ...
- 前端面试查漏补缺--(一) 防抖和节流
前言 本系列最开始是为了自己面试准备的.后来发现整理越来越多,差不多有十二万字符,最后决定还是分享出来给大家. 为了分享整理出来,花费了自己大量的时间,起码是只自己用的三倍时间.如果喜欢的话,欢迎收藏 ...
- 计算机三级网络技术查漏补缺
计算机三级网络技术查漏补缺 DHCP(Dynamic Host Configuration Protocol) DMZ(demilitarized zone) 可信计算机评估准则 VLAN 集线器工作 ...
- 2019/5/12 查漏补缺
目录 2019/5/12 查漏补缺 数据类型分为两大类:基本类型和引用类型: java中类的继承关系 关于接口 重载和重写 静态变量 java中的关键字和保留字 数据库操作 实现数据库收回部分权限的操 ...
最新文章
- 漫画解读语音识别技术的实现原理与应用
- 自学必看篇:从零基础到精通的Python学习路线(附加教程)
- js实现webSocket客户端
- MyBatis——insert并返回主键ID解决方案
- C语言写文件到txt里有屯字,C语言10 文件.ppt
- 使用计算机绘制景物图像的两个主要步骤是,计算机11考试.doc
- VBA打开TXT类文件读写相关操作代码
- Servlet-Session
- 高效的敏捷测试第十三课 自动化测试、用例测试、接口测试、大数据测试
- 隐藏百度地图logo
- Gym 101572 K.Kayaking Trip【二分+贪心】
- C4.5(决策树预测)算法
- 【FL论文阅读】Communication-Efficient Learning of Deep Networks from Decentralized Data
- Win7系统组策略怎么打开 打开组策略的几种方法
- php反序列化--字符串逃逸
- php实现关键字搜索,php关键字搜索
- Java如何计算年龄
- 域名可以过户吗?域名过户需要多久?
- 前端Uncaught TypeError: $(...).popup is not a function问题
- 解决《空中英语教室》Super MP3光盘繁体字乱码的方法