上一篇我们有分析runtime的大概实现过程,我们知道了objc_msgsend这个东西。它说的就是c语言的消息分发底层的大概实现。那么我们在iOS开发的过程中,实际会使用到的最直接的基于runtime机制的消息方法不就是performselector方法嘛!今天,我们就来分析一下这个东西:(同样成果是基于自己实践与看别人的博客的基础,所以也唠叨一下,希望各位可以多看看别人的博客哦,或者自己写写)
SEL:这个是一个类似c++函数指针的东西,它可以与字符串相互转换。它对应相应的方法地址,找到地址就可以调用方法了;
调用@selector,就要用到我们的perform selector了。
线程在运行过程中,可能需要与其它线程进行通信。我们可以使用NSObject中的一些方法:
在主线程执行:(比如UI主线程)
performSelectorOnMainThread: withObject: waitUntilDone:
performSelectorOnMainThread: WithObject: waitUntilDone: modes:
waitUntilDoone控制同步和异步。NO为异步;YES为同步。
在指定线程内执行:
performSelector:OnThread: withObject: waitUntilDone:
performSelector: OnThread: WithObject: waitUntilDone: modes:
在当前线程执行:performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
取消发送给当前线程的某个消息
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:
上面的这些都是出自NSRunloop.h;还有一套NSObject.h
-(id)performSelector:(SEL)aSelector;
-(id)performSelector:(SEL)aSelector withObject:(id)object;
-(id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
我们经常用到的阳光是继承自NSObject的类;
但是,你大概注意到了,使用这个的话有个缺陷,它的参数个数一般不多于2个。好,这个时候,我们就注意到iOS还有另外一套东西:NSInvocation;
这个就没什么好讲的了,直接上代码,看了就明白了:
.h文件
#import <Foundation/Foundation.h>
/**
* 这是一个Category,创建category的方法是source里的Object-C file,输入的名字就是+后面的名字。
*/
@interface NSObject (performSelector)
-(id)performSelector:(SEL)aSelector withObjects:(NSArray *)object;
@end
#import "NSObject+performSelector.h"
@implementation NSObject (performSelector)
-(id)performSelector:(SEL)aSelector withObjects:(NSArray *)object
{
//这是一个签名对象,它是通过被调用消息所属类创建出来的,如果传入的方法不存在就无法生成。
NSMethodSignature * signature = [[selfclass] instanceMethodSignatureForSelector:aSelector];
if (signature==nil) {
NSString * info = [NSStringstringWithFormat:@"-[%@ %@]:",[selfclass],NSStringFromSelector(aSelector)];
@throw [[NSExceptionalloc] initWithName:@"方法没有"reason:info userInfo:nil];
return nil;
}
//创建NSInvocation对象
NSInvocation * invocation = [NSInvocationinvocationWithMethodSignature:signature];
//设置调用者
[invocation setTarget:self];
//设置被执行的方法
[invocation setSelector:aSelector];
//设置参数
/**
* 这里就要多说几句了。我们知道,通常我们习惯使用的是performSelector,但是这个东西有点缺陷,那就是它对于参数大于2个的方法的调用就比较麻烦,要额外处理,使用才会出现这个NSInvocation;但是使用NSInvocation的一个需要注意的地方就是设置参数时下标必须从2开始,因为0、1已经被target和selector占用了。
*/
//这个是函数的参数个数,而不是传进来的数组个数
NSInteger arguments = signature.numberOfArguments-2;
NSUInteger objcCount = object.count;
NSInteger count = MIN(arguments, objcCount);
for (int i=0; i<count; i++) {
NSObject * obj = object[i];
if ([obj isKindOfClass:[NSNullclass]]) {
obj = nil;
}
[invocation setArgument:&obj atIndex:i+2];
}
//消息调用
[invocation invoke];
//
id res = nil;
if (signature.methodReturnLength!=0) {
[invocation getReturnValue:&res];
}
return res;
}
@end
#import "ViewController.h"
#import "NSObject+performSelector.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[superviewDidLoad];
NSArray *arr = @[@"1",@"2",@"3"];
//一个参数
[selfperformSelector:@selector(call:)withObjects:@[arr]];
NSDictionary * dic =@{@"1":@"a",@"2":@"b",@"3":@"c",@"4":@"d"};
//两个参数
[self performSelector:@selector(call:withDic:)withObjects:@[arr,dic]];
}
- (void)call:(NSArray *)arr
{
// NSLog(@"%@",arr);
for (NSString * strin arr) {
NSLog(@"%@",str);
}
}
- (void)call:(NSArray *)arr withDic:(NSDictionary *)dic
{
for (NSString * strin arr) {
NSLog(@"%@",str);
}
for (id keyin dic) {
NSLog(@"%@",[dicobjectForKey:key]);
}
}