We are familiar with the performSelector family of functions, but it is often difficult to distinguish the different variants and underlying principles, so I want to use the Runtime source code and GUNStep source code to pick apart the pieces. Straighten out the different variants of performSelector, and make sense of the underlying implementation of each method.

The code for this article is available on Github

NSObjectPerformSelector

1.1 explore

  • performSelector:(SEL)aSelector

The performSelector method is one of the simplest apis, and uses it as follows

- (void)jh_performSelector
{
     [self performSelector:@selector(task)];
}

- (void)task
{
    NSLog(@"%s", __func__);
}

/ / output
2020- 03- 12 11:13:26.321254+0800 PerformSelectorIndepth[61807:828757] -[ViewController task]
Copy the code

PerformSelector: The method simply passes in an SEL, which is implemented at runtime:

- (id)performSelector:(SEL)sel {
    if(! sel) [self doesNotRecognizeSelector:sel];
    return ((id(*) (id, SEL))objc_msgSend)(self, sel);
}
Copy the code

  • performSelector:(SEL)aSelector withObject:(id)object

PerformSelector: withObject: method compared with more than one way to a parameter, use the following:

- (void)jh_performSelectorWithObj
{
    [self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"}];
}

- (void)taskWithParam:(NSDictionary *)param
{
    NSLog(@"%s", __func__);
    NSLog(@ "% @", param);
}

/ / output
2020- 03- 12 11:12:34.473153+0800 PerformSelectorIndepth[61790:827408] -[ViewController taskWithParam:]
2020- 03- 12 11:12:34.473381+0800 PerformSelectorIndepth[61790:827408] {
    param = leejunhui;
}
Copy the code

PerformSelector: withObject: approach the underlying implementation is as follows:

- (id)performSelector:(SEL)sel withObject:(id)obj {
    if(! sel) [self doesNotRecognizeSelector:sel];
    return ((id(*) (id, SEL, id))objc_msgSend)(self, sel, obj);
}
Copy the code

  • performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2

This method has one more parameter than the previous method:

- (void)jh_performSelectorWithObj1AndObj2
{
    [self performSelector:@selector(taskWithParam1:param2:) withObject:@{@"param1": @"lee"} withObject:@{@"param2": @"junhui"}];
}

- (void)taskWithParam1:(NSDictionary *)param1 param2:(NSDictionary *)param2
{
    NSLog(@"%s", __func__);
    NSLog(@ "% @", param1);
    NSLog(@ "% @", param2);
}

/ / output
2020- 03- 12 11:17:52.889731+0800 PerformSelectorIndepth[61859:833076] -[ViewController taskWithParam1:param2:]
2020- 03- 12 11:17:52.889921+0800 PerformSelectorIndepth[61859:833076] {
    param1 = lee;
}
2020- 03- 12 11:17:52.890009+0800 PerformSelectorIndepth[61859:833076] {
    param2 = junhui;
}
Copy the code

PerformSelector: withObject: withObject: approach the underlying implementation is as follows:

- (id)performSelector:(SEL)sel withObject:(id)obj1 withObject:(id)obj2 {
    if(! sel) [self doesNotRecognizeSelector:sel];
    return ((id(*) (id, SEL, id.id))objc_msgSend)(self, sel, obj1, obj2);
}
Copy the code

1.2 summary

methods The underlying implementation
performSelector: ((id(*)(id, SEL))objc_msgSend)(self, sel)
performSelector:withObject: ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj)
performSelector:withObject:withObject: ((id(*)(id, SEL, id, id))objc_msgSend)(self, sel, obj1, obj2)

These three methods should be part of the performSelector family of methods that we use a lot, but we just need to remember that underneath all three methods are messages that are being sent.

Runloop-relatedPerformSelector

As shown above, in the NSRunLoop header file, two categories are defined

  • NSDelayedPerformingCorresponding to theNSObject
  • NSOrderedPerformCorresponding to theNSRunLoop

2.1 NSObject Classification NSDelayedPerforming

2.1.1 explore

  • performSelector:WithObject:afterDelay:
- (void)jh_performSelectorwithObjectafterDelay
{
    [self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:1.f];
}

- (void)taskWithParam:(NSDictionary *)param
{
    NSLog(@"%s", __func__);
    NSLog(@ "% @", param);
}

/ / output
2020- 03- 12 11:25:01.475634+0800 PerformSelectorIndepth[61898:838345] -[ViewController taskWithParam:]
2020- 03- 12 11:25:01.475837+0800 PerformSelectorIndepth[61898:838345] {
    param = leejunhui;
}
Copy the code

This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.

This method sets a timer in the runloop corresponding to the current thread to execute the SEL passed in. The timer is triggered only in NSDefaultRunLoopMode. When the timer is started, the thread attempts to retrieve the SEL from the runloop and execute it. If runloop has started and is in NSDefaultRunLoopMode, SEL executes successfully. Otherwise, the timer waits until the runloop is in NSDefaultRunLoopMode

With breakpoint debugging as shown in the figure below, the runloop bottom layer finally triggers the execution of the task via __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ ().

Since NSRunLoop is not open source, we can only peek into the low-level implementation details through GNUStep, as shown below:

- (void) performSelector: (SEL)aSelector
	      withObject: (id)argument
	      afterDelay: (NSTimeInterval)seconds
{
  NSRunLoop		*loop = [NSRunLoop currentRunLoop];
  GSTimedPerformer	*item;

  item = [[GSTimedPerformer alloc] initWithSelector: aSelector
					     target: self
					   argument: argument
					      delay: seconds];
  [[loop _timedPerformers] addObject: item];
  RELEASE(item);
  [loop addTimer: item->timer forMode: NSDefaultRunLoopMode];
}


/* * The GSTimedPerformer class is used to hold information about * messages which are due to be sent to objects at a particular time. */
@interface GSTimedPerformer: NSObject
{
@public
  SEL		selector;
  id		target;
  id		argument;
  NSTimer	*timer;
}

- (void) fire;
- (id) initWithSelector: (SEL)aSelector
		 target: (id)target
	       argument: (id)argument
		  delay: (NSTimeInterval)delay;
- (void) invalidate;
@end
Copy the code

We can see that the performSelector: WithObject: afterDelay: the ground floor

  • Gets the value of the current threadNSRunLoopObject.
  • Through incomingSELargumentdelayInitialize oneGSTimedPerformerInstance object,GSTimedPerformerType is encapsulated inNSTimerObject.
  • Then put theGSTimedPerformerInstance join toRunLoopThe object’s_timedPerformersIn a member variable
  • releaseGSTimedPerformerobject
  • In order todefault modetimerObject joins torunloop

  • performSelector:WithObject:afterDelay:inModes

PerformSelector: WithObject: afterDelay: inModes method compared with the last one more modes parameters, based on the definition of official documents, only when the runloop modes are any mode, to perform a task, If modes are empty, the task will not be performed.

- (void)jh_performSelectorwithObjectafterDelayInModes
{
    [self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:1.f inModes:@[NSRunLoopCommonModes]];
}

- (void)taskWithParam:(NSDictionary *)param
{
    NSLog(@"%s", __func__);
    NSLog(@ "% @", param);
}

// Print the following
2020- 03- 12 11:38:58.479152+0800 PerformSelectorIndepth[62006:851520] -[ViewController taskWithParam:]
2020- 03- 12 11:38:58.479350+0800 PerformSelectorIndepth[62006:851520] {
    param = leejunhui;
}
Copy the code

If we change the modes parameter to UITrackingRunLoopMode, the timer will only be triggered when the scrollView is rolled

Let’s take a look at GNUStep’s corresponding implementation:

- (void) performSelector: (SEL)aSelector
	      withObject: (id)argument
	      afterDelay: (NSTimeInterval)seconds
		 inModes: (NSArray*)modes
{
  unsigned	count = [modes count];

  if (count > 0)
    {
      NSRunLoop		*loop = [NSRunLoop currentRunLoop];
      NSString		*marray[count];
      GSTimedPerformer	*item;
      unsigned		i;

      item = [[GSTimedPerformer alloc] initWithSelector: aSelector
						 target: self
					       argument: argument
						  delay: seconds];
      [[loop _timedPerformers] addObject: item];
      RELEASE(item);
      if ([modes isProxy])
	{
	  for (i = 0; i < count; i++) { marray[i] = [modes objectAtIndex: i]; }}else
	{
          [modes getObjects: marray];
	}
      for (i = 0; i < count; i++) { [loop addTimer: item->timer forMode: marray[i]]; }}}@end
Copy the code

As you can see, unlike the underlying implementation of the previous method, timer objects of different modes are added to the runloop in a loop.


  • cancelPreviousPerformRequestsWithTarget:
  • cancelPreviousPerformRequestsWithTarget:selector:object:

CancelPreviousPerformRequestsWithTarget: methods and cancelPreviousPerformRequestsWithTarget: selector: object: Method are two class, their role is to cancel prior to execution by performSelector: WithObject: afterDelay: methods registered task. Use as follows:

- (void)jh_performSelectorwithObjectafterDelayInModes
{
    // The timer is triggered only when the scrollView is rolled
// [self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:1.f inModes:@[UITrackingRunLoopMode]];
    
    [self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:5.f inModes:@[NSRunLoopCommonModes]];
}

- (IBAction)cancelTask {
    NSLog(@"%s", __func__);
    [ViewController cancelPreviousPerformRequestsWithTarget:self selector:@selector(taskWithParam:) object:@{@"param": @"leejunhui"}];
    
    // [ViewController cancelPreviousPerformRequestsWithTarget:self];
}

/ / output
2020- 03- 12 11:52:33.549213+0800 PerformSelectorIndepth[62172:865289] -[ViewController cancelTask]
Copy the code

There is a difference, it is cancelPreviousPerformRequestsWithTarget: class method will be cancelled on all the target through the performSelector: WithObject: afterDelay: Instance methods registered timed tasks and cancelPreviousPerformRequestsWithTarget: selector: object: only through incoming SEL cancel match to automate tasks

The underlying implementation in GNUStep cancelPreviousPerformRequestsWithTarget: method is as follows:

/* * Cancels any perform operations set up for the specified target * in the current run loop. */
+ (void) cancelPreviousPerformRequestsWithTarget: (id)target
{
    NSMutableArray	*perf = [[NSRunLoop currentRunLoop] _timedPerformers];
    unsigned		count = [perf count];
    
    if (count > 0)
    {
        GSTimedPerformer	*array[count];
        
        IF_NO_GC(RETAIN(target));
        [perf getObjects: array];
        while (count-- > 0)
        {
            GSTimedPerformer	*p = array[count];
            
            if(p->target == target) { [p invalidate]; [perf removeObjectAtIndex: count]; } } RELEASE(target); }}// GSTimedPerformer example method
- (void) invalidate
{
    if(timer ! =nil) { [timer invalidate]; DESTROY(timer); }}Copy the code

The logic here is clear:

  • Retrieves the member variables of the current Runloop object_timedPerformers
  • Check whether the scheduled task array is empty. If it is not empty, it will continue to proceed
  • Initialize a local, empty array of tasks, and passgetObjectsRetrieves the task from a member variable
  • Loop through all tasks through the while loop, if a match is foundtarget, calls the task’s invalidate method, inside which the timer is stopped and then destroyed. And then we need to put the member variables_timedPerformersThe corresponding task is removed

Another way to cancel a task is implemented below:

/* * 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.

* 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); }}Copy the code

The difference here is that in addition to checking whether the target matches, it also checks whether the SEL matches and whether the parameters match.


2.1.2 summary

  • performSelector:WithObject:afterDelay:
    • When the runloop of the thread in which the method resides is in default mode, the given task is fired at the given time. The underlying principle is to add a timer object to a Runloop object in default mode and wait for it to wake up.
  • performSelector:WithObject:afterDelay:inModes:
    • When the runloop of the thread of the method is in any given mode, the given task is fired at the given time. The underlying principle is that the loop adds a timer object to the runloop object in a given mode and waits for it to wake up.
  • cancelPreviousPerformRequestsWithTarget:
    • canceltargetObject throughperformSelector:WithObject:afterDelay:Method orperformSelector:WithObject:afterDelay:inModes:Method registeredAll scheduled Tasks
  • cancelPreviousPerformRequestsWithTarget:selector:object:
    • canceltargetObject throughperformSelector:WithObject:afterDelay:Method orperformSelector:WithObject:afterDelay:inModes:Method registeredSpecifies a scheduled task

These four methods exist in the NSRunLoop source code as the NSDelayedPerforming classification of NSObject, so we need to pay attention to the details when we use them, and that is whether the thread executing these methods is the main thread. If it is the main thread, then it should be fine. However, if these methods are executed in a child thread, the runloop corresponding to the child thread must be enabled to ensure successful execution.

- (void)jh_performSelectorwithObjectafterDelay
{
    dispatch_async(dispatch_get_global_queue(0.0) ^ {[self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:1.f];
    });
    
// [self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:1.f];
}
    
- (void)taskWithParam:(NSDictionary *)param
{
    NSLog(@"%s", __func__);
    NSLog(@ "% @", param);
}    


// No output
Copy the code

As shown above, the GCD asynchronous execution function executes a task on a global concurrent queue without any print-out, and the result is completely different when we add the runloop startup code:

For the performSelector: WithObject: afterDelay: inModes method, if you encounter such a situation, it’s the same solution.

2.2 Classification of NSRunLoop NSOrderedPerform

2.2.1 explore

  • performSelector:target:argument:order:modes:

performSelector:target:argument:order:modes: The caller of this method is an NSRunLoop instance, and it needs to pass in the SEL to be executed, its corresponding target, and the argument to be received by the SEL. Finally, it needs to pass in the priority order of this task, and a set of operation modes. The purpose is that when currentMode of runloop is in any of the modes in the set, SEL execution will be triggered in order of priority. Specific use is as follows:

- (void)jh_performSelectorTargetArgumentOrderModes
{
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop performSelector:@selector(runloopTask5) target:self argument:nil order:5 modes:@[NSRunLoopCommonModes]];
    [runloop performSelector:@selector(runloopTask1) target:self argument:nil order:1 modes:@[NSRunLoopCommonModes]];
    [runloop performSelector:@selector(runloopTask3) target:self argument:nil order:3 modes:@[NSRunLoopCommonModes]];
    [runloop performSelector:@selector(runloopTask2) target:self argument:nil order:2 modes:@[NSRunLoopCommonModes]];
    [runloop performSelector:@selector(runloopTask4) target:self argument:nil order:4 modes:@[NSRunLoopCommonModes]];
}

- (void)runloopTask1
{
    NSLog(@ "runloop task 1");
}

- (void)runloopTask2
{
    NSLog(@ "runloop task 2");
}

- (void)runloopTask3
{
    NSLog(@ "runloop task 3");
}

- (void)runloopTask4
{
    NSLog(4 "@" runloop task);
}

- (void)runloopTask5
{
    NSLog(5 "@" runloop task);
}

/ / output
2020- 03- 12 14:23:27.088636+0800 PerformSelectorIndepth[62976:972980] runloop task1
2020- 03- 12 14:23:27.088760+0800 PerformSelectorIndepth[62976:972980] runloop task2
2020- 03- 12 14:23:27.088868+0800 PerformSelectorIndepth[62976:972980] runloop task3
2020- 03- 12 14:23:27.088964+0800 PerformSelectorIndepth[62976:972980] runloop task4
2020- 03- 12 14:23:27.089048+0800 PerformSelectorIndepth[62976:972980] runloop task5
Copy the code

You can see that the output is executed in the order in which we passed the order argument as tasks.

The underlying implementation of this layer in GUNStep is as follows:

- (void) performSelector: (SEL)aSelector
                  target: (id)target
                argument: (id)argument
                   order: (NSUInteger)order
                   modes: (NSArray*)modes
{
    unsigned		count = [modes count];
    
    if (count > 0)
    {
        NSString			*array[count];
        GSRunLoopPerformer	*item;
        
        item = [[GSRunLoopPerformer alloc] initWithSelector: aSelector
                                                     target: target
                                                   argument: argument
                                                      order: order];
        
        if ([modes isProxy])
        {
            unsigned	i;
            
            for (i = 0; i < count; i++) { array[i] = [modes objectAtIndex: i]; }}else
        {
            [modes getObjects: array];
        }
        while (count-- > 0)
        {
            NSString	*mode = array[count];
            unsigned	end;
            unsigned	i;
            GSRunLoopCtxt	*context;
            GSIArray	performers;
            
            context = NSMapGet(_contextMap, mode);
            if (context == nil)
            {
                context = [[GSRunLoopCtxt alloc] initWithMode: mode
                                                        extra: _extra];
                NSMapInsert(_contextMap, context->mode, context);
                RELEASE(context);
            }
            performers = context->performers;
            
            end = GSIArrayCount(performers);
            for (i = 0; i < end; i++)
            {
                GSRunLoopPerformer	*p;
                
                p = GSIArrayItemAtIndex(performers, i).obj;
                if (p->order > order)
                {
                    GSIArrayInsertItem(performers, (GSIArrayItem)((id)item), i);
                    break; }}if (i == end)
            {
                GSIArrayInsertItem(performers, (GSIArrayItem)((id)item), i);
            }
            i = GSIArrayCount(performers);
            if (i % 1000= =0 && i > context->maxPerformers)
            {
                context->maxPerformers = i;
                NSLog(@"WARNING ... there are %u performers scheduled"
                      @" in mode %@ of %@\n(Latest: [%@ %@])",
                      i, mode, self.NSStringFromClass([target class]),
                      NSStringFromSelector(aSelector)); } } RELEASE(item); }}@interface GSRunLoopPerformer: NSObject
{
@public
    SEL		selector;
    id		target;
    id		argument;
    unsigned	order;
}
Copy the code

We already know the performSelector: WithObject: afterDelay: The underlying implementation of the timer method uses a data structure that wraps the timer object. In this case, it uses a data structure that wraps the selector, target, argument, and priority order. It also stores the task queue to be executed in the member variables of context context, so it is actually a simple insertion and sorting process.


  • cancelPerformSelector:target:argument:
  • cancelPerformSelectorsWithTarget:

CancelPerformSelector: target: argument: and cancelPerformSelectorsWithTarget: It’s easy to use, one passing in selector, target, and argument, and the other just passing in target. It is used to search for tasks in the task queue of performers at the bottom of Runloop according to given three parameters or targets, and remove them from the queue if they are found.

The underlying implementation is as follows:

/** * Cancels any perform operations set up for the specified target * in the receiver. */
- (void) cancelPerformSelectorsWithTarget: (id) target
{
    NSMapEnumerator	enumerator;
    GSRunLoopCtxt		*context;
    void			*mode;
    
    enumerator = NSEnumerateMapTable(_contextMap);
    
    while (NSNextMapEnumeratorPair(&enumerator, &mode, (void**)&context))
    {
        if(context ! =nil)
        {
            GSIArray	performers = context->performers;
            unsigned	count = GSIArrayCount(performers);
            
            while (count--)
            {
                GSRunLoopPerformer	*p;
                
                p = GSIArrayItemAtIndex(performers, count).obj;
                if(p->target == target) { GSIArrayRemoveItemAtIndex(performers, count); }}}}NSEndMapTableEnumeration(&enumerator);
}

/** * Cancels any perform operations set up for the specified target * in the receiver, but only if the value of aSelector and argument * with which the performs were set up match those supplied.

* Matching of the argument may be either by pointer equality or by * use of the [NSObject-isEqual:] method. */
- (void) cancelPerformSelector: (SEL)aSelector target: (id) target argument: (id) argument { NSMapEnumerator enumerator; GSRunLoopCtxt *context; void *mode; enumerator = NSEnumerateMapTable(_contextMap); while (NSNextMapEnumeratorPair(&enumerator, &mode, (void**)&context)) { if(context ! =nil) { GSIArray performers = context->performers; unsigned count = GSIArrayCount(performers); while (count--) { GSRunLoopPerformer *p; p = GSIArrayItemAtIndex(performers, count).obj; if(p->target == target && sel_isEqual(p->selector, aSelector) && (p->argument == argument || [p->argument isEqual: argument])) { GSIArrayRemoveItemAtIndex(performers, count); }}}}NSEndMapTableEnumeration(&enumerator); } Copy the code

2.2.2 summary

  • performSelector:target:argument:order:modes:
    • Fires the given task when the thread of the method’s runloop is in any given mode and at the beginning of the next Runloop message loop. The underlying principle is that the loop adds a timer-like object to the task queue in the context of the Runloop, waiting to be woken up
  • cancelPerformSelector:target:argument:
    • Cancels the target object from passingperformSelector:target:argument:order:modes:Method method registeredAssigned task
  • cancelPerformSelectorsWithTarget:
    • Cancels the target object from passingperformSelector:target:argument:order:modes:Method method registeredAll the tasks

It is also important to note that if these methods are executed in a child thread, the runloop corresponding to the child thread must be enabled to ensure successful execution.

3, Thread relatedperformSelector

As shown in the figure above, NSObject’s classification of NSThreadPerformAdditions is defined in NSThread, in which five performSelector methods are defined.

3.1 explore

  • performSelector:onThread:withObject:waitUntilDone:
  • performSelector:onThread:withObject:waitUntilDone:modes:

The first method calls the second method, and the mode passes in kCFRunLoopCommonModes. We will only study the first method here.

This method needs than performSeletor: withObject: more than two parameters, respectively which thread is to perform a task and whether to block the current thread. Be careful with this method though, as the following is a common mistake:

Failed target thread exited while waiting for the perform; failed target thread exited while waiting for the perform Be familiar with the iOS multithreaded classmates know NSThread threads instantiated object after the start will be recycling system, and then call the performSelector: onThread: withObject: waitUntilDone: The method executes on a thread that has already been reclaimed, and obviously crashes. The solution here is to start the runloop corresponding to the child thread, so that the thread can “work when something is up, sleep when nothing is up” function, as follows:

For the waitUntilDone parameter, if we set it to YES:

If set to NO:

So waitUntilDone here can simply be understood as controlling synchronous or asynchronous execution.

Before exploring the GNUStep counterpart, familiarize yourself with GSRunLoopThreadInfo

/* Used to handle events performed in one thread from another. */
@interface      GSRunLoopThreadInfo : NSObject
{
  @public
  NSRunLoop             *loop;
  NSLock                *lock;
  NSMutableArray        *performers;
#ifdef _WIN32
  HANDLE	        event;
#else
  int                   inputFd;
  int                   outputFd;
#endif	
}
Copy the code

GSRunLoopThreadInfo is a thread-specific property that stores information between the thread and the runloop, which can be obtained as follows:

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;
}
Copy the code

Then there’s another GSPerformHolder:

/** * This class performs a dual function ... * 

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

* During initialisation, the default runloop is set up to watch * for data arriving on inputFd. *

*

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

*

* The initialize method of this class is called before any new threads * run. *

*/
@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 Copy the code

GSPerformHolder encapsulates the task details (receiver, argument, selector) as well as the operation mode (mode) and a conditional lock (NSConditionLock).

Then we gaze into source performSelector: onThread: withObject: waitUntilDone: modes: on the concrete implementation:

- (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: 0modes: 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); }}Copy the code
  • Declare aGSRunLoopThreadInfoObject and itemNSThreadthread
  • Checks whether the run mode array parameter is empty
  • Gets the current thread and assigns the result to the local thread variable declared in the first step
  • Determine if the incoming thread aThread is null, then assign the current thread to aThread
  • After ensuring that aThread is not empty, get the corresponding value of the threadGSRunLoopThreadInfoObject and assign to the local INFO variable declared in the first step
  • After ensuring that the info has a value, determine whether the task is being executed on the current thread
  • If the task is executed on the current thread, then determine whether to block the current thread or whether the current thread’s Runloop is empty.
    • If so, call it directlyperformSelector:withObjectTo carry out the mission.
    • If not, it is called through the thread’s corresponding Runloop objectperformSelector:target:argument:order:modes:To carry out the mission.
  • Declare one if the task is not executed on the current threadGSPerformHolderLocal variable that declares an empty conditional lockNSConditionLock
    • Determines whether the thread to execute the task has been reclaimed and throws an exception if it has been reclaimed
    • If not recycled
      • Determines whether to block the current thread and initializes the conditional lock if the passed argument needs to block
      • Class based on passed parameters and conditional locksGSPerformHolderThe instance
      • Then add it to infoGSPerformHolderThe instance
      • Then determine if the conditional lock is not empty, give the conditional lock when to lock, then unlock the conditional lock, then release the conditional lock
      • judgeGSPerformHolderWhether the local variable has been released, if not, throw an exception

  • performSelectorOnMainThread:withObject:waitUntilDone:
  • performSelectorOnMainThread:withObject:waitUntilDone:modes:

As the name suggests, these two methods simply execute tasks on the main thread, depending on the parameters passed in to decide whether to block the main thread and in which running modes to execute tasks. The usage method is as follows:

- (void)jh_performSelectorOnMainThreadwithObjectwaitUntilDone
{
    [self performSelectorOnMainThread:@selector(threadTask:) withObject:@{@"param": @"leejunhui"} waitUntilDone:NO];
// [self performSelectorOnMainThread:@selector(threadTask:) withObject:@{@"param": @"leejunhui"} waitUntilDone:NO modes:@[NSRunLoopCommonModes]];
}

- (void)threadTask:(NSDictionary *)param
{
    NSLog(@"%s", __func__);
    NSLog(@ "% @"[NSThread currentThread]);
}

/ / output
2020- 03- 12 16:14:31.783962+0800 PerformSelectorIndepth[63614:1057033] -[ViewController threadTask:]
2020- 03- 12 16:14:31.784126+0800 PerformSelectorIndepth[63614:1057033] <NSThread: 0x600002e76dc0>{number = 1, name = main}
Copy the code

Since the execution is on the main thread, there is no need to manually enable runloop. Let’s take a look at the underlying implementation of these two methods in GNUStep:

- (void) performSelectorOnMainThread: (SEL)aSelector
                          withObject: (id)anObject
                       waitUntilDone: (BOOL)aFlag
                               modes: (NSArray*)anArray
{
    /* It's possible that this method could be called before the NSThread * class is initialised, so we check and make sure it's initiailised * if necessary. */
    if (defaultThread == nil) {[NSThread currentThread];
    }
    [self performSelector: aSelector
                 onThread: defaultThread
               withObject: anObject
            waitUntilDone: aFlag
                    modes: anArray];
}

- (void) performSelectorOnMainThread: (SEL)aSelector
                          withObject: (id)anObject
                       waitUntilDone: (BOOL)aFlag
{
    [self performSelectorOnMainThread: aSelector
                           withObject: anObject
                        waitUntilDone: aFlag
                                modes: commonModes()];
}
Copy the code

It is not hard to see, here is actually called performSelector: onThread: withObject: waitUntilDone: method of modes, but there is a need to pay attention to detail, is likely before the NSThread class is initialized, Call the performSelectorOnMainThread method, so need to manually call [NSThread currentThread].


  • performSelectorInBackground:withObject:

Finally is to explore performSelectorInBackground: withObject: method, this method use is as follows:

- (void)jh_performSelectorOnBackground
{
    [self performSelectorInBackground:@selector(threadTask:) withObject:@{@"param": @"leejunhui"}];
}

- (void)threadTask:(NSDictionary *)param
{
    NSLog(@"%s", __func__);
    NSLog(@ "% @"[NSThread currentThread]);
}

/ / output
2020- 03- 12 16:19:36.751675+0800 PerformSelectorIndepth[63660:1061569] -[ViewController threadTask:]
2020- 03- 12 16:19:36.751990+0800 PerformSelectorIndepth[63660:1061569] <NSThread: 0x6000027a0ac0>{number = 6, name = (null)}
Copy the code

As we can see from the output, it is clear that a thread has been opened to execute the task. Let’s look at the underlying implementation of GNUStep:

- (void) performSelectorInBackground: (SEL)aSelector
                          withObject: (id)anObject
{
    [NSThread detachNewThreadSelector: aSelector
                             toTarget: self
                           withObject: anObject];
}
Copy the code

You can see that underneath it is the class method of the NSThread that is called to perform the incoming task. We’ll explore the details of NSThreads later.


3.2 summary

  • performSelector:onThread:withObject:waitUntilDone:performSelector:onThread:withObject:waitUntilDone:modes:
    • Determines whether to block the current thread and fire the given task at the beginning of the next Runloop message loop when the thread’s runloop is in any given mode.
  • performSelectorOnMainThread:withObject:waitUntilDone:performSelectorOnMainThread:withObject:waitUntilDone:modes:
    • Determines whether to block the main thread when its runloop is in any given mode and fires the given task at the beginning of the next Runloop message loop.
  • performSelectorInBackground:withObject:
    • Performs a given task on a child thread. The bottom layer is throughNSThreaddetachNewThreadThe implementation.

Four,