目录
前言
OC中发送消息:使用中括号把接收者和消息括起来。直到运行时才会把消息与方法实现绑定。
[person run]; 会被编译器转为 objc_msgSend(person,@selector(run));
objc_msgSend
/*
objc_msgSend(person, @selector(run))
消息发送步骤:
1. 检测 SEL 是否应该被忽略
2. 检测 target 是否是nil ,为nil则忽略该消息。
向nil发消息(不会崩)
返回值是对象,返 nil
返回值为指针类型,返回 0。
返回值为结构体,返回 0(结构体中各个字段的值将都是0)。
返回值以上都不是,返回 值未定义
3. 查找IMP
具体参见下面第1小节
*/
id objc_msgSend(id self,SEL op, ...);
/*
根据实例对象和SEL 寻找 IMP
// 第一个参数: id
typedef struct objc_object *id;
// 第二个参数: SEL
typedef struct objc_selector *SEL;
// 第三个参数: 方法参数列表
*/
SEL
方法名的唯一标识
一个方法名对应一个SEL(不同类相同方法的SEL相同)。
OC中不支持函数重载就是因为一个类的方法列表中不能存在两个相同的 SEL 。
SEL x1=@selector(method:);
SEL x2=NSSelectorFromString(@"method:");
SEL x3=sel_registerName("method:");
typedef struct objc_selector *SEL;
struct objc_selector {
char *name; OBJC2_UNAVAILABLE;
char *types; OBJC2_UNAVAILABLE;
};
IMP
方法具体实现的地址
typedef id (*IMP)(id, SEL, ...); (类实例或元类,SEL,参数) 函数地址
/*
instance -> class -> method -> SEL -> IMP -> 实现函数
实例对象中存放 isa 指针以及实例变量,有 isa 指针可以找到实例对象所属的类对象 (类也是对象,面向对象中一切都是对象),类中存放着实例方法列表,在这个方法列表中 SEL 作为 key,IMP 作为 value。 在编译时期,根据方法名字会生成一个唯一的 Int 标识,这个标识就是 SEL。IMP 其实就是函数指针 指向了最终的函数实现。整个 Runtime 的核心就是 objc_msgSend 函数,通过给类发送 SEL 以传递消息,找到匹配的 IMP 再获取最终的实现。
类中的 super_class 指针可以追溯整个继承链。向一个对象发送消息时,Runtime 会根据实例对象的 isa 指针找到其所属的类,并自底向上直至根类(NSObject)中 去寻找 SEL 所对应的方法实现,找到后就运行。
metaClass是元类,也有 isa 指针、super_class 指针。其中保存了类方法列表。
*/
1. 方法执行过程
1: 首先,在对象的缓存方法列表中寻找被调用的方法,如果找到则转向相应方法实现并执行。
objc_cache
2: 如果没找到,在对象中的方法列表中寻找被调用的方法,如果找到则存添加到缓存列表并转向相应方法实现并执行。
objc_method_list
3: 如果没找到,去父类中继续执行1,2。
4: 以此类推,如果一直到根类还没找到则转向【拦截调用】。
5: 如果没有重写拦截调用的方法,程序报错。
2. 拦截调用(unrecognized selector崩溃前的一道关卡)
当找不到相应方法,在未识别方法崩溃之前会转向拦截调用(有三次补救机会)。
注意:
1. 有些个第三方内部也会实现下面几个方法,如果不小心被自己写的方法覆盖会出错
》》》》实例方法
当调用不存在的实例方法时首先会调用方法1
方法1 (默认返回false,然后去调用方法2(方法2未实现则崩溃))
// 可添加方法后返回true
+ (BOOL)resolveInstanceMethod:(SEL)aSEL{
if (aSEL == @selector(goToSchool:)) {
class_addMethod([self class], aSEL, class_getMethodImplementation([self class], @selector(myInstanceMethod:)), "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
方法2
// 转发给拥有该方法的实例
// 返回nil 则调用3(3未实现则崩)
- (id)forwardingTargetForSelector:(SEL)aSelector{
if(aSelector == @selector(mysteriousMethod:)){
return [Person new];
}
return [super forwardingTargetForSelector:aSelector];
}
方法3
// 作相应处理后,调用invokeWithTarget:将invocation传给拥有该方法的实例
- (void)forwardInvocation:(NSInvocation *)anInvocation{
anInvocation.target=nil;
[anInvocation invocation];
/*
// invocation封装了原始的消息和消息的参数
Person *person=[Person new];
if ([person respondsToSelector:[anInvocation selector]]){
[anInvocation invokeWithTarget: person];
}else{
[super forwardInvocation:anInvocation];
}
*/
}
/*
调用forwardInvocation:之前,会先调用methodSignatureForSelector:,并取到返回的方法签名用于生成NSInvocation对象。
在重写forwardInvocation:的同时必须重写methodSignatureForSelector:获取函数的参数和返回值,系统会自动创建NSInvocation并调用forwardInvocation:,否则会抛异常。
返回nil会抛异常。
*/
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
}
return signature;
}
》》》》类方法
1
// 当调用不存在的类方法时调用(默认:false,调用2(2未实现则崩))
// 可作相应处理后返回true
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(learnClass:)) {
class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(myClassMethod:)), "v@:");
return YES;
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
2
例
例1
请求网络获取数据的时候会经常需要:responseObsect[@"data"][@"persons"]
万一后端数据返回的data数据类型不是字典还是数组,会导致未识别方法崩溃。
解决:在NSArray分类中添加
+(BOOL)resolveInstanceMethod:(SEL)aSEL{
if (aSEL==@selector(objectForKeyedSubscript:)) {
class_addMethod([self class], aSEL, class_getMethodImplementation([self class], @selector(myInstanceMethod:)), "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
-(void)myInstanceMethod:(id)param{
NSLog(@"objectForKeyedSubscript未识别方法崩溃捕捉");
}
例2
万一后端数据返回的data数据类型是null,会导致未识别方法崩溃。
解决:在NSNull分类中添加
-(NSMethodSignature *)methodSignatureForSelector:(SEL)selector{
//
NSMethodSignature *signature=[super methodSignatureForSelector:selector];
if(!signature){
signature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation{
invocation.target = nil;
[invocation invoke];
NSLog(@"NSNull未识别方法崩溃捕捉");
}