iOS的performSelector是如何實現的?

performSelector系列接口是runtime的一大特點,能夠執行延遲,還能指定線程執行。本文從源碼角度分析了其實現原理。objective-c

數據結構

關於涉及到的數據結構,這裏只是簡單介紹一下。swift

SEL

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

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

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;
};
複製代碼

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.oop

methodSignatureForSelector

該方法返回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);
	}
    }
}
複製代碼

methodForSelector

根據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

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一致。

集合對象的makeObjectsPerformSelector

集合也能夠批量對其中的對象使用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];
	}
    }
}
複製代碼

selector 與 runloop

涉及到延遲的performSelector,無疑是與runloop密切相關的。

延遲執行的selector

延遲執行的方法都在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操做

而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

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]; 便可。

NSRunLoop中的performSelector

上邊講了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

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

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];)
  }

  ......
}
複製代碼

因此,目前爲止的流程以下:

  1. 調用runloop的 runMode:beforeDate: 方法
  2. 在[self _checkPerformers: context]中,遍歷執行每一個GSRunLoopPerformer對象的fire函數。
  3. 其中調用[target performSelector: selector withObject: argument]便可。

selector 與 Thread

再看指定線程的相關接口是如何實現的呢?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];
}
複製代碼

若是就在當前線程,則:

  1. 若waitUntilDone傳入YES,且runloop爲nil,則直接執行[self performSelector: aSelector withObject: anObject],至關於調用objc_msgSend。
  2. 不然,調用runloop的performSelector:target:argument:order:modes:方法將任務加入runloop中。

若是不在當前線程,初始化一個GSPerformHolder對象,調用GSRunLoopThreadInfo對象的addPerformer:方法。

GSPerformHolder

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

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;
}
複製代碼

因此,流程以下:

  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:中,也有線索。

- (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函數。

CFRunLoopPerformBlock

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
複製代碼

參考資料

相關文章
相關標籤/搜索