美文网首页ios面试汇总ios进阶
iOS performSelector 各个方法原理讲解

iOS performSelector 各个方法原理讲解

作者: 孙掌门 | 来源:发表于2019-12-17 21:21 被阅读0次

iOS performSelector 各个方法原理讲解

1.performSelecor 是延迟到运行时才会去检查方法是否存在,编译时不会检查方法是否存在,比如我们运行时添加一个方法,而在编译时是不存在的,所以就需要用perform来调用

2.直接调用一般需要导入头文件或者声明方法,而perform不需要

perform 如果想传递三个参数以上,有三种方式

perfromSelector:withObject: afterDelay:

1: NSInvocation(不常用)
2:将多个参数封装为一个参数,如字典结构体等(常用)
3:objc_msgSend(如:((void (*) (id, SEL, NSString *, NSNumber *, NSArray *)) objc_msgSend) (self, sel, name, age, arr);),分别传递字符串,int,和arr(较常用)


- (void)test2{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:0];
        NSLog(@"3");
    });
}
- (void)test{
    NSLog(@"2");
    
}

上面的代码打印什么呢?结果是打印 1,3

- (void)test2{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:0];
        NSLog(@"3");
    });
}
- (void)test{
    NSLog(@"2");
    
}

这个打印又是什么呢?答案是都会打印

解释:

之所以第一个不打印2的原因是,我们将任务放到一个全局的并发队列当中,而这个队列被我们GCD放在底层的某一个线程当中,而系统默认是不开启这些线程的runloop的,所以需要我们手动提交到runloop上面,而主线程默认是开启的,所以第二个会打印。

原理及测试

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay; 其实就是在内部创建了 NSTimer ,然后添加到当前的runloop上面,之所以没有调用,是因为子线程的runloop默认是不开启的,那我们就先测试开启runloop,注意runloop的开启顺序

测试1:

- (void)test2{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        [[NSRunLoop currentRunLoop] run];
        [self performSelector:@selector(test) withObject:nil afterDelay:1];
        NSLog(@"3");
        NSLog(@"%@",[NSRunLoop currentRunLoop]);
    });
}
- (void)test{
    NSLog(@"2");
    
}

我们发现还是没有打印2, 我们最后打印了当前的runloop,发现其中有timer

timers = <CFArray 0x6000014a8a20 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (
    0 : <CFRunLoopTimer 0x6000005b8300 [0x7fff80617cb0]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 598278999 (0.999223948 @ 128738630533972), callout = (Delayed Perform) ViewController test (0x7fff2576c7c2 / 0x10c1b08b0) (/Users/sunchengxiu/Library/Developer/CoreSimulator/Devices/EF8A0739-0CA2-400E-AE5B-F238182E9901/data/Containers/Bundle/Application/59B9688A-6BC2-4BBD-9DB5-652769D108B4/blogTest.app/blogTest), context = <CFRunLoopTimer context 0x6000025dc600>}
)},

这个子线程的runloop确实添加了一个timer,但是没有执行,那么我们将run位置调换一下

- (void)test2{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        
        [self performSelector:@selector(test) withObject:nil afterDelay:5];
        [[NSRunLoop currentRunLoop] run];
        NSLog(@"3");
        NSLog(@"%@",[NSRunLoop currentRunLoop]);
    });
}
- (void)test{
    NSLog(@"2");
    
}

这时候发现打印了2,1,等待5秒打印2,然后3.

再看一段代码

- (void)test2{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        
        [self performSelector:@selector(test) withObject:nil afterDelay:5];
        NSLog(@"4");
        [[NSRunLoop currentRunLoop] run];
        NSLog(@"3");
//        NSLog(@"%@",[NSRunLoop currentRunLoop]);
    });
}
- (void)test{
    NSLog(@"2");
    
}

打印结果为1,4,2,3在runloop开启之前4也会打印,run 之所以要放在后面,是因为,run只是要尝试开启当前线程的runloop,但是当前线程如果没有任何事件(source、timer、observer)的话,也并不会开启成功,所以这就是为什么要放在后面的原因。

performSelector 执行的线程就是当前的线程.

那为什么会先打印14,然后就开始等待打印2,3,呢,从代码的打印上面可以看出,当我们没有提交当前的runloop的时候,是不会阻塞的,因为perform这个方法是在当前线程中执行的,所以当我们提交run的时候,检测到有source事件,也就是timer事件,会在当前线程阻塞去执行timer,当timer执行完毕之后,继续向下执行。

performSelectorOnMainThread

- (void)test3{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        
        [self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];
        NSLog(@"3");
    });
}
- (void)test{
    NSLog(@"%@",[NSThread currentThread]);
    sleep(3);
    NSLog(@"2");
    
}

上面的打印结果又是什么呢?答案是 1,隔3秒然后打印2,在打印3,打印的线程为主线程,虽然切换到了主线程,但是waitUntilDone参数会让这个任务执行完成之后才会向下执行,会阻塞当前线程等待performSelectorOnMainThread的任务执行完毕才可以打印3.

如果将waitUntilDone:NO,改为了 NO

那么会立刻打印13,然后隔3秒打印2,不会阻塞当前线程。waitUntilDone:NO,这个参数只是决定是否阻塞当前线程,如果为YES,就需要等待任务结束后才可以向下执行,如果为NO,则不阻塞。

performSelectorInBackground


- (void)test4{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        
        [self performSelectorInBackground:@selector(test) withObject:nil];
        NSLog(@"3");
    });
}
- (void)test{
    NSLog(@"%@",[NSThread currentThread]);
    sleep(3);
    NSLog(@"2");
    
}

打印结果为132,在子线程中执行,互不影响,不阻塞当前线程

performSelector:withObject

- (void)test5{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        
        [self performSelector:@selector(test) withObject:nil];
        NSLog(@"3");
    });
}
- (void)test{
    NSLog(@"%@",[NSThread currentThread]);
    sleep(3);
    NSLog(@"2");
    
}

打印结果为123,说明 performSelector:withObject 会阻塞当前线程,等方法执行完之后,才可以向下执行,上面我说的阻塞当前线程是因为我的performSelector方法里面是同步的,如果有异步方法,也不会阻塞当前线程,这个要搞明白。

相关文章

网友评论

    本文标题:iOS performSelector 各个方法原理讲解

    本文链接:https://www.haomeiwen.com/subject/wubenctx.html