消息发送
NSMutableString * mstr = [[NSMutableString alloc] initWithString:@"123"];
[mstr appendString:@"456"];
第二句中,mstr称为消息接收者,appendString:称为选择器,也就是我们常用的selector
,selector
和参数共同构成了消息,所以第二句话可以理解为:将消息“增加拼接一个字符串@"456"发送给消息接收者str。
OC中的消息传递采用动态绑定机制来决定具体调用哪个方法,OC的实例方法在转写为c语言后实际就是一个函数,但是OC并不是在编译期决定调用哪个函数,而是在运行期决定,因为编译期根本不能确定最终会调用哪个函数,这是由于运行期可以修改主方法的实现。
id num = @123;
NSLog(@"%@", num);
[__NSCFNumber appendString:]: unrecognized selector sent to instance 0xad4994905b249f87
[num appendString:@"Hello world"];
编译时没有任何问题,因为id类型可以指向任何类型的实例是,NSString
有一个方法appendString:
,在编译期不确定这个num到底具体指代什么类型的实例对象,并且在运行期还可以给NSNumber类型添加新的方法,因此编译期发现有appendString:
的函数就不会报错。但在运行时,在NSNumber
类中找不到appendString:
方法,就会报错。这也是消息传递的强大之处各弊端,编译期无法检查到未定义的方法,运行期可以添加新的方法。
clang -rewrite-objc main.m
该命令可以将.m的OC文件转写为.cpp文件。
Person * p1 = [Person alloc];
p1 = [p1 init];
p.name = @"fang";
//通过clang命令后,转换为如下代码。
Person * p2 = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
p2 = objc_msgSend(p2, sel_registerName("init"));
objc_msgSend(p2, sel_registerName("setName:"), @"fang");
NSLog(@"%@",p2.name);
OCruntime通过objc_msgSend
函数将一个面向对象的消息传递转为了面积过程的函数调用。
objc_msgSend根据消息的接收者和selector
选择适当的方法来调用。
结构体struct objc_class
中包含一个成员变量 struct objc_method_list **ethodLists
,这个成员变量保存了实例方法列表,struct objc_method_list中包含了一个结构体struct _objc_method,结构体struct _objc_method
包含了以下几个成员变量:结构体struct _objc_method
的大小,方法个数以及最重要的方法列表,方法列表存储的是方法描述结构体strust _objc_method
,该结构体保存了选择器、方法类型以及方法的具体实现。可以看出方法的具体实现就是一个函数指针,也就是自定义的实例方法,选择器selector
可以理解为是一个字符串类型的名称,用于查找对应的函数实现。(苹果没有开源selector
的相关代码,但可以查到GNU OC中关于selector
的定义,也是一个结构体,但是结构体里存储的就是一个字符串类型的名称)。
objc_msgSend
的工作原理
为匹配消息的接收都和选择器,需要在消息的接收者所在的类中去搜索这个 struct objc_method_list
方法列表,如果能找到就可以直接跳转到相关的具体实现中去调用,如果找不到,就会通过super_class
指针沿着继承树向上去搜索,如果找到就跳转,如果到了继承树的根部(通常为NSObject
)还没有找到,那就会调用NSObject
的一个方法doesNotRecognizeSelector:
,这个方法就会报unrecognized selector
错误(消息转发之后。)
这样一次次搜索和静态绑定那样直接跳转到函数指针指向的位置去执行来比肯定是耗时很多的,因此,类对象(结构体struct objc_class
)中有一个成员变量 struct objc_cache
,这个缓存里缓存的正是搜索方法的匹配结果,这样在第二次及以后再访问时就可以采用映射的方式找到相关实现的具体位置。
消息转发
一、所属类动态方法解析
如果沿着继承树没有搜索到相关方法则会向接收者所属类进行一次请求,看是否能够动态添加一个方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"resolveInstanceMethod:%@",NSStringFromSelector(sel));
if (sel == @selector(appendString:)) {
class_addMethod([self class], sel, (IMP)resolveMethod, "v@:");
return NO;
}
return [super resolveInstanceMethod:sel];
}
void resolveMethod(id self, SEL _cmd) {
NSLog(@"%@", NSStringFromSelector(_cmd));
};
//类方法+ (BOOL)resolveClassMethod:(SEL)sel
如果+ (BOOL)resolveInstanceMethod:(SEL)sel
方法返回为NO
,则进入第二步
二、消息转发,备用接收者
当对象所属类不能动态添加方法后,runtime就会询问 接收者是否 有其他对象可以处理这个未知的selector
,- (id)forwardingTargetForSelector:(SEL)aSelector
反回可以接收的对象
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%@",NSStringFromSelector(aSelector));
if (aSelector == @selector(jump:)) {
return [[Person alloc] init];
}
return nil;
}
如果没有其他可以接收消息的对象,- (id)forwardingTargetForSelector:(SEL)aSelector
返回nil
,则进入第三步, 消息重定向
三、消息重定向
当没有备用接收者时,runtime会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
// 获取函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(jump:)) {
return [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
return nil;
}
// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%@",anInvocation);
Person * p = [[Person alloc] init];
[anInvocation invokeWithTarget:p];
}
如果 -methodSignatureForSelector:
返回 nil
。则 Runtime 系统会发出 -doesNotRecognizeSelector:
消息,程序也就崩溃了。
网友评论