iOS执行器performSelector详解

栏目: IOS · 发布时间: 7年前

内容简介:performSelector(方法执行器),iOS中提供了如下几种常用的调用方式performSelector响应Objective-C动态性,将方法的绑定延迟到运行时,因此编译阶段不会检测方法有效性,即方法不存在也不会提示报错。反之因为此特性,performSelector也广泛用于动态化和组件化的模块中。如果方法名称也是动态不确定的,会提示如下警告:

performSelector(方法执行器),iOS中提供了如下几种常用的调用方式

[self performSelector:@selector(sureTestMethod)];
[self performSelector:@selector(sureTestMethod)
           withObject:params];
[self performSelector:@selector(sureTestMethod)
           withObject:params
           withObject:params2];
......
复制代码

performSelector响应Objective-C动态性,将方法的绑定延迟到运行时,因此编译阶段不会检测方法有效性,即方法不存在也不会提示报错。反之因为此特性,performSelector也广泛用于动态化和组件化的模块中。

如果方法名称也是动态不确定的,会提示如下警告:

SEL selector = @selector(dynamicMethod);
[self performSelector:selector];
复制代码
:warning: PerformSelector may cause a leak because its selector is unknown
复制代码

意为因为当前方法名未知可能会引起内存泄露相关问题。 可以通过如下代码忽略此警告

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector:selector];
#pragma clang diagnostic pop
复制代码

performSelector默认最多只可传递两个参数,若需多参可将参数封装为NSArray、NSDictionary、NSInvocation进行传递。另外方法调用本质都是消息机制,也可以通过msg_send实现。

id params;
id params2;
id params3;

SEL selector = NSSelectorFromString(@"sureTestMethod:params2:params3:");
objc_msgSend(self, selector,params,params2,params3);

- (void)sureTestMethod:(id)params params2:(id)params2 params3:(id)params3 {
    NSLog(@"sureTestMethod-multi-parameter");
}
复制代码

2、performSelector延迟调用

[self performSelector:@selector(sureTestMethod:)
           withObject:params
           afterDelay:3];
复制代码

此方法意为在当前Runloop中延迟3秒后执行selector中方法。 使用该方法需要注意以下事项: 在子线程中调用performSelector: withObject: afterDelay:默认无效,如下代码并不会打印sureTestMethodCall

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [self performSelector:@selector(sureTestMethod:)
               withObject:params
               afterDelay:3];
});
- (void)sureTestMethod:(id)objcet {
    NSLog(@"sureTestMethodCall");
}
复制代码

这是因为performSelector: withObject: afterDelay:是在当前Runloop中延时执行的,而子线程的Runloop默认不开启,因此无法响应方法。

所以我们尝试在GCD Block中添加 [[NSRunLoop currentRunLoop]run];

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self performSelector:@selector(sureTestMethod:)
                   withObject:params
                   afterDelay:3];
        [[NSRunLoop currentRunLoop]run];
    });
复制代码

运行代码发现可以正常打印sureTestMethodCall。

这里有个坑需要注意,曾经尝试将 [[NSRunLoop currentRunLoop]run]添加在performSelector: withObject: afterDelay:方法前,但发现延迟方法仍然不调用,这是因为若想开启某线程的Runloop,必须具有timer、source、observer任一事件才能触发开启。

简言之如下代码在执行 [[NSRunLoop currentRunLoop]run]前没有任何事件添加到当前Runloop,因此该线程的Runloop是不会开启的,从而延迟事件不执行。

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [[NSRunLoop currentRunLoop]run];
        [self performSelector:@selector(sureTestMethod:)
                   withObject:params
                   afterDelay:3];
    });
复制代码

关于Runloop,可详见:深入理解RunLoop

3、performSelector取消延迟

我们在View上放置一个Button,预期需求是防止暴力点击,只响应最后一次点击时的事件。

此需求我们可以通过cancelPreviousPerformRequestsWithTarget来进行实现。cancelPreviousPerformRequestsWithTarget的作用为取消当前延时任务。在执行延迟事件前取消当前存在的延迟任务即可实现如上效果。

- (IBAction)buttonClick:(id)sender {
    id params;
    [[self class]cancelPreviousPerformRequestsWithTarget:self
                                                selector:@selector(sureTestMethod:)
                                                  object:params];
    [self performSelector:@selector(sureTestMethod:)
               withObject:params
               afterDelay:3];
}

- (void)sureTestMethod:(id)objcet {
    NSLog(@"sureTestMethodCall");
}
复制代码

重复点击后,打印结果如下,只响应了一次点击

2019-05-06 11:29:50.352157+0800 performSelector[14342:457353] sureTestMethodCall
复制代码

4、performSelector模拟多线程

我们可以通过performSelectorInBackground将某selector任务放在子线程中

[self performSelectorInBackground:@selector(sureTestMethod:)
                           withObject:params];
- (void)sureTestMethod:(id)objcet {
    NSLog(@"%@",[NSThread currentThread]);
}
复制代码
//<NSThread: 0x600003854080>{number = 3, name = (null)}
复制代码

打印结果可见当前方法运行在子线程中。

回到主线程执行我们可以通过方法

[self performSelectorOnMainThread:@selector(sureTestMethod)
                       withObject:params
                    waitUntilDone:NO];
复制代码

waitUntilDone表示是否等待当前selector任务完成后再执行后续任务。示例如下,waitUntilDone为YES时,打印1,2,3。为NO时打印1,3,2。

NSLog(@"1");
    [self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:NO];
    NSLog(@"3");
复制代码
- (void)test {
    sleep(3);
    NSLog(@"2");
}
复制代码

另外performSelector还提供了将任务执行在某个指定线程的操作

[self performSelector:@selector(sureTestMethod:)
                 onThread:thread
               withObject:params
            waitUntilDone:NO];
复制代码

使用该方法一定要注意所在线程生命周期是否正常,若thread已销毁不存在,而performSelector强行执行任务在该线程,会导致崩溃:

NSThread *thread = [[NSThread alloc]initWithBlock:^{
    NSLog(@"do thread event");
}];
[thread start];
[self performSelector:@selector(sureTestMethod:)
             onThread:thread
           withObject:params
        waitUntilDone:YES];
复制代码

上述代码会导致崩溃,崩溃信息为:

*** Terminating app due to uncaught exception 'NSDestinationInvalidException',
reason: '*** -[ViewController performSelector:onThread:withObject:waitUntilDone:modes:]:
target thread exited while waiting for the perform'
复制代码

因为thread开启执行do thread event完毕后即退出销毁,所以在等待执行任务时Thread已不存在导致崩溃。

好了,关于performSelector的内容暂时写到这里了,有其他补充欢迎评论~

iOS执行器performSelector详解


以上所述就是小编给大家介绍的《iOS执行器performSelector详解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Just My Type

Just My Type

Simon Garfield / Profile Books / 2010-10-21 / GBP 14.99

What's your type? Suddenly everyone's obsessed with fonts. Whether you're enraged by Ikea's Verdanagate, want to know what the Beach Boys have in common with easy Jet or why it's okay to like Comic Sa......一起来看看 《Just My Type》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具