iOS performSelector相关操作

今天遇到一个问题,先来给各位看官看一下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

[self performSelector:@selector(backGroundThread) onThread:[NSThread currentThread] withObject:nil waitUntilDone:NO];
    NSLog(@"hello world 1");
});

- (void)backGroundThread{
NSLog(@"—hello world2—");
}

问:这段代码会打印什么?

可能很多人会开始猜了。这里先卖个关子,带着你的疑问继续看下去吧。

首先,总体来看一下苹果提供的performSelector系列的API

@interface NSObject (NSThreadPerformAdditions)

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

/****************   Delayed perform  ******************/

@interface NSObject (NSDelayedPerforming)

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

@end

可以看到,这几个是苹果提供的常用的多线程事件相关操作。用这几个方法,我们可以在子线程处理某些方法、拉回到主线程做一些操作,在当前线程延迟多久做一些操作等等。
performSelectorInBackground这个就不说了。

今天主要来研究下带有waitUntilDone参数的方法,即performSelectorOnMainThread: withObject: waitUntilDone:performSelector: onThread: withObject: waitUntilDone:两个方法:
先来看下官方是怎么解释waitUntilDone的wait这个参数的:

wait
A Boolean that specifies whether the current thread blocks until after the specified selector is performed on the receiver on the main thread. 
Specify YES to block this thread; otherwise, specify NO to have this method return immediately.
If the current thread is also the main thread, and you specify YES for this parameter, the message is delivered and processed immediately.

可以看到,这个参数是个bool类型,如果为YES,则会阻塞当前线程直到指定的方法执行完成才返回。
如果为NO, 则会立即返回。

这时如果把上面例子中的代码改为这样:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    [self performSelector:@selector(backGroundThread) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES];
     NSLog(@"hello world 1");
});

- (void)backGroundThread{
NSLog(@"—hello world2—");
}

此时结果是什么?
相信大家都能看出来,肯定是 先打印hello world 2,然后再打印hello world1的.
然后回归到最上面的问题。
结果是什么呢?
好奇的小伙伴们可以项目中跑一下看看:
其实结果是:只有hello world 1

2019-07-17 14:24:38.417458+0800 RunloopDemo[4298:394797] hello world 1

这是为什么呢?为什么performSelector要执行的方法没有走呢?
这个瞎猜是没用的, 我们只能去查苹果是怎么解释performSelector:onThread:withObject:waitUntilDone:的了:

## Discussion

You can use this method to deliver messages to other threads in your application. 
The message in this case is a method of the current object that you want to execute on the target thread. 

This method queues the message on the run loop of the target thread using the default run loop modes—that is, 
the modes associated with the [NSRunLoopCommonModes]  constant. 
As part of its normal run loop processing, the target thread dequeues the message (assuming it is running in one of the default run loop modes) and invokes the desired method.

You cannot cancel messages queued using this method. If you want the option of canceling a message on the current thread, 
you must use either the [performSelector:withObject:afterDelay:] or [performSelector:withObject:afterDelay:inModes:] method.

### Special Considerations

This method registers with the runloop of its current context, and depends on that runloop being run on a regular basis to perform correctly. 
One common context where you might call this method and end up registering with a runloop that is not automatically run on a regular basis is when being invoked by a dispatch queue. 
If you need this type of functionality when running on a dispatch queue, you should use [dispatch_after] and related methods to get the behavior you want.

可以看到:
此方法是为了把当前线程的对象传递给别的线程的,此方法会被加入到目标线程的runloop队列中,该runloop使用默认mode--NSRunLoopCommonModes。当该线程的runloop执行的时候,它会以此出队列,然后执行想要执行的方法。
由此可以得出结论,此方法是依赖于线程的runloop的
而上面例子中,我们使用了dispatch_async创建了一个子线程,我们知道,子线程的runloop是默认不启动的,因此,我们添加对应的方法即可。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
   
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        [self performSelector:@selector(backGroundThread) onThread:[NSThread currentThread] withObject:nil waitUntilDone:NO];
        
        NSLog(@"hello world 1");
        
        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
        [currentRunLoop run];
    });
}

- (void)backGroundThread{
    NSLog(@"—hello world2—");
}

果然,此时再打印,结果就正常了:

2019-07-17 14:42:18.784324+0800 RunloopDemo[4477:428656] hello world 1
2019-07-17 14:42:18.784650+0800 RunloopDemo[4477:428656] —hello world2—

但是,经过一系列的操作,我发现

        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
        [currentRunLoop run];

两行代码放的位置,也是有讲究的。



一共1、2、3三种情况,结果又不尽相同。

  1. 假如放在1️⃣的位置,结果如何呢?又变成只有hello world 1了
2019-07-17 14:47:44.859178+0800 RunloopDemo[4549:445631] hello world 1
  1. 2️⃣的位置呢?只有hello world 2了
2019-07-17 14:48:29.820267+0800 RunloopDemo[4566:448005] —hello world2—
  1. 位置3️⃣,结果就是上面那样,是正常的

是不是很奇怪呢?
因此再来看下[[NSRunLoop currentRunLoop] run]方法苹果是怎么解释的了。

### Discussion

If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the `NSDefaultRunLoopMode`by repeatedly invoking [runMode:beforeDate:].
In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers. 

Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. 
macOS can install and remove additional input sources as needed to process requests targeted at the receiver’s thread. 
Those sources could therefore prevent the run loop from exiting. 

If you want the run loop to terminate, you shouldn't use this method. 
Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:


BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

where `shouldKeepRunning` is set to `NO`somewhere else in the program.

同样可以看到,RunLoop的run方法,在有输入源或者定时器的情况下,是重复的调用runMode:beforeDate:方法,换句话说,他是一个无限的循环,而且手动移除输入源和定时器并不能保证runloop会退出。所以苹果建议我们使用runMode:beforeDate:,并且给了下面一个标准的写法。
所以,我们上面的代码就可以改为:

        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
        //[currentRunLoop run];
        [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

此时,放置在2️⃣、3️⃣位置都是正常运行的了。

那位置1️⃣呢?仔细想想也是可以理解的,
因为run方法中说了,如果输入源或定时器都没有的情况下,runloop是直接退出的。
在位置1️⃣的时候开启runloop,此时并没有输入源加入, 所以此时runloop直接就退出了。
(performSelector:onThread:withObject:waitUntilDone: 方法会把方法作为输入源添加到runloop中),
所以2️⃣、3️⃣位置的时候,就不会出现此问题。

那再来看下拉回到主线程的方法

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

因为主线程的Runloop是默认开启的,所以不需要我们来处理。
把最上方中方法改为performSelectorOnMainThread,然后waitUntilDone参数为YES的时候,结果如下:

2019-07-17 15:05:58.550527+0800 RunloopDemo[4714:483346] —hello world2—
2019-07-17 15:05:58.550725+0800 RunloopDemo[4714:483400] hello world 1

waitUntilDone参数为NO的时候,结果如下:

2019-07-17 15:06:52.652589+0800 RunloopDemo[4736:485901] hello world 1
2019-07-17 15:06:52.661368+0800 RunloopDemo[4736:485839] —hello world2—

可以看出,此参数大致可以按“同步”、“异步”的方式来理解。

因此,可以得出结论!!!!

在方法- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait中, 执行方法的时候,是把输入源添加到对应线程的RunLoop中的,但是RunLoop此时并没有启动,所以方法不用调用。话句话说:方法的调用顺序取决于RunLoop的启动时机,参照- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;方法的结果,我们可以知道,RunLoop的启动时机应该是在当前线程调用方法作用域的最后位置。

最终代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
   
//    [self threadInfo:@"UI THREAD"];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//        [self threadInfo:@"dispatch async"];

//        [self performSelector:@selector(backGroundThread) onThread:[NSThread currentThread] withObject:nil waitUntilDone:NO];
        [self performSelectorOnMainThread:@selector(backGroundThread) withObject:nil waitUntilDone:NO];
//        [self performSelectorInBackground:@selector(backGroundThread) withObject:nil];
//        [self performSelector:@selector(backGroundThread) withObject:nil afterDelay:2 inModes:@[NSDefaultRunLoopMode]];
//        [self performSelector:@selector(backGroundThread) withObject:nil afterDelay:(2)];
        
        NSLog(@"hello world 1");
        
        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
        //[currentRunLoop run];
        [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
}

- (void)backGroundThread{
    NSLog(@"—hello world2—");
}

举一反三

苹果提供的别的API, 诸如

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

假如他们也是在子线程中调用的话,我们同样也是需要手动开启runloop的。

以上。END

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 翻译来源: RunLoops Run Loops RunLoops是与线程紧密相关的基础架构的一部分,简称运行循环...
    AlexCorleone阅读 563评论 0 1
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,089评论 1 32
  • ======================= 前言 RunLoop 是 iOS 和 OSX 开发中非常基础的一个...
    i憬铭阅读 867评论 0 4
  • 1.设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类...
    司马DE晴空阅读 1,278评论 0 7
  • 来源:击水湘江 链接://www.greatytc.com/p/536184bfd163 实例化讲解Run...
    __Lex阅读 269评论 0 2