performSelector系列接口是runtime的一大特點,能夠執行延遲,還能指定線程執行。本文從源碼角度分析了其實現原理。objective-c
關於涉及到的數據結構,這裏只是簡單介紹一下。swift
SEL用於在runtime中表示一個方法名。與其對應的方法實現則使用IMP來表示。api
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
複製代碼
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類型的方法選擇器。數組
struct objc_selector {
void *sel_id;
const char *sel_types;
};
複製代碼
或者簡單理解爲char *, 映射到方法的C字符串。bash
struct objc_selector {
char name[64 or ...];
};
複製代碼
IMP即爲一個函數指針,指向函數的實現體。數據結構
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.app
Method即用於表示runtime的一個方法,其內部包含了selector和函數實現_imp,以及方法簽名。less
typedef struct objc_method *Method;
An opaque type that represents a method in a class definition. 複製代碼
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
複製代碼
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.oop
該方法返回SEL對應的方法簽名。
/**
* 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。
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);
}
}
}
複製代碼
根據SEL來查找到對應的函數實現IMP。
在swift/stdlib/public/runtime/SwiftObject.mm中的實現以下:
- (IMP)methodForSelector:(SEL)sel {
return class_getMethodImplementation(object_getClass(self), sel);
}
複製代碼
直接調用runtime的函數class_getMethodImplementation便可。而在libs-base/Source/NSObject.m中的實現以下:
/**
* 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用於執行一個SEL。
/**
* 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的底層實現以下:
- (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中的實現以下:
/**
* 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一致。
集合也能夠批量對其中的對象使用performSelector。如NSOperationQueue中cancelAllOperations的操做:
- (void) cancelAllOperations {
[[self operations] makeObjectsPerformSelector: @selector(cancel)];
}
複製代碼
又如其餘一些第三方庫中的用法,好比ReactiveCoacoa中:
[orderedGroups makeObjectsPerformSelector:@selector(sendError:) withObject:error];
[orderedGroups makeObjectsPerformSelector:@selector(sendCompleted)];
複製代碼
其實現原理很簡單,就是批量調用performSelector操做而已
/**
* 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];
}
}
}
複製代碼
涉及到延遲的performSelector,無疑是與runloop密切相關的。
延遲執行的方法都在NSObject的一個category (TimedPerformers)中。
/**
* 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 selector的相關方法都是從NSRunLoop對象的_timedPerformers數組中找到對應的GSTimedPerformer,執行其invalidate便可。源碼以下:
/**
* 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很簡單,即包含了一個selector執行所必須的信息,以及一個NSTimer。
/*
* 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
複製代碼
實現以下:
@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]; 便可。
上邊講了NSObject的一個category。而runloop自身也有提供performSelector的相關接口。
@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
複製代碼
而其實現相對複雜了一些:
/**
* 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的優先級越高。
p = GSIArrayItemAtIndex(performers, i).obj;
if (p->order > order)
{
GSIArrayInsertItem(performers, (GSIArrayItem)((id)item), i);
break;
}
複製代碼
GSRunLoopPerformer的用法基本與以前的GSTimedPerformer相似。
/*
* 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
複製代碼
@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對象中有個數組performers,以上操做會將GSRunLoopPerformer對象放到該performers中。進一步追蹤該performers數組。
在runloop的 runMode:beforeDate: 方法中:
- (BOOL) runMode: (NSString*)mode beforeDate: (NSDate*)date
{
......
[self _checkPerformers: context];
......
[self acceptInputForMode: mode beforeDate: d];
......
}
複製代碼
_checkPerformers中即有觸發GSRunLoopPerformer的fire函數的地方。
- (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];)
}
......
}
複製代碼
因此,目前爲止的流程以下:
再看指定線程的相關接口是如何實現的呢?NSObject (NSThreadPerformAdditions) 中。
/**
* <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()];
}
複製代碼
/**
* <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];
}
複製代碼
若是就在當前線程,則:
若是不在當前線程,初始化一個GSPerformHolder對象,調用GSRunLoopThreadInfo對象的addPerformer:方法。
h = [GSPerformHolder newForReceiver: self
argument: anObject
selector: aSelector
modes: anArray
lock: l];
複製代碼
/**
* 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函數。
- (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操做。
# define NS_DURING @try {
# define NS_HANDLER } @catch (NSException * localException) {
# define NS_ENDHANDLER }
複製代碼
因此,最終關鍵點在於,fire函數的觸發時機是在哪裏?來看一看GSRunLoopThreadInfo。
GSRunLoopThreadInfo的說明已經很是直觀了,用於從當前線程向另外一個線程中執行events,其內部loop實例變量即指向當前線程的runloop。
/* 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函數以下:
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變量進行判斷。
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。
/* 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對象。
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函數。
+ (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;
}
複製代碼
因此,流程以下:
另外,在acceptInputForMode:beforeDate:中,也有線索。
- (void) acceptInputForMode: (NSString*)mode
beforeDate: (NSDate*)limit_date
{
......
done = [context pollUntil: timeout_ms within: _contextStack];
......
}
複製代碼
/**
* 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函數。
runloop中的block任務,就是經過CFRunLoopPerformBlock加進去的。
RunLoop.current.perform {
print("RunLoop.current.perform")
}
複製代碼
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,因此會存在內存泄漏。
SEL cop = @selector(copy);
NSString *a = @"aaa";
id se = [a performSelector:cop];
NSLog(@"se %@", se);
......
se = nil; // 手動置爲nil釋放內存,以避免存在內存泄漏
複製代碼
因此,performSelector的執行代碼會有一個存在內存泄漏風險的警告。若是肯定不會有泄漏,則添加#pragma消除警告便可。
SEL selector = @selector(onTestPerformSeletor);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:selector];
#pragma clang diagnostic pop
複製代碼