1、Runtime
1.1、Runtime :Objective-C运行时,指代码跑起来了.被装载到内存中去的过程,具有动态性,能够进行消息传递、动态方法解析、消息转发、类型编码、声明属性等一系类操作。
1.2、Runtime有两个版本:一个现在(modern)版本,一个传统(legacy)版本
传统版本对应的编程接⼝:Objective-C 1.0。
现⾏版本对应的编程接⼝:Objective-C 2.0 。
传统版本⽤于Objective-C 1.0, 32位的Mac OS X的平台上。
现⾏版本:iPhone程序和Mac OS X v10.5 及以后的系统中的 64 位程序 。
1.3、Runtime交互
Objective-C程序可在三个不同
的层次上与Runtime进行交互:通过Objective-C源代码、Foundation框架的NSObject类中定义的方法、直接调用运行时函数。
2、objc_msgSend
解释:发送一个带有简单返回值的消息到一个类的实例
//发送一个带有简单返回值的消息到一个类的实例。
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
objc_msgSend:
self-->self指向接收消息的类实例的指针
op-->处理消息的方法的选择器
...-->方法参数的变量参数列表
//发送一个带有简单返回值的消息到类的实例的超类。
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
objc_msgSendSuper:
super-->指向一个objc_super数据结构的指针。传递标识 消息被发送到的上下文,包括接收消息的类的实例
消息和开始搜索方法实现的超类。
op-->SEL类型的指针。传递将处理消息的方法的选择器。
...-->方法参数的变量参数列表
接下来通过源码来查看:
//main函数中方法的调用
#import
#import
@interface LGTeacher : NSObject
- (void)sayHello;
@end
@implementation LGTeacher
- (void)sayHello{
NSLog(@"666 %s",__func__);
}
@end
@interface LGPerson : LGTeacher
- (void)sayHello;
- (void)sayNB;
@end
@implementation LGPerson
- (void)sayNB{
NSLog(@"666");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
LGTeacher *teacher = [LGTeacher alloc];
[person sayNB];
[person sayHello];
}
return 0;
}
通过Clang查看源码:
__OBJC_RW_DLLIMPORT void objc_msgSend(void);
__OBJC_RW_DLLIMPORT void objc_msgSendSuper(void);
__OBJC_RW_DLLIMPORT void objc_msgSend_stret(void);
__OBJC_RW_DLLIMPORT void objc_msgSendSuper_stret(void);
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
LGTeacher *teacher = ((LGTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGTeacher"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
当遇到方法调用时,编译器生成对对应
objc_msgSend 、 objc_msgSend_stret, objc_msgSendSuper、 objc_msgSendSuper_stret。
其中
发送到超类的消息使用objc_msgSendSuper
其他消息发送使用 objc_msgSend
将数据结构作为返回值的方法
objc_msgSendSuper_stret
objc_msgSend_stret
在main方法中调用objc_msgSend ,objc_msgSendSuper,此时发现使用clang会报错,无法生成main.cpp文件
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
LGTeacher *teacher = [LGTeacher alloc];
[person sayNB];
[person sayHello];
//2.消息发送
objc_msgSend(person, sel_registerName("sayNB"));
struct objc_super lgsuper;
lgsuper.receiver = person;//消息的接收者还是person
lgsuper.super_class = [LGTeacher class];//告诉父类是谁
objc_msgSendSuper(&lgsuper, sel_registerName("sayHello"));
}
return 0;
}
objc_super结构
```#ifndef OBJC_SUPER
#define OBJC_SUPER
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
#endif
receiver:消息的接收者
super_class:指定消息的接收者超类
输出打印:
2021-06-27 16:36:12.851501+0800 001-运行时感受[8467:207092] 666
2021-06-27 16:36:12.851894+0800 001-运行时感受[8467:207092] 666 -[LGTeacher sayHello]
2021-06-27 16:36:12.851951+0800 001-运行时感受[8467:207092] 666
2021-06-27 16:36:12.851992+0800 001-运行时感受[8467:207092] 666 -[LGTeacher sayHello]
总结:
[person sayNB]等价于objc_msgSend(person, sel_registerName("sayNB"))
无论是[person sayHello]还是objc_msgSendSuper(&lgsuper, sel_registerName("sayHello"))执行的都是父类中的实现
3.objc_msgSend源码查看
搜索objc_msgSend,打开源码文件objc-msg-arm64.s
_objc_msgSend
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check 判断接受者是否存在
#if SUPPORT_TAGGED_POINTERS //是否支持tagged_pointers对象
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero //直接返回空 LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone: //获取isa完毕LGetIsaDone
// calls imp or objc_msgSend_uncached
//开启缓存查找流程
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
LNilOrTagged
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged: //小对象或者空判断
b.eq LReturnZero // nil check 直接返回空了LReturnZero
GetTaggedClass
b LGetIsaDone //小对象ISA处理
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
CacheLookup
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
ldr p10, [x16, #CACHE] // p10 = mask|buckets
lsr p11, p10, #48 // p11 = mask
and p10, p10, #0xffffffffffff // p10 = buckets
and w12, w1, w11 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
and p10, p11, #0x0000fffffffffffe // p10 = buckets
tbnz p11, #0, LLookupPreopt\Function
#endif
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
ldr p11, [x16, #CACHE] // p11 = mask|buckets
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel != _cmd) {
b.ne 3f // scan more
// } else {
2: CacheHit \Mode // hit: call or return imp
// }
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
// wrap-around:
// p10 = first bucket
// p11 = mask (and maybe other bits on LP64)
// p12 = _cmd & mask
//
// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
// So stop when we circle back to the first probed bucket
// rather than when hitting the first bucket again.
//
// Note that we might probe the initial bucket twice
// when the first probed slot is the last entry.
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
// p13 = buckets + (mask << 1+PTRSHIFT)
// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p13, p10, p11, LSL #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
// do {
4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel == _cmd)
b.eq 2b // goto hit
cmp p9, #0 // } while (sel != 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
LLookupEnd\Function:
objc_msgSend总体流程图: