performSelector

摘要:
https://opensource.apple.com/tarballs/objc4/非延迟方法-(id)performSelector:obj);}-(id)performSelector:(id)obj1withObject:sel)[self-doesNotRecognizeSelector:@selector(test)];@选择器(测试1)];

perfromSelector

底层源码地址:https://opensource.apple.com/tarballs/objc4/

非延迟方法

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

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

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

performSelector是运行时系统负责去找方法,在编译时不会对调用的方法做检查,只有在运行的时候才会检查,如果方法存在就调用,如果方法不存在就不会调用。当然也可以通过使用 - (BOOL)respondsToSelector:(SEL)aSelector;方法去判断对象是否实现了要调用的方法。
这三个方法调用都是直接执行,相当于直接通过对象调用方法, [self performSelector:@selector(test)];与[self test]; 执行的结果是一致的,通过这些方法去执行是不需要子线程去启动Runloop的方法内运行的线程就是调用performSelector所在的线程
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"当前线程:%@",[NSThread currentThread]);
    [self performSelector:@selector(test1)];
    [self performSelector:@selector(test2:) withObject:@"小明"];
    [self performSelector:@selector(test3:andAge:) withObject:@"小明" withObject:@"10"];
    NSLog(@"****************************************");
    dispatch_queue_t queue = dispatch_queue_create("新的并发队列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"当前线程:%@",[NSThread currentThread]);
        [self performSelector:@selector(test1)];
        [self performSelector:@selector(test2:) withObject:@"小明"];
        [self performSelector:@selector(test3:andAge:) withObject:@"小明" withObject:@"10"];
    });
}

-(void)test1{
    NSLog(@"执行了test1 当前线程:%@",[NSThread currentThread]);
}

-(void)test2:(NSString *)name{
    NSLog(@"执行了test2 name:%@ 当前线程:%@",name,[NSThread currentThread]);
}

-(void)test3:(NSString *)name andAge:(NSString *) age{
    NSLog(@"执行了test3 name:%@ age:%@ 当前线程:%@",name,age,[NSThread currentThread]);
}
/*输出
 当前线程:<NSThread: 0x600000624a80>{number = 1, name = main}
 执行了test1 当前线程:<NSThread: 0x600000624a80>{number = 1, name = main}
 执行了test2 name:小明 当前线程:<NSThread: 0x600000624a80>{number = 1, name = main}
 执行了test3 name:小明 age:10 当前线程:<NSThread: 0x600000624a80>{number = 1, name = main}
 ****************************************
 当前线程:<NSThread: 0x60000066d380>{number = 6, name = (null)}
 执行了test1 当前线程:<NSThread: 0x60000066d380>{number = 6, name = (null)}
 执行了test2 name:小明 当前线程:<NSThread: 0x60000066d380>{number = 6, name = (null)}
 执行了test3 name:小明 age:10 当前线程:<NSThread: 0x60000066d380>{number = 6, name = (null)}
上面的方法,最多可以支持传递2个参数,如果要传递2个以上,上面的方法就不能使用了。

     方法一 objc_msgSend

((void (*) (id, SEL, NSString *, NSString *, NSString *)) objc_msgSend) (self, @selector(test4:andAge:andSex:), @"小明", @"10", @"男");

  1. -(void)test4:(NSString *)name andAge:(NSString *) age andSex:(NSString *)sex{
        NSLog(@"执行了test4 name:%@ age:%@ sex:%@ 当前线程:%@",name,age,sex,[NSThread currentThread]);
    }
    
    /*输出
    执行了test4 name:小明 age:10 sex:男 当前线程:<NSThread: 0x600000a68580>{number = 1, name = main}
    */
  2. 方法二 NSInvocation
 //1、方法签名
  NSMethodSignature *signature = [[self class]instanceMethodSignatureForSelector:@selector(test4:andAge:andSex:)];
    //包装方法
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    //方法调用者
     invocation.target = self;
     //要调用的方法和方法签名中的方法一样
    invocation.selector = @selector(test4:andAge:andSex:);
    NSString *name = @"小明";
    NSString *age = @"10";
    NSString *sex = @"男";
    //设置传递的参数 0 代表target 1代表 selector 所以从2开始
    [invocation setArgument:&name atIndex:2];
    [invocation setArgument:&age atIndex:3];
    [invocation setArgument:&sex atIndex:4];
    //执行方法
    [invocation invoke];
    //获取返回值
    NSString *returnValue = @"";
    [invocation getReturnValue:&returnValue];
    NSLog(@"返回值:%@",returnValue);
    
    
-(NSString *)test4:(NSString *)name andAge:(NSString *) age andSex:(NSString *)sex{
    NSLog(@"执行了test4 name:%@ age:%@ sex:%@ 当前线程:%@",name,age,sex,[NSThread currentThread]);
    return @"这是返回值";
}

/*
 执行了test4 name:小明 age:10 sex:男 当前线程:<NSThread: 0x600000f9c980>{number = 1, name = main}
  返回值:这是返回值
*/


延迟执行方法

- (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上执行aSelector消息,这个计时器的默认模式是NSDefaultRunLoopMode。当计时器触发时,会尝试从runloop中取出消息行,如果runloop运行的模式是NSDefaultRunLoopMode,那么就会执行它,如果当前runloop是其他模式,则会等待runloop处于NSDefaultRunLoopMode在运行。

如果希望在运行循环处于NSDefaultRunLoopMode以外的其他模式时使消息出队,请改用performSelector:withObject:afterDelay:inModes:方法。如果不确定当前线程是否为主线程,则可以使用performSelectorOnMainThread:withObject:waitUntilDone:或performSelectorOnMainThread:withObject:waitUntilDone:modes:方法来确保选择器在主线程上执行。要取消排队的消息,请使用cancelPreviousPerformRequestsWithTarget:或cancelPreviousPerformRequestsWithTarget:selector:object:方法。

特别注意事项
此方法向其当前上下文的runloop进行注册,并依赖于runloop才能正确执行。一种常见情况是,当在 dispatch queue上调用这个方法,但是runloop并没有启动,这个方法是不会运行的。如果想使用这个延迟功能在dispatch queue上,则应使用dispatch_after和相关方法来获得所需的行为。

  1. 在主线程中调用
-(void)test1:(NSString *)name{
    NSLog(@"执行了test1 name:%@ 当前线程:%@",name,[NSThread currentThread]);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //在主线程中,它的runloop是默认开启的,所有下面的方法是可以直接执行
    [self performSelector:@selector(test1:) withObject:@"小明" afterDelay:2];
}


//输出
//执行了test1 name:小明 当前线程:<NSThread: 0x6000012a8b00>{number = 1, name = main}

    在队列中,不调用[[NSRunLoop currentRunLoop] run];

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        //这个方法是不会执行的,因为此时的这个线程的runloop默认是没有开启的
        [self performSelector:@selector(test1:) withObject:@"小红" afterDelay:2];
    });
}
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        [[NSRunLoop currentRunLoop] run];
        //这个方法是不会执行的,调用run方法只是尝试开启当前线程中的runloop,但是如果该线程中并没有任何事件(source、timer、observer)的话,runloop并不会开启。
        [self performSelector:@selector(test1:) withObject:@"小红" afterDelay:1];
    });
}

在队列中,在调用performSelector之后调用[[NSRunLoop currentRunLoop] run];

-(void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        //成功调用函数
        [self performSelector:@selector(test1:) withObject:@"小红" afterDelay:1];
        [[NSRunLoop currentRunLoop] run];
    });
}
//输出
//执行了test1 name:小红 当前线程:<NSThread: 0x6000015b0a00>{number = 7, name = (null)}

免责声明:文章转载自《performSelector》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇canvas画图标签的使用idea+html图片加载失败下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

定位多线程内存越界问题实践总结

最近定位了在一个多线程服务器程序(OceanBase MergeServer)中,一个线程非法篡改另一个线程的内存而导致程序core掉的问题。定位这个问 题花了整整一周的时间,期间历经曲折,尝试了各种内存调试的办法。往往感觉就要柳暗花明了,却发现又进入了另一个死胡同。最后,使用 强大的mprotect+backtrace+libsigsegv等工具成...

Let's GO(四)

人生苦短,Let's GO Let's GO(一) Let's GO(二) Let's GO(三) Let's GO(四) 今天我学了什么? 1.panic && recover Go的logo是一只萌萌的囊地鼠(Gopher) 当Go程序出现错误,程序将报panic(恐慌) 所以是错误代码吓到小地鼠了吗哈哈 然后需要用recover来...

java面试宝典

相关概念 面向对象的三个特征 封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象. 多态的好处 允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用).主要有以下优点: 可替换性:多态对已存在代码具有可替换性. 可扩充性:增加新的子类不影响已经存在的类结构. 接口性:多态是超累通过方法签名...

QT之深入理解QThread

QT之深入理解QThread       理解QThread之前需要了解下QThread类,QThread拥有的资源如下(摘录于QT 5.1 帮助文档):       在以上资源中,本文重点关注槽:start();信号:started()、finished();受保护的方法:run()、exec();   理解QThread     QThread与通常所...

gitk、Git GUI 图形化工具中文显示乱码的解决方案

在Windows下使用gitk、Git-Gui时,可能会出现代码中的中文乱码的情况。解决方法:在软件的安装目录下,在Gitmingw64etcgitconfig文件末尾添加: [gui]encoding=utf-8 这样代码中的中文就能够正常显示了。 另外 关于软件的其他部分的软件相关中文乱码,解决方法如下:打开软件,在Edit -> Prefere...

使用 suspend 和 resume 暂停和恢复线程

suspend 和 resume 的使用 在 Thread 类中有这样两个方法:suspend 和 resume,这两个方法是成对出现的。 suspend() 方法的作用是将一个线程挂起(暂停), resume() 方法的作用则是将一个挂起的线程重新开始并继续向下运行。 通过一个例子来看一下这两个方法的使用: public class SuspendT...