方法的底层原理
我们都知道OC是一门动态的语言,runtime是指我们的程序在运行的过程中可以对于我们的类、对象、属性、方法、变量进行修改,可以改变他们的数据结构,可以添加或者删除一些函数,可以去改变一些变量的值等等的操作.runtime就是可以实现语言动态的一组api,runtime所有都是围绕两个核心
1:类的各方面的动态配置(可以详细看各种的API)
2:消息传递(消息的发送和消息的转发,消息的发送就是runtime通过sel找imp,然后实现对应的方法)
本文就是来看看消息发送objc_msgSend方法,具过程:
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
[p study];
[p happy];
}
return NSApplicationMain(argc, argv);
}
然后我们cd到项目根目录下,输入代码clang -rewrite-objc main.m , 将上面代码输出为.cpp文件。
然后可以看到上面代码转换成:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
LGPerson *p = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("study"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("happy"));
}
return NSApplicationMain(argc, argv);
}
我们发现,alloc这种类方法和study、happy这种对象方法都被包装成了((void (*)(id, SEL))(void *)objc_msgSend)(receiver, sel)格式.
当我们的方法都没有参数,可转换成objc_msgSend后都会带有两个默认的参数消息的接收者receiver、消息的方法名sel.
类方法的接收者是类对象,他就会通过类对象的isa指针找到我们的元类,从元类中找对应的方法;
实例对象方法的接收者是实例对象,他就会通过实例对象的isa指针找到我们的类对象,从类对象中找对应的方法.
当我们的方法有参数时,我们的参数会放在objc_msgSend的第三个参数及以后中。
加上参数和返回值后如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
LGPerson *p = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("study"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("happy"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("say:"), (NSString *)&__NSConstantStringImpl__var_folders_kk_qj63rdg529qgrqh6k3c_cz2r0000gn_T_main_a8c8ed_mi_5);
((BOOL (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("hadEat"));
}
return NSApplicationMain(argc, argv);
}
这里可以看到.cpp文件下,繁多的msgSend方法,正是这些方法够足了消息发送的多样性。
objc_msgSend 和 objc_msgSendSuper 的区别
- (instancetype)init {
if (self = [super init]) {
NSLog(@"%@", [self class]);
NSLog(@"%@", [super class]);
}
return self;
}
.cpp下
static instancetype _I_WTStudent_init(WTStudent * self, SEL _cmd) {
if (self = ((WTStudent *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("WTStudent"))}, sel_registerName("init"))) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_x6_75ftxbbd4t97nl_2t2sh7kww0000gn_T_WTStudent_8d6cff_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_x6_75ftxbbd4t97nl_2t2sh7kww0000gn_T_WTStudent_8d6cff_mi_1, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("WTStudent"))}, sel_registerName("class")));
}
return self;
}
结果可以遇见不论是[self class] 抑或是[super class],都是当前类,而不是打印父类为什么呢 ?
[self class]发送的消息是objc_msgSend,消息接收者是self,方法编号是class
[super class]本质是objc_msgSendSuper, 消息接收者也是self, 方法编号class, 所以[super class]打印出来的一样是self这个对象所指的类,也就是LGStudent。
小结
[super class]中的super只是一个编译指示器,就是让当前对象获取该方法,越过本类,直接从父类找。
消息的接受这还是本类self,所以在执行的时候打印出来的还是本来。
结合之前的章节----方法的快速查找流程
objc_msgSend(receiver,sel…)
- 判断 receiver是否存在
- receiver - isa -class
- class --内存平移 --cache
- cache – buckets
- buckets – 对于sel(如何保证查找速度呢?二分法)
- buckets 有对应的sel – cacheHit - 调用imp
- buckets 没有对应的sel – _objec_msgSend_uncached
类方法?
当前的缓存里面没有该方法----- 方法的慢查找
lookUpImpForward
– 先找当前累的methodList
–父类的cache
–父类的methodList
– 父类为niu
– forward
– Imp