iOS 查漏補(bǔ)缺 - PerformSelector

performSelector 系列的函數(shù)我們都不陌生付鹿,但是對(duì)于它不同的變種以及底層原理在很多時(shí)候還是容易分不清楚牵触,所以筆者希望通過 runtime 源碼以及 GUNStep 源碼來一個(gè)個(gè)抽絲剝繭跋炕,把不同變種的 performSelector 理順,并搞清楚每個(gè)方法的底層實(shí)現(xiàn)膳沽,如有錯(cuò)誤崔列,歡迎指正。

本文的代碼已放在 Github 屁桑,歡迎自取

一医寿、NSObject 下的 PerformSelector

1.1 performSelector:(SEL)aSelector

performSelector 方法是最簡(jiǎn)單的一個(gè) api,使用方法如下

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

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

// 輸出
2020-03-12 11:13:26.321254+0800 PerformSelectorIndepth[61807:828757] -[ViewController task]

performSelector: 方法只需要傳入一個(gè) SEL蘑斧,在 runtime 底層實(shí)現(xiàn)為:

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

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

performSelector:withObject: 方法相比于上一個(gè)方法多了一個(gè)參數(shù)靖秩,使用起來如下:

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

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

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

performSelector:withObject: 方法底層實(shí)現(xiàn)如下:

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

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

這個(gè)方法相比上一個(gè)方法又多了一個(gè)參數(shù):

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

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

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

performSelector:withObject:withObject: 方法底層實(shí)現(xiàn)如下:

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

1.4 小結(jié)

方法 底層實(shí)現(xiàn)
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)

這三個(gè)方法應(yīng)該是使用頻率很高的 performSelector 系列方法了,我們只需要記住這三個(gè)方法在底層都是執(zhí)行的消息發(fā)送即可竖瘾。

二沟突、Runloop 相關(guān)的 PerformSelector

image

如上圖所示,在 NSRunLoop 頭文件中捕传,定義了兩個(gè)的分類惠拭,分別是

  • NSDelayedPerforming 對(duì)應(yīng)于 NSObject
  • NSOrderedPerform 對(duì)應(yīng)于 NSRunLoop

2.1 NSObject 分類 NSDelayedPerforming

2.1.1 performSelector:WithObject:afterDelay:

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

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

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

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

這個(gè)方法會(huì)在當(dāng)前線程所對(duì)應(yīng)的 runloop 中設(shè)置一個(gè)定時(shí)器來執(zhí)行傳入的 SEL。定時(shí)器需要在 NSDefaultRunLoopMode 模式下才會(huì)被觸發(fā)庸论。當(dāng)定時(shí)器啟動(dòng)后职辅,線程會(huì)嘗試從 runloop 中取出 SEL 然后執(zhí)行。
如果 runloop 已經(jīng)啟動(dòng)并且處于 NSDefaultRunLoopMode 的話聂示,SEL 執(zhí)行成功域携。否則,直到 runloop 處于 NSDefaultRunLoopMode 前催什,timer 都會(huì)一直等待

通過斷點(diǎn)調(diào)試如下圖所示,runloop 底層最終是通過 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ ()來觸發(fā)任務(wù)的執(zhí)行。

image

因?yàn)?NSRunLoop 并沒有開源蒲凶,所以我們只能通過 GNUStep 來窺探底層實(shí)現(xiàn)細(xì)節(jié)气筋,如下所示:

- (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

我們可以看到,在 performSelector:WithObject:afterDelay: 底層

  • 獲取當(dāng)前線程的 NSRunLoop 對(duì)象旋圆。
  • 通過傳入的 SEL 宠默、argumentdelay 初始化一個(gè) GSTimedPerformer 實(shí)例對(duì)象,GSTimedPerformer 類型里面封裝了 NSTimer 對(duì)象灵巧。
  • 然后把 GSTimedPerformer 實(shí)例加入到 RunLoop 對(duì)象的 _timedPerformers 成員變量中
  • 釋放掉 GSTimedPerformer 對(duì)象
  • default modetimer 對(duì)象加入到 runloop

2.1.2 performSelector:WithObject:afterDelay:inModes

performSelector:WithObject:afterDelay:inModes 方法相比上個(gè)方法多了一個(gè) modes 參數(shù)搀矫,根據(jù)官方文檔的定義,只有當(dāng) runloop 處于 modes 中的任意一個(gè) mode 時(shí)刻肄,才會(huì)執(zhí)行任務(wù)瓤球,如果 modes 為空,那么將不會(huì)執(zhí)行任務(wù)敏弃。

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

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

// 打印如下
2020-03-12 11:38:58.479152+0800 PerformSelectorIndepth[62006:851520] -[ViewController taskWithParam:]
2020-03-12 11:38:58.479350+0800 PerformSelectorIndepth[62006:851520] {
    param = leejunhui;
}

這里我們?nèi)绻?modes 參數(shù)改為 UITrackingRunLoopMode卦羡,那么就只有在 scrollView 發(fā)生滾動(dòng)的時(shí)候才會(huì)觸發(fā) timer

我們?cè)倏匆幌?GNUStep 對(duì)應(yīng)的實(shí)現(xiàn):

- (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

可以看到,相比于上一個(gè)方法的底層實(shí)現(xiàn)不同的是麦到,這里會(huì)循環(huán)添加不同 modetimer 對(duì)象到 runloop 中绿饵。

2.1.3 cancelPreviousPerformRequestsWithTarget:cancelPreviousPerformRequestsWithTarget:selector:object:

cancelPreviousPerformRequestsWithTarget: 方法和 cancelPreviousPerformRequestsWithTarget:selector:object: 方法是兩個(gè)類方法,它們的作用是取消執(zhí)行之前通過 performSelector:WithObject:afterDelay: 方法注冊(cè)的任務(wù)瓶颠。使用起來如下所示:

- (void)jh_performSelectorwithObjectafterDelayInModes
{
    // 只有當(dāng) scrollView 發(fā)生滾動(dòng)時(shí)拟赊,才會(huì)觸發(fā)timer
//    [self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:1.f inModes:@[UITrackingRunLoopMode]];
    
    [self performSelector:@selector(taskWithParam:) withObject:@{@"param": @"leejunhui"} afterDelay:5.f inModes:@[NSRunLoopCommonModes]];
}

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

// 輸出
2020-03-12 11:52:33.549213+0800 PerformSelectorIndepth[62172:865289] -[ViewController cancelTask]

這里有一個(gè)區(qū)別,就是 cancelPreviousPerformRequestsWithTarget: 類方法會(huì)取消掉 target 上所有的通過 performSelector:WithObject:afterDelay: 實(shí)例方法注冊(cè)的定時(shí)任務(wù)粹淋,而 cancelPreviousPerformRequestsWithTarget:selector:object: 只會(huì)通過傳入的 SEL 取消匹配到的定時(shí)任務(wù)

GNUStepcancelPreviousPerformRequestsWithTarget: 方法底層實(shí)現(xiàn)如下:

/*
 * 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 實(shí)例方法
- (void) invalidate
{
    if (timer != nil)
    {
        [timer invalidate];
        DESTROY(timer);
    }
}

這里的邏輯其實(shí)很清晰:

  • 取出當(dāng)前 runloop 對(duì)象的成員變量 _timedPerformers
  • 判斷定時(shí)任務(wù)數(shù)組是否為空吸祟,不為空才會(huì)繼續(xù)往下走
  • 初始化一個(gè)局部的空的任務(wù)數(shù)組,然后通過 getObjects 從成員變量中取出任務(wù)
  • 通過 while 循環(huán)遍歷所有的任務(wù)廓啊,如果匹配到了對(duì)應(yīng)的 target欢搜,則調(diào)用任務(wù)的 invalidate 方法,在這個(gè)方法內(nèi)部會(huì)把定時(shí)器停掉然后銷毀谴轮。接著還需要把成員變量 _timedPerformers 中對(duì)應(yīng)的任務(wù)移除掉

另一個(gè)取消任務(wù)的方法底層實(shí)現(xiàn)如下:

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

這里的實(shí)現(xiàn)不一樣的地方就是除了判斷 target 是否匹配外炒瘟,還會(huì)判斷 SEL 是否匹配,以及參數(shù)是否匹配第步。

2.1.4 小結(jié)

  • performSelector:WithObject:afterDelay:
    • 在該方法所在線程的 runloop 處于 default mode 時(shí)疮装,根據(jù)給定的時(shí)間觸發(fā)給定的任務(wù)。底層原理是把一個(gè) timer 對(duì)象以 default mode 加入到 runloop 對(duì)象中粘都,等待喚醒廓推。
  • performSelector:WithObject:afterDelay:inModes:
    • 在該方法所在線程的 runloop 處于給定的任一 mode 時(shí),根據(jù)給定的時(shí)間觸發(fā)給定的任務(wù)翩隧。底層原理是循環(huán)把一個(gè) timer 對(duì)象以給定的 mode 加入到 runloop 對(duì)象中樊展,等待喚醒。
  • cancelPreviousPerformRequestsWithTarget:
    • 取消 target 對(duì)象通過 performSelector:WithObject:afterDelay: 方法或 performSelector:WithObject:afterDelay:inModes: 方法注冊(cè)的所有定時(shí)任務(wù)
  • cancelPreviousPerformRequestsWithTarget:selector:object:
    • 取消 target 對(duì)象通過 performSelector:WithObject:afterDelay: 方法或 performSelector:WithObject:afterDelay:inModes: 方法注冊(cè)的指定的定時(shí)任務(wù)

這四個(gè)方法是作為 NSObjectNSDelayedPerforming 分類存在于 NSRunLoop 源代碼中,所以我們?cè)谑褂玫臅r(shí)候要注意一個(gè)細(xì)節(jié)专缠,那就是執(zhí)行這些方法的線程是否是主線程雷酪,如果是主線程,那么執(zhí)行起來是沒有問題的涝婉,但是哥力,如果是在子線程中執(zhí)行這些方法,則需要開啟子線程對(duì)應(yīng)的 runloop 才能保證執(zhí)行成功墩弯。

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


// 沒有輸出

如上所示的代碼吩跋,通過 GCD 的異步執(zhí)行函數(shù)在全局并發(fā)隊(duì)列上執(zhí)行任務(wù),并沒有任何打印輸出渔工,我們加入 runloop 的啟動(dòng)代碼后結(jié)果將完全不一樣:

image

對(duì)于 performSelector:WithObject:afterDelay:inModes 方法锌钮,如果遇到這樣的情況,也是一樣的解決方案涨缚。

2.2 NSRunLoop 的分類 NSOrderedPerform

2.2.1 performSelector:target:argument:order:modes:

performSelector:target:argument:order:modes: 方法的調(diào)用者是 NSRunLoop 實(shí)例轧粟,然后需要傳入要執(zhí)行的 SEL,以及 SEL 對(duì)應(yīng)的 target脓魏,和 SEL 要接收的參數(shù) argument兰吟,最后是此次任務(wù)的優(yōu)先級(jí) order,以及一個(gè) 運(yùn)行模式集合 modes茂翔,目的是當(dāng) runloopcurrentMode 處于這個(gè)運(yùn)行模式集合中的其中任意一個(gè) mode 時(shí)混蔼,就會(huì)按照優(yōu)先級(jí) order 來觸發(fā) SEL 的執(zhí)行。具體使用如下:

- (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 任務(wù)1");
}

- (void)runloopTask2
{
    NSLog(@"runloop 任務(wù)2");
}

- (void)runloopTask3
{
    NSLog(@"runloop 任務(wù)3");
}

- (void)runloopTask4
{
    NSLog(@"runloop 任務(wù)4");
}

- (void)runloopTask5
{
    NSLog(@"runloop 任務(wù)5");
}

// 輸出
2020-03-12 14:23:27.088636+0800 PerformSelectorIndepth[62976:972980] runloop 任務(wù)1
2020-03-12 14:23:27.088760+0800 PerformSelectorIndepth[62976:972980] runloop 任務(wù)2
2020-03-12 14:23:27.088868+0800 PerformSelectorIndepth[62976:972980] runloop 任務(wù)3
2020-03-12 14:23:27.088964+0800 PerformSelectorIndepth[62976:972980] runloop 任務(wù)4
2020-03-12 14:23:27.089048+0800 PerformSelectorIndepth[62976:972980] runloop 任務(wù)5

可以看到輸出結(jié)果就是按照我們傳入的 order 參數(shù)作為任務(wù)執(zhí)行的順序珊燎。

GUNStep 中這個(gè)底層的底層實(shí)現(xiàn)如下:

- (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;
}

我們已經(jīng)知道了 performSelector:WithObject:afterDelay: 方法底層實(shí)現(xiàn)使用一個(gè)包裹 timer 對(duì)象的數(shù)據(jù)結(jié)構(gòu)的方式惭嚣,而這里是使用了一個(gè)包裹了 selectortarget悔政、argument 以及優(yōu)先級(jí) order 的數(shù)據(jù)結(jié)構(gòu)的方式來實(shí)現(xiàn)晚吞。同時(shí)在 context 上下文的成員變量 performers 中存儲(chǔ)了要執(zhí)行的任務(wù)隊(duì)列,所以這里實(shí)際上就是一個(gè)簡(jiǎn)單的插入排序的過程谋国。

2.2.2 cancelPerformSelector:target:argument:cancelPerformSelectorsWithTarget:

cancelPerformSelector:target:argument:cancelPerformSelectorsWithTarget: 使用起來比較簡(jiǎn)單槽地,一個(gè)需要傳入 selectortargetargument芦瘾,另一個(gè)只需要傳入 target捌蚊。它們的作用分別是根據(jù)給定的三個(gè)參數(shù)或 target 去 runloop 底層的 performers 任務(wù)隊(duì)列中查找任務(wù),找到了就從隊(duì)列中移除掉近弟。

而底層具體實(shí)現(xiàn)具體如下:

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

/**
 * Cancels any perform operations set up for the specified target
 * in the receiver, but only if the value of aSelector and argument
 * with which the performs were set up match those supplied.<br />
 * Matching of the argument may be either by pointer equality or by
 * use of the [NSObject-isEqual:] method.
 */
- (void) cancelPerformSelector: (SEL)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);
}

2.2.3 小結(jié)

  • performSelector:target:argument:order:modes:
    • 在該方法所在線程的 runloop 處于給定的任一 mode 時(shí)缅糟,且處于下一次 runloop 消息循環(huán)的開頭的時(shí)候觸發(fā)給定的任務(wù)。底層原理是循環(huán)把一個(gè)類似于 timer 的對(duì)象加入到 runloop 的上下文的任務(wù)隊(duì)列中祷愉,等待喚醒
  • cancelPerformSelector:target:argument:
    • 取消 target 對(duì)象通過 performSelector:target:argument:order:modes: 方法方法注冊(cè)的指定的任務(wù)
  • cancelPerformSelectorsWithTarget:
    • 取消 target 對(duì)象通過 performSelector:target:argument:order:modes: 方法方法注冊(cè)的所有任務(wù)

這里同樣的也需要注意窗宦,如果是在子線程中執(zhí)行這些方法赦颇,則需要開啟子線程對(duì)應(yīng)的 runloop 才能保證執(zhí)行成功

三赴涵、Thread 相關(guān)的 performSelector

image

如上圖所示沐扳,在 NSThread 中定義了 NSObject 的分類 NSThreadPerformAdditions,其中定義了 5 個(gè) performSelector 的方法句占。

3.1 performSelector:onThread:withObject:waitUntilDone:performSelector:onThread:withObject:waitUntilDone:modes:

根據(jù)官方文檔的解釋,第一個(gè)方法相當(dāng)于調(diào)用了第二個(gè)方法躯嫉,然后 mode 傳入的是 kCFRunLoopCommonModes纱烘。我們這里只研究第一個(gè)方法。

這個(gè)方法需要相比于 performSeletor:withObject: 多了兩個(gè)參數(shù)祈餐,分別是要哪個(gè)線程執(zhí)行任務(wù)以及是否阻塞當(dāng)前線程擂啥。但是使用這個(gè)方法一定要小心,如下圖所示是一個(gè)常見的錯(cuò)誤用法:

image

這里報(bào)的錯(cuò)是 target thread exited while waiting for the perform帆阳,就是說已經(jīng)退出的線程無法執(zhí)行定時(shí)任務(wù)哺壶。
熟悉 iOS 多線程的同學(xué)都知道 NSThread 實(shí)例化之后的線程對(duì)象在 start 之后就會(huì)被系統(tǒng)回收,而之后調(diào)用的 performSelector:onThread:withObject:waitUntilDone: 方法又在一個(gè)已經(jīng)回收的線程上執(zhí)行任務(wù)蜒谤,顯然就會(huì)崩潰山宾。這里的解決方案就是給這個(gè)子線程對(duì)應(yīng)的 runloop 啟動(dòng)起來,讓線程具有 『有事來就干活鳍徽,沒事干就睡覺』 的功能资锰,具體代碼如下:

image

對(duì)于 waitUntilDone 參數(shù),如果我們?cè)O(shè)置為 YES:

image

如果設(shè)置為 NO:

image

所以這里的 waitUntilDone 可以簡(jiǎn)單的理解為控制同步或異步執(zhí)行阶祭。

在探索 GNUStep 對(duì)應(yīng)實(shí)現(xiàn)之前绷杜,我們先熟悉一下 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  
}

GSRunLoopThreadInfo 是每個(gè)線程特有的一個(gè)屬性,存儲(chǔ)了線程和 runloop 之間的一些信息濒募,可以通過下面的方式獲取:

GSRunLoopThreadInfo *
GSRunLoopInfoForThread(NSThread *aThread)
{
    GSRunLoopThreadInfo   *info;
    
    if (aThread == nil)
    {
        aThread = GSCurrentThread();
    }
    if (aThread->_runLoopInfo == nil)
    {
        [gnustep_global_lock lock];
        if (aThread->_runLoopInfo == nil)
        {
            aThread->_runLoopInfo = [GSRunLoopThreadInfo new];
        }
        [gnustep_global_lock unlock];
    }
    info = aThread->_runLoopInfo;
    return info;
}

然后是另一個(gè) GSPerformHolder:

/**
 * This class performs a dual function ...
 * <p>
 *   As a class, it is responsible for handling incoming events from
 *   the main runloop on a special inputFd.  This consumes any bytes
 *   written to wake the main runloop.<br />
 *   During initialisation, the default runloop is set up to watch
 *   for data arriving on inputFd.
 * </p>
 * <p>
 *   As instances, each  instance retains perform receiver and argument
 *   values as long as they are needed, and handles locking to support
 *   methods which want to block until an action has been performed.
 * </p>
 * <p>
 *   The initialize method of this class is called before any new threads
 *   run.
 * </p>
 */
@interface GSPerformHolder : NSObject
{
    id          receiver;
    id          argument;
    SEL         selector;
    NSConditionLock *lock;      // Not retained.
    NSArray     *modes;
    BOOL                  invalidated;
@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

GSPerformHolder 封裝了任務(wù)的細(xì)節(jié)(receiver, argument, selector)以及運(yùn)行模式(mode)和一把條件鎖( NSConditionLock )鞭盟。

接著我們目光聚焦到源碼 performSelector:onThread:withObject:waitUntilDone:modes: 具體實(shí)現(xiàn)上:

- (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);
    }
}
  • 聲明一個(gè)GSRunLoopThreadInfo 對(duì)象和一條 NSThread 線程
  • 判斷運(yùn)行模式數(shù)組參數(shù)是否為空
  • 獲取當(dāng)前線程,將結(jié)果賦值于第一步聲明的局部線程變量
  • 判斷如果傳入的要執(zhí)行任務(wù)的線程 aThread 如果為空瑰剃,那么就把當(dāng)前線程賦值于到 aThread 上
  • 確保 aThread 不為空之后獲取該線程對(duì)應(yīng)的 GSRunLoopThreadInfo 對(duì)象并賦值于第一步聲明的局部 info 變量
  • 確保 info 有值后齿诉,判斷是否是在當(dāng)前線程上執(zhí)行任務(wù)
  • 如果是在當(dāng)前線程上執(zhí)行任務(wù),接著判斷是否要阻塞當(dāng)前線程培他,或當(dāng)前線程的 runloop 為空鹃两。
    • 如果是的話,則直接調(diào)用 performSelector:withObject 來執(zhí)行任務(wù)
    • 如果不是的話舀凛,則通過線程對(duì)應(yīng)的 runloop 對(duì)象調(diào)用 performSelector:target:argument:order:modes: 來執(zhí)行任務(wù)
  • 如果不是在當(dāng)前線程上執(zhí)行任務(wù)俊扳,聲明一個(gè) GSPerformHolder 局部變量,聲明一把空的條件鎖 NSConditionLock
    • 判斷要執(zhí)行任務(wù)的線程是否已經(jīng)被回收猛遍,如果已被回收馋记,則拋出異常
    • 如果未被回收
      • 判斷是否要阻塞當(dāng)前線程号坡,如果傳入的參數(shù)需要阻塞,則初始化條件鎖
      • 根據(jù)傳入的參數(shù)及條件鎖初始化 GSPerformHolder 實(shí)例
      • 然后在 info 中加入 GSPerformHolder 實(shí)例
      • 然后判斷條件鎖如果不為空梯醒,賦予條件鎖何時(shí)加鎖的條件宽堆,然后解鎖條件鎖,然后釋放條件鎖
      • 判斷 GSPerformHolder 局部變量是否已經(jīng)被釋放茸习,如果沒有被釋放畜隶,拋出異常

3.2 performSelectorOnMainThread:withObject:waitUntilDone:performSelectorOnMainThread:withObject:waitUntilDone:modes:

顧名思義,這兩個(gè)方法其實(shí)就是在主線程上執(zhí)行任務(wù)号胚,根據(jù)傳入的參數(shù)決定是否阻塞主線程籽慢,以及在哪些運(yùn)行模式下執(zhí)行任務(wù)。使用方法如下:

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

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

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

因?yàn)槭窃谥骶€程上執(zhí)行猫胁,所以并不需要手動(dòng)開啟 runloop箱亿。我們來看下這兩個(gè)方法在 GNUStep 中底層實(shí)現(xiàn):

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

不難看出,這里其實(shí)就是調(diào)用的 performSelector:onThread:withObject:waitUntilDone:modes 方法弃秆,但是有一個(gè)細(xì)節(jié)需要注意届惋,就是有可能在 NSThread 類被初始化之前,就調(diào)用了 performSelectorOnMainThread 方法菠赚,所以需要手動(dòng)調(diào)用一下 [NSThread currentThread]脑豹。

3.3 performSelectorInBackground:withObject:

最后要探索的是 performSelectorInBackground:withObject: 方法,這個(gè)方法用法如下:

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

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

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

根據(jù)輸出我們可知衡查,這里顯然是開了一條子線程來執(zhí)行任務(wù)晨缴,我們看一下 GNUStep 的底層實(shí)現(xiàn):

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

可以看到在底層其實(shí)是調(diào)用的 NSThread 的類方法來執(zhí)行傳入的任務(wù)。關(guān)于 NSThread 細(xì)節(jié)我們后面會(huì)進(jìn)行探索峡捡。

3.4 小結(jié)

  • performSelector:onThread:withObject:waitUntilDone:performSelector:onThread:withObject:waitUntilDone:modes:
    • 在該方法所在線程的 runloop 處于給定的任一 mode 時(shí)击碗,判斷是否阻塞當(dāng)前線程,并且處于下一次 runloop 消息循環(huán)的開頭的時(shí)候觸發(fā)給定的任務(wù)们拙。
  • performSelectorOnMainThread:withObject:waitUntilDone:performSelectorOnMainThread:withObject:waitUntilDone:modes:
    • 當(dāng)主線程的 runloop 處于給定的任一 mode 時(shí)稍途,判斷是否阻塞主線程,并且處于下一次 runloop 消息循環(huán)的開頭的時(shí)候觸發(fā)給定的任務(wù)砚婆。
  • performSelectorInBackground:withObject:
    • 在子線程上執(zhí)行給定的任務(wù)械拍。底層是通過 NSThreaddetachNewThread 實(shí)現(xiàn)。

四装盯、總結(jié)

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坷虑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子埂奈,更是在濱河造成了極大的恐慌迄损,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件账磺,死亡現(xiàn)場(chǎng)離奇詭異芹敌,居然都是意外死亡痊远,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門氏捞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碧聪,“玉大人,你說我怎么就攤上這事液茎〕炎耍” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵捆等,是天一觀的道長(zhǎng)哼凯。 經(jīng)常有香客問我,道長(zhǎng)楚里,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任猎贴,我火速辦了婚禮班缎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘她渴。我一直安慰自己达址,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布趁耗。 她就那樣靜靜地躺著沉唠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苛败。 梳的紋絲不亂的頭發(fā)上满葛,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音罢屈,去河邊找鬼嘀韧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛缠捌,可吹牛的內(nèi)容都是我干的锄贷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼曼月,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼谊却!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起哑芹,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤炎辨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后聪姿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹦魔,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡激率,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勿决。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乒躺。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖低缩,靈堂內(nèi)的尸體忽然破棺而出嘉冒,到底是詐尸還是另有隱情,我是刑警寧澤咆繁,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布讳推,位于F島的核電站,受9級(jí)特大地震影響玩般,放射性物質(zhì)發(fā)生泄漏银觅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一坏为、第九天 我趴在偏房一處隱蔽的房頂上張望究驴。 院中可真熱鬧,春花似錦匀伏、人聲如沸洒忧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)熙侍。三九已至,卻和暖如春履磨,著一層夾襖步出監(jiān)牢的瞬間蛉抓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工剃诅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芝雪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓综苔,卻偏偏與公主長(zhǎng)得像惩系,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子如筛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 伸出我的雙手堡牡, 用它們蹈探火苗。 添一把青枝綠葉杨刨, 剝奪花開的權(quán)利晤柄。 濃煙滾滾而上, 望著近在咫尺的灰燼 佇立妖胀。 ...
    詩(shī)辰閱讀 157評(píng)論 0 1
  • 親愛的七色書香園的家人們: 大家好芥颈!惜別幸福的寒假惠勒,我們迎來了新的學(xué)期。新的征途爬坑,新的希望纠屋,讓我用這第一封...
    k麗霞閱讀 320評(píng)論 0 2
  • 在以前的社會(huì)中售担,相隔兩地的戀人傳達(dá)思念往往是靠書信郵寄,雖然很慢署辉,但很安心族铆。而如今,隨著網(wǎng)絡(luò)和通信設(shè)備的發(fā)達(dá)哭尝,傳遞...
    追逐繁星的貓閱讀 318評(píng)論 0 0
  • 五十肩白天疼哥攘,晚上更疼,有的人甚至因持續(xù)疼痛而失眠材鹦!這是為啥呢逝淹? 一、炎癥代謝產(chǎn)物-夜晚聚集 肩周炎的疼痛侠姑,一方面...
    葆和堂閱讀 65評(píng)論 0 2
  • 備注:以上文字介紹來自形色app,特此說明箩做。以下圖片為本人用手機(jī)拍的莽红,為原創(chuàng)攝影。
    高中數(shù)學(xué)夢(mèng)覺閱讀 171評(píng)論 0 1