iOS源码解析: performSelector是如何实现的

performSelector系列接口是runtime的一大特色,可以动态执行任务,延迟执行,还能指定线程执行。NSNotificationCenter等很多技术实际上就会使用到performSelector。本文从源码角度分析了其实现原理。

数据结构

关于涉及到的数据结构,这里只是简单介绍一下。

SEL

SEL用于在runtime中表示一个方法名。与其对应的方法实现则使用IMP来表示。

1
2
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
1
2
3
4
5
Method selectors are used to represent the name of a method at runtime. A method selector is a C string that has been registered (or “mapped“) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded.

You can add new selectors at runtime and retrieve existing selectors using the function sel_registerName.

When using selectors, you must use the value returned from sel_registerName or the Objective-C compiler directive @selector(). You cannot simply cast a C string to SEL.

并未找到objc_selector的底层实现,我们理解它为映射到一个方法名的字符串即可。编译器的@selector()与Runtime自身的sel_registerName都可以获取SEL类型的方法选择器。

1
2
3
4
struct objc_selector {
void *sel_id;
const char *sel_types;
};

或者简单理解为char *, 映射到方法的C字符串。

1
2
3
struct objc_selector  {
char name[64 or ...];
};

IMP

IMP即为一个函数指针,指向函数的实现体。

1
2
3
A pointer to the start of a method implementation.

id (*IMP)(id, SEL, ...)

This data type is a pointer to the start of the function that implements the method. This function uses standard C calling conventions as implemented for the current CPU architecture. The first argument is a pointer to self (that is, the memory for the particular instance of this class, or, for a class method, a pointer to the metaclass). The second argument is the method selector. The method arguments follow.

Method

Method即用于表示runtime的一个方法,其内部包含了selector和函数实现_imp,以及方法签名。

1
2
typedef struct objc_method *Method;
An opaque type that represents a method in a class definition.
1
2
3
4
5
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};

selector 与 NSObject

NSObject提供了performSelector的一系列接口。可以说单独的performSelector仅相当于NSInvocation或者objc_msgSend,动态执行传入的selector而已。所以,很多博客讲到performSelector一定与runloop有关,其实是不准确的。

The performSelector: method is equivalent to sending an aSelector message directly to the receiver. The performSelector: method allows you to send messages that aren’t determined until run-time. This means that you can pass a variable selector as the argument.

methodSignatureForSelector

该方法返回SEL对应的方法签名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* If we respond to the method directly, create and return a method
* signature. Otherwise raise an exception.
*/
- (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector
{
struct objc_method *mth;

if (0 == aSelector)
{
return nil;
}
mth = GSGetMethod(object_getClass(self), aSelector, YES, YES);
if (mth != 0)
{
const char *types = method_getTypeEncoding(mth);

if (types != 0)
{
return [NSMethodSignature signatureWithObjCTypes: types];
}
}
[NSException raise: NSInvalidArgumentException format:
@"NSProxy should not implement 'methodSignatureForSelector:'"];
return nil;
}

GSMethod即为Method,该GSGetMethod方法出自GNUStep中的runtime实现,调用了runtime的class_copyMethodList函数获取所有方法列表,然后查找到相应的Method。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
typedef Method	GSMethod;

GSMethod
GSGetMethod(Class cls, SEL sel,
BOOL searchInstanceMethods,
BOOL searchSuperClasses)
{
if (cls == 0 || sel == 0)
{
return 0;
}

if (searchSuperClasses == NO)
{
unsigned int count;
Method method = NULL;
Method *methods;

if (searchInstanceMethods == NO)
{
methods = class_copyMethodList(object_getClass(cls), &count);
}
else
{
methods = class_copyMethodList(cls, &count);
}
if (methods != NULL)
{
unsigned int index = 0;

while ((method = methods[index++]) != NULL)
{
if (sel_isEqual(sel, method_getName(method)))
{
break;
}
}
free(methods);
}
return method;
}
else
{
if (searchInstanceMethods == NO)
{
return class_getClassMethod(cls, sel);
}
else
{
return class_getInstanceMethod(cls, sel);
}
}
}

methodForSelector

根据SEL来查找到对应的函数实现IMP。

在swift/stdlib/public/runtime/SwiftObject.mm中的实现如下:

1
2
3
- (IMP)methodForSelector:(SEL)sel {
return class_getMethodImplementation(object_getClass(self), sel);
}

直接调用runtime的函数class_getMethodImplementation即可。而在libs-base/Source/NSObject.m中的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Returns a pointer to the C function implementing the method used
* to respond to messages with aSelector.
* <br />Raises NSInvalidArgumentException if given a null selector.
*/
- (IMP) methodForSelector: (SEL)aSelector
{
if (aSelector == 0)
[NSException raise: NSInvalidArgumentException
format: @"%@ null selector given", NSStringFromSelector(_cmd)];
/* The Apple runtime API would do:
* return class_getMethodImplementation(object_getClass(self), aSelector);
* but this cannot ask self for information about any method reached by
* forwarding, so the returned forwarding function would ge a generic one
* rather than one aware of hardware issues with returning structures
* and floating points. We therefore prefer the GNU API which is able to
* use forwarding callbacks to get better type information.
*/
return objc_msg_lookup(self, aSelector);
}

这里采用了不同的方式,原因在注释中写的很明确了。class_getMethodImplementation函数无法获取到消息转发过来的所有方法的信息。

performSelector

performSelector用于执行一个SEL。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Performs the specified selector. The selector must correspond to a method
* that takes no arguments.
*/
- (id) performSelector: (SEL)aSelector;
/**
* Performs the specified selector, with the object as the argument. This
* method does not perform any automatic unboxing, so the selector must
* correspond to a method that takes one object argument.
*/
- (id) performSelector: (SEL)aSelector
withObject: (id)anObject;
/**
* Performs the specified selector, with the objects as the arguments. This
* method does not perform any automatic unboxing, so the selector must
* correspond to a method that takes two object arguments.
*/
- (id) performSelector: (SEL)aSelector
withObject: (id)object1
withObject: (id)object2;
/**
* Returns YES if the object can respond to messages with the specified
* selector. The default implementation in NSObject returns YES if the
* receiver has a method corresponding to the method, but other classes may
* return YES if they can respond to a selector using one of the various
* forwarding mechanisms.
*/
- (BOOL) respondsToSelector: (SEL)aSelector;

在swift/stdlib/public/runtime/SwiftObject.mm中, performSelector的底层实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (id)performSelector:(SEL)aSelector {
return ((id(*)(id, SEL))objc_msgSend)(self, aSelector);
}

- (id)performSelector:(SEL)aSelector withObject:(id)object {
return ((id(*)(id, SEL, id))objc_msgSend)(self, aSelector, object);
}

- (id)performSelector:(SEL)aSelector withObject:(id)object1
withObject:(id)object2 {
return ((id(*)(id, SEL, id, id))objc_msgSend)(self, aSelector, object1,
object2);
}

这不就是objc_msgSend么。。。而在libs-base/Source/NSObject.m中的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* Causes the receiver to execute the method implementation corresponding
* to aSelector and returns the result.<br />
* The method must be one which takes two arguments and returns an object.
* <br />Raises NSInvalidArgumentException if given a null selector.
*/
- (id) performSelector: (SEL)aSelector
withObject: (id) object1
withObject: (id) object2
{
IMP msg;

if (aSelector == 0)
[NSException raise: NSInvalidArgumentException
format: @"%@ null selector given", NSStringFromSelector(_cmd)];

/* The Apple runtime API would do:
* msg = class_getMethodImplementation(object_getClass(self), aSelector);
* but this cannot ask self for information about any method reached by
* forwarding, so the returned forwarding function would ge a generic one
* rather than one aware of hardware issues with returning structures
* and floating points. We therefore prefer the GNU API which is able to
* use forwarding callbacks to get better type information.
*/
msg = objc_msg_lookup(self, aSelector);
if (!msg)
{
[NSException raise: NSGenericException
format: @"invalid selector '%s' passed to %s",
sel_getName(aSelector), sel_getName(_cmd)];
return nil;
}

return (*msg)(self, aSelector, object1, object2);
}

这里,就通过runtime的objc_msg_lookup函数查找到SEL对应的IMP实现,然后进行调用。原理与上边的一样。

所以,单纯的performSelector仅仅是提供了动态执行方法的能力,与NSInvocation一样。

NSProxy中关于performSelector的接口,基本与NSObject一致。

集合对象的makeObjectsPerformSelector

集合也可以批量对其中的对象使用performSelector。如NSOperationQueue中cancelAllOperations的操作:

1
2
3
- (void) cancelAllOperations {
[[self operations] makeObjectsPerformSelector: @selector(cancel)];
}

又如其他一些第三方库中的用法,比如ReactiveCoacoa中:

1
2
[orderedGroups makeObjectsPerformSelector:@selector(sendError:) withObject:error];
[orderedGroups makeObjectsPerformSelector:@selector(sendCompleted)];

其实现原理很简单,就是批量调用performSelector操作而已

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Makes each object in the array perform aSelector.<br />
* This is done sequentially from the first to the last object.
*/
- (void) makeObjectsPerformSelector: (SEL)aSelector
{
NSUInteger c = [self count];

if (c > 0)
{
IMP get = [self methodForSelector: oaiSel];
NSUInteger i = 0;

while (i < c)
{
[(*get)(self, oaiSel, i++) performSelector: aSelector];
}
}
}

selector 与 runloop

涉及到延迟的performSelector,无疑是与runloop密切相关的。

延迟执行的selector

延迟执行的方法都在NSObject的一个category (TimedPerformers)中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Sets given message to be sent to this instance after given delay,
* in any run loop mode. See [NSRunLoop].
*/
- (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];
}

这里,采用一个GSTimedPerformer对象,将selector、target、argument、delay进行封装。NSRunLoop采用_timedPerformers来保存这些GSTimedPerformer对象,之后将GSTimedPerformer对象的timer加到runloop的DefaultMode中。如果指定了modes,则加到对应的modes中。

所以,performSelector的afterDelay特性是通过runloop+timer来实现的。

cancel操作

而cancel selector的相关方法都是从NSRunLoop对象的_timedPerformers数组中找到对应的GSTimedPerformer,执行其invalidate即可。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* 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);
}
}

/**
* 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);
}
}

GSTimedPerformer

GSTimedPerformer很简单,即包含了一个selector执行所必须的信息,以及一个NSTimer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* 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

实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@implementation GSTimedPerformer

- (void) dealloc
{
[self finalize];
TEST_RELEASE(timer);
RELEASE(target);
RELEASE(argument);
[super dealloc];
}

- (void) fire
{
DESTROY(timer);
[target performSelector: selector withObject: argument];
[[[NSRunLoop currentRunLoop] _timedPerformers]
removeObjectIdenticalTo: self];
}

- (void) finalize
{
[self invalidate];
}

- (id) initWithSelector: (SEL)aSelector
target: (id)aTarget
argument: (id)anArgument
delay: (NSTimeInterval)delay
{
self = [super init];
if (self != nil)
{
selector = aSelector;
target = RETAIN(aTarget);
argument = RETAIN(anArgument);
timer = [[NSTimer allocWithZone: NSDefaultMallocZone()]
initWithFireDate: nil
interval: delay
target: self
selector: @selector(fire)
userInfo: nil
repeats: NO];
}
return self;
}

- (void) invalidate
{
if (timer != nil)
{
[timer invalidate];
DESTROY(timer);
}
}

@end

这完全就是NSTimer的使用场景,没啥可说的。将timer加到runloo中,然后timer的时机到了,执行 [target performSelector: selector withObject: argument]; 即可。

NSRunLoop中的performSelector

上边讲了NSObject的一个category。而runloop自身也有提供performSelector的相关接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@interface NSRunLoop(OPENSTEP)

- (void) addPort: (NSPort*)port
forMode: (NSString*)mode;

- (void) cancelPerformSelectorsWithTarget: (id)target;

- (void) cancelPerformSelector: (SEL)aSelector
target: (id)target
argument: (id)argument;

- (void) configureAsServer;

- (void) performSelector: (SEL)aSelector
target: (id)target
argument: (id)argument
order: (NSUInteger)order
modes: (NSArray*)modes;

- (void) removePort: (NSPort*)port
forMode: (NSString*)mode;

@end

而其实现相对复杂了一些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/**
* Sets up sending of aSelector to target with argument.<br />
* The selector is sent before the next runloop iteration (unless
* cancelled before then) in any of the specified modes.<br />
* The target and argument objects are retained.<br />
* The order value is used to determine the order in which messages
* are sent if multiple messages have been set up. Messages with a lower
* order value are sent first.<br />
* If the modes array is empty, this method has no effect.
*/
- (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;
if (sel_isEqual(aSelector, @selector(fire)))
{
NSLog(@"WARNING ... there are %u performers scheduled"
@" in mode %@ of %@\n(Latest: fires %@)",
i, mode, self, target);
}
else
{
NSLog(@"WARNING ... there are %u performers scheduled"
@" in mode %@ of %@\n(Latest: [%@ %@])",
i, mode, self, NSStringFromClass([target class]),
NSStringFromSelector(aSelector));
}
}
}
RELEASE(item);
}
}

该方法可以传入一个order,即为执行的优先级。注意这段代码,根据order的大小,将item(即GSRunLoopPerformer对象)。order值越小,则SEL的优先级越高。

1
2
3
4
5
6
p = GSIArrayItemAtIndex(performers, i).obj;
if (p->order > order)
{
GSIArrayInsertItem(performers, (GSIArrayItem)((id)item), i);
break;
}

GSRunLoopPerformer

GSRunLoopPerformer的用法基本与之前的GSTimedPerformer类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* The GSRunLoopPerformer class is used to hold information about
* messages which are due to be sent to objects once each runloop
* iteration has passed.
*/
@interface GSRunLoopPerformer: NSObject
{
@public
SEL selector;
id target;
id argument;
unsigned order;
}

- (void) fire;
- (id) initWithSelector: (SEL)aSelector
target: (id)target
argument: (id)argument
order: (NSUInteger)order;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@implementation GSRunLoopPerformer

- (void) dealloc
{
RELEASE(target);
RELEASE(argument);
[super dealloc];
}

- (void) fire
{
NS_DURING
{
[target performSelector: selector withObject: argument];
}
NS_HANDLER
{
NSLog(@"*** NSRunLoop ignoring exception '%@' (reason '%@') "
@"raised during performSelector... with target %s(%s) "
@"and selector '%s'",
[localException name], [localException reason],
GSClassNameFromObject(target),
GSObjCIsInstance(target) ? "instance" : "class",
sel_getName(selector));
}
NS_ENDHANDLER
}

- (id) initWithSelector: (SEL)aSelector
target: (id)aTarget
argument: (id)anArgument
order: (NSUInteger)theOrder
{
self = [super init];
if (self)
{
selector = aSelector;
target = RETAIN(aTarget);
argument = RETAIN(anArgument);
order = theOrder;
}
return self;
}

@end

我们看到最关键的一句是 [target performSelector: selector withObject: argument]; ,target是id类型,所以这又回到了NSObject的performSelector函数。

不过GSRunLoopPerformer中并没有NSTimer,那么它是如何触发fire函数的呢?

GSRunLoopCtxt

GSRunLoopCtxt对象中有个数组performers,以上操作会将GSRunLoopPerformer对象放到该performers中。进一步追踪该performers数组。

在runloop的 runMode:beforeDate: 方法中:

1
2
3
4
5
6
7
8
9
10
11
12
- (BOOL) runMode: (NSString*)mode beforeDate: (NSDate*)date
{
......

[self _checkPerformers: context];

......

[self acceptInputForMode: mode beforeDate: d];

......
}

_checkPerformers中即有触发GSRunLoopPerformer的fire函数的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (BOOL) _checkPerformers: (GSRunLoopCtxt*)context
{
......

GSRunLoopPerformer *array[count];
for (i = 0; i < count; i++)
{
array[i] = GSIArrayItemAtIndex(performers, i).obj;
}
performers->count = 0;

/* Finally, fire the requests and release them.
*/
for (i = 0; i < count; i++)
{
[array[i] fire];
RELEASE(array[i]);
IF_NO_GC([arp emptyPool];)
}

......
}

所以,目前为止的流程如下:

  1. 调用runloop的 runMode:beforeDate: 方法
  2. 在[self _checkPerformers: context]中,遍历执行每个GSRunLoopPerformer对象的fire函数。
  3. 其中调用[target performSelector: selector withObject: argument]即可。

selector 与 Thread

再看指定线程的相关接口是如何实现的呢?NSObject (NSThreadPerformAdditions) 中。

主线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* <p>This method performs aSelector on the receiver, passing anObject as
* an argument, but does so in the specified thread. The receiver
* and anObject are both retained until the method is performed.
* </p>
* <p>The selector is performed when the runloop of aThread next
* runs in one of the modes specified in anArray.<br />
* Where this method has been called more than once before the runloop
* of the thread runs in the required mode, the order in which the
* operations in the thread is done is the same as that in which
* they were added using this method.
* </p>
* <p>If there are no modes in anArray,
* the method has no effect and simply returns immediately.
* </p>
* <p>The argument aFlag specifies whether the method should wait until
* the selector has been performed before returning.<br />
* <strong>NB.</strong> This method does <em>not</em> cause the runloop of
* aThread to be run ... so if the runloop is not executed by some
* code in aThread, the thread waiting for the perform to complete
* will block forever.
* </p>
* <p>As a special case, if aFlag == YES and the current thread is aThread,
* the modes array is ignored and the selector is performed immediately.
* This behavior is necessary to avoid the current thread being blocked by
* waiting for a perform which will never happen because the runloop is
* not executing.
* </p>
*/
- (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];
}

/**
* Invokes -performSelector:onThread:withObject:waitUntilDone:modes:
* using the supplied arguments and an array containing common modes.<br />
* These modes consist of NSRunLoopMode, NSConnectionreplyMode, and if
* in an application, the NSApplication modes.
*/
- (void) performSelectorOnMainThread: (SEL)aSelector
withObject: (id)anObject
waitUntilDone: (BOOL)aFlag
{
[self performSelectorOnMainThread: aSelector
withObject: anObject
waitUntilDone: aFlag
modes: commonModes()];
}

其他线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
* <p>This method performs aSelector on the receiver, passing anObject as
* an argument, but does so in the specified thread. The receiver
* and anObject are both retained until the method is performed.
* </p>
* <p>The selector is performed when the runloop of aThread next
* runs in one of the modes specified in anArray.<br />
* Where this method has been called more than once before the runloop
* of the thread runs in the required mode, the order in which the
* operations in the thread is done is the same as that in which
* they were added using this method.
* </p>
* <p>If there are no modes in anArray,
* the method has no effect and simply returns immediately.
* </p>
* <p>The argument aFlag specifies whether the method should wait until
* the selector has been performed before returning.<br />
* <strong>NB.</strong> This method does <em>not</em> cause the runloop of
* aThread to be run ... so if the runloop is not executed by some
* code in aThread, the thread waiting for the perform to complete
* will block forever.
* </p>
* <p>As a special case, if aFlag == YES and the current thread is aThread,
* the modes array is ignored and the selector is performed immediately.
* This behavior is necessary to avoid the current thread being blocked by
* waiting for a perform which will never happen because the runloop is
* not executing.
* </p>
*/
- (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);
}
}

/**
* Invokes -performSelector:onThread:withObject:waitUntilDone:modes:
* using the supplied arguments and an array containing common modes.<br />
* These modes consist of NSRunLoopMode, NSConnectionreplyMode, and if
* in an application, the NSApplication modes.
*/
- (void) performSelector: (SEL)aSelector
onThread: (NSThread*)aThread
withObject: (id)anObject
waitUntilDone: (BOOL)aFlag
{
[self performSelector: aSelector
onThread: aThread
withObject: anObject
waitUntilDone: aFlag
modes: commonModes()];
}

/**
* Creates and runs a new background thread sending aSelector to the receiver
* and passing anObject (which may be nil) as the argument.
*/
- (void) performSelectorInBackground: (SEL)aSelector
withObject: (id)anObject
{
[NSThread detachNewThreadSelector: aSelector
toTarget: self
withObject: anObject];
}

如果就在当前线程,则:

  1. 若waitUntilDone传入YES,且runloop为nil,则直接执行[self performSelector: aSelector withObject: anObject],相当于调用objc_msgSend。
  2. 否则,调用runloop的performSelector:target:argument:order:modes:方法将任务加入runloop中。

如果不在当前线程,初始化一个GSPerformHolder对象,调用GSRunLoopThreadInfo对象的addPerformer:方法。

GSPerformHolder

1
2
3
4
5
h = [GSPerformHolder newForReceiver: self
argument: anObject
selector: aSelector
modes: anArray
lock: l];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 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

那么,selector到底是如何执行的么?看来一定是fire函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
- (void) fire
{
GSRunLoopThreadInfo *threadInfo;

if (receiver == nil){
return; // Already fired!
}
threadInfo = GSRunLoopInfoForThread(GSCurrentThread());
[threadInfo->loop cancelPerformSelectorsWithTarget: self];
NS_DURING
{
[receiver performSelector: selector withObject: argument];
}
NS_HANDLER
{
ASSIGN(exception, localException);
if (nil == lock)
{
NSLog(@"*** NSRunLoop ignoring exception '%@' (reason '%@') "
@"raised during perform in other thread... with receiver %p (%s) "
@"and selector '%s'",
[localException name], [localException reason], receiver,
class_getName(object_getClass(receiver)),
sel_getName(selector));
}
}
NS_ENDHANDLER
DESTROY(receiver);
DESTROY(argument);
DESTROY(modes);
if (lock != nil)
{
NSConditionLock *l = lock;

[lock lock];
lock = nil;
[l unlockWithCondition: 1];
}
}

fire函数中,先是调用了runloop的cancelPerformSelectorsWithTarget函数。然后就是直接调用 [receiver performSelector: selector withObject: argument]; ,即最终都是objc_msgSend。

将执行操作包装起来的,实际上就是一个try-catch操作。

1
2
3
# define NS_DURING       @try {
# define NS_HANDLER } @catch (NSException * localException) {
# define NS_ENDHANDLER }

所以,最终关键点在于,fire函数的触发时机是在哪里?来看一看GSRunLoopThreadInfo。

GSRunLoopThreadInfo

GSRunLoopThreadInfo的说明已经非常直观了,用于从当前线程向另一个线程中执行events,其内部loop实例变量即指向当前线程的runloop。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* 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
}
/* Add a performer to be run in the loop's thread. May be called from
* any thread.
*/
- (void) addPerformer: (id)performer;
/* Fire all pending performers in the current thread. May only be called
* from the runloop when the event/descriptor is triggered.
*/
- (void) fire;
/* Cancel all pending performers.
*/
- (void) invalidate;
@end

/* Return (and optionally create) GSRunLoopThreadInfo for the specified
* thread (or the current thread if aThread is nil).<br />
* If aThread is nil and no value is set for the current thread, create
* a GSRunLoopThreadInfo and set it for the current thread.
*/
GSRunLoopThreadInfo *
GSRunLoopInfoForThread(NSThread *aThread) GS_ATTRIB_PRIVATE;

通常使用 GSRunLoopThreadInfo *info = GSRunLoopInfoForThread(aThread); 来从线程中获取runloop相关信息。

GSRunLoopInfoForThread函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

可以看出,NSThread中的_runLoopInfo实例变量即存储了runloop的信息。

GSRunLoopThreadInfo对象的performers数组用于保存GSPerformHolder对象,以上代码即调用了addPerformer方法添加performer对象。addPerformer方法中会对signalled变量进行判断。

1
2
3
4
5
6
7
8
9
10
11
12
if (YES == signalled)
{
[performers addObject: performer];
}
[lock unlock];
if (NO == signalled)
{
/* We failed to add the performer ... so we must invalidate it in
* case there is code waiting for it to complete.
*/
[performer invalidate];
}

GSRunLoopThreadInfo对象的fire函数会触发执行所有的performer。

1
2
3
4
/* Fire all pending performers in the current thread.  May only be called
* from the runloop when the event/descriptor is triggered.
*/
- (void) fire;

该函数会遍历所有的GSPerformHolder对象,依次调用runloop对象的performSelector:方法。target即为GSPerformHolder对象。

1
2
3
4
5
6
7
8
9
for (i = 0; i < c; i++) {
GSPerformHolder *h = [toDo objectAtIndex: i];

[loop performSelector: @selector(fire)
target: h
argument: nil
order: 0
modes: [h modes]];
}

而GSRunLoopThreadInfo的fire函数的触发时机在GSRunLoopCtxt的awakenedBefore函数中:首先获取到GSRunLoopThreadInfo对象,然后判断poll操作,符合条件则触发其fire函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
+ (BOOL) awakenedBefore: (NSDate*)when
{
GSRunLoopThreadInfo *threadInfo = GSRunLoopInfoForThread(nil);
NSTimeInterval ti = (when == nil) ? 0.0 : [when timeIntervalSinceNow];
int milliseconds = (ti <= 0.0) ? 0 : (int)(ti*1000);
struct pollfd pollfds;

/* Watch for signals from other threads.
*/
pollfds.fd = threadInfo->inputFd;
pollfds.events = POLLIN;
pollfds.revents = 0;
if (poll(&pollfds, 1, milliseconds) == 1)
{
NSDebugMLLog(@"NSRunLoop", @"Fire perform on thread");
[threadInfo fire];
return YES;
}
return NO;
}

所以,流程如下:

  1. (BOOL)runMode: (NSString)mode beforeDate: (NSDate)date;
  2. (void)acceptInputForMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
  3. [GSRunLoopCtxt awakenedBefore: nil];
  4. GSRunLoopThreadInfo:fire
  5. [loop performSelector: @selector(fire)

另外,在acceptInputForMode:beforeDate:中,也有线索。

1
2
3
4
5
6
7
8
9
- (void) acceptInputForMode: (NSString*)mode
beforeDate: (NSDate*)limit_date
{
......

done = [context pollUntil: timeout_ms within: _contextStack];

......
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Perform a poll for the specified runloop context.
* If the method has been called re-entrantly, the contexts stack
* will list all the contexts with polls in progress
* and this method must tell those outer contexts not to handle events
* which are handled by this context.
*/
- (BOOL) pollUntil: (int)milliseconds within: (NSArray*)contexts
{
GSRunLoopThreadInfo *threadInfo = GSRunLoopInfoForThread(nil);

......

if (fdIndex == threadInfo->inputFd)
{
NSDebugMLLog(@"NSRunLoop", @"Fire perform on thread");
[threadInfo fire];
watcher = nil;
}

......
}

其中,会触发GSRunLoopThreadInfo对象的fire函数。

CFRunLoopPerformBlock

runloop中的block任务,就是通过CFRunLoopPerformBlock加进去的。

1
2
3
RunLoop.current.perform {
print("RunLoop.current.perform")
}
1
2
3
4
5
6
7
public func perform(inModes modes: [RunLoop.Mode], block: @escaping () -> Void) {
CFRunLoopPerformBlock(getCFRunLoop(), (modes.map { $0._cfStringUniquingKnown })._cfObject, block)
}

public func perform(_ block: @escaping () -> Void) {
perform(inModes: [.default], block: block)
}

内存问题

performSelector方法仅仅相当于objc_msgSend而已,编译器认定其调用返回值是一个对象,并且不会对其进行引用计数管理。所以需要手动管理器引用计数。

如果是alloc、new、copy、mutableCopy,则方法调用会开辟一块内存空间,编译器依然不会对其retain和release,所以会存在内存泄漏。

1
2
3
4
5
6
SEL cop = @selector(copy);
NSString *a = @"aaa";
id se = [a performSelector:cop];
NSLog(@"se %@", se);
......
se = nil; // 手动置为nil释放内存,以免存在内存泄漏

所以,performSelector的执行代码会有一个存在内存泄漏风险的警告。如果确定不会有泄漏,则添加#pragma消除警告即可。

1
2
3
4
5
6
SEL selector = @selector(onTestPerformSeletor);

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:selector];
#pragma clang diagnostic pop

参考资料

坚持原创技术分享,您的支持将鼓励我继续创作! So,来杯咖啡?