2021 swfit 重拾之路

swift - perform

2021-07-05  本文已影响0人  大也

1.知识补充
那么直接调用没有定义的方法:在编译时候就能够发现(借助Xcode可以写完就发现),但是使用performSelector的话一定是在运行时候才能发现(此时程序崩溃);

Cocoa支持在运行时向某个类添加方法,即方法编译时不存在,但是运行时候存在,这时候必然需要使用performSelector去调用。

所以有时候如果使用了performSelector,为了程序的健壮性,会使用检查方法respondsToSelector。

直接调用方法时候,一定要在头文件中声明该方法的使用,也要将头文件import进来。而使用performSelector时候, 可以不用import头文件包含方法的对象,直接用performSelector调用即可。

performSelector是在iOS中的一种方法调用方式。他可以向一个对象传递任何消息,而不需要在编译的时候声明这些方法。所以这也是runtime的一种应用方式。
所以performSelector和直接调用方法的区别就在与runtime。直接调用编译是会自动校验。如果方法不存在,那么直接调用 在编译时候就能够发现,编译器会直接报错。
但是使用performSelector的话一定是在运行时候才能发现,如果此方法不存在就会崩溃。所以如果使用performSelector的话他就会有个最佳伴侣respondsToSelector:;来在运行时判断对象是否响应此方法。

备注:runtime
在这小作总结:OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法。利用runtime机制让我们可以在程序运行时动态修改类、对象中的所有属性、方法,就算是私有方法以及私有属性都是可以动态修改的。

2.swift 方法

例子1
//调用方法
perform(<#T##aSelector: Selector!##Selector!#>) 
//调用方法 1个参数
      perform(<#T##aSelector: Selector!##Selector!#>, with: <#T##Any!#>)
//调用方法 2个参数
      perform(<#T##aSelector: Selector!##Selector!#>, with: <#T##Any!#>, with: <#T##Any!#>)
例子2
//调用方法 延迟调用 
//注意:使用该方法需要注意以下事项: 在子线程中调用performSelector: withObject: afterDelay:默认无效
这是因为performSelector: withObject: afterDelay:是在当前Runloop中延时执行的,而子线程的Runloop默认不开启,因此无法响应方法 除非再子线程中添加 [[NSRunLoop currentRunLoop]run];
//        perform(#selector(alertStyleTitleOnly), with: nil, afterDelay: 0.0, inModes: [RunLoop.Mode.default])
//        perform(#selector(方法), with: 参数, afterDelay: 延迟时间, inModes: 线程数组)
      perform(<#T##aSelector: Selector##Selector#>, with: <#T##Any?#>, afterDelay: <#T##TimeInterval#>)
      perform(<#T##aSelector: Selector##Selector#>, with: <#T##Any?#>, afterDelay: <#T##TimeInterval#>, inModes: <#T##[RunLoop.Mode]#>)
例子3 performSelector取消延迟
      ADFunctionTableVC.cancelPreviousPerformRequests(withTarget: <#T##Any#>)
      ADFunctionTableVC.cancelPreviousPerformRequests(withTarget: <#T##Any#>, selector: <#T##Selector#>, object: <#T##Any?#>)
例子4
waitUntilDone表示是否等待当前selector任务完成后再执行后续任务。示例如下,waitUntilDone为YES时,打印1,2,3。为NO时打印1,3,2。
注意 modes 一直找不到它的解释 暂时无法记忆
//        perform(#selector(alertStyleTitleOnly), on: Thread.current, with: nil, waitUntilDone: false)

      perform(<#T##aSelector: Selector##Selector#>, on: <#T##Thread#>, with: <#T##Any?#>, waitUntilDone: <#T##Bool#>)
      perform(<#T##aSelector: Selector##Selector#>, on: <#T##Thread#>, with: <#T##Any?#>, waitUntilDone: <#T##Bool#>, modes: <#T##[String]?#>)
例子5
注意 modes 一直找不到它的解释 暂时无法记忆
//        performSelector(onMainThread: #selector(alertStyleTitleOnly), with: nil, waitUntilDone: true)
        performSelector(inBackground: <#T##Selector#>, with: <#T##Any?#>)
        performSelector(onMainThread: <#T##Selector#>, with: <#T##Any?#>, waitUntilDone: <#T##Bool#>)
        performSelector(onMainThread: <#T##Selector#>, with: <#T##Any?#>, waitUntilDone: <#T##Bool#>, modes: <#T##[String]?#>)

备注 在swift中 使用 调用的方法要加@objc

----------------例子

例子1
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
 
    /*performSelector
     - (id)performSelector:(SEL)aSelector;
     - (id)performSelector:(SEL)aSelector withObject:(id)object;
     - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
     
      - (BOOL)respondsToSelector:(SEL)aSelector;
     */
    
    //其次,进行调用
    //没有参数
    BOOL isNoParam= [self respondsToSelector:@selector(methodNoParam)];
    if (isNoParam) {
        [self performSelector:@selector(methodNoParam)];
    }
    
    //一个参数
    BOOL isOneParam= [self respondsToSelector:@selector(methodWithOneParam:)];
    if (isOneParam) {
        [self performSelector:@selector(methodWithOneParam:) withObject:@"first"];
    }
    
    //二个参数
    BOOL isParams= [self respondsToSelector:@selector(methodWithParams: andParamSecond:)];
    if (isParams) {
        [self performSelector:@selector(methodWithParams: andParamSecond:) withObject:@"first" withObject:@"second"];
    }

    //建立动态的函数,然后调用它们
    NSArray *objectArray = @[@{@"methodName":@"DynamicParameterString:", @"value":@"String"},
                           @{@"methodName":@"DynamicParameterNumber:", @"value":@2}];
    for (NSDictionary *dic in objectArray) {
        SEL selector = NSSelectorFromString([dic objectForKey:@"methodName"]);
        if ([self respondsToSelector:selector]) {
            //这里会有警告:PerformSelector may cause a leak because its selector is unknown
            [self performSelector:selector withObject:[dic objectForKey:@"value"]];
        }
    }
    
    //PerformSelector may cause a leak because its selector is unknown 解决方法
    //1.使用函数指针方式
    for (NSDictionary *dic in objectArray) {
        SEL selector = NSSelectorFromString([dic objectForKey:@"methodName"]);
        if ([self respondsToSelector:selector]){
            IMP imp = [self methodForSelector:selector];
            void (*func)(id, SEL) = (void *)imp;
            func(self, selector);
        }
    }
    //其它方法消除警告:http://www.tuicool.com/articles/iu6zuu

}

//首先,定义要调用的方法
- (void)methodNoParam{
    NSLog(@"methodNoParam");
}

- (void)methodWithOneParam:(id)paramFirst{
    NSLog(@"methodWithOneParam: %@", paramFirst);
}

- (void)methodWithParams:(id)paramFirst andParamSecond:(id) paramSecond{
    NSLog(@"methodWithOneParam: %@,%@", paramFirst,paramSecond);
}


- (void)DynamicParameterString:(NSString *)string{
    NSLog(@"DynamicParameterString: %@",string);
}

- (void)DynamicParameterNumber:(NSNumber *)number{
    NSLog(@"DynamicParameterNumber: %@",number);
}
例子2
//子线程 不打印
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [self performSelector:@selector(sureTestMethod:)
               withObject:params
               afterDelay:3];
});
- (void)sureTestMethod:(id)objcet {
    NSLog(@"sureTestMethodCall");
}

//正常
dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self performSelector:@selector(sureTestMethod:)
                   withObject:params
                   afterDelay:3];
        [[NSRunLoop currentRunLoop]run];
    });

这里有个坑需要注意,曾经尝试将 [[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];
    });
例子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");
}
重复点击后,打印结果如下,只响应了一次点击
performSelector[14342:457353] sureTestMethodCall
例子4

  NSLog(@"1");
    [self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:NO];
    NSLog(@"3");

- (void)test {
    sleep(3);
    NSLog(@"2");
}

waitUntilDone表示是否等待当前selector任务完成后再执行后续任务。示例如下,waitUntilDone为YES时,打印1,2,3。为NO时打印1,3,2。
我们在View上放置一个Button,预期需求是防止暴力点击,只响应最后一次点击时的事件。

另外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已不存在导致崩溃。

例子5

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

[self performSelectorInBackground:@selector(sureTestMethod:)
                           withObject:params];
- (void)sureTestMethod:(id)objcet {
    NSLog(@"%@",[NSThread currentThread]);
}

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

[self performSelectorOnMainThread:@selector(sureTestMethod)
                       withObject:params
                    waitUntilDone:NO];

https://blog.csdn.net/weixin_33720956/article/details/91364175
https://www.jianshu.com/p/672c0d4f435a

上一篇 下一篇

猜你喜欢

热点阅读