官方文档及资源地址
Documentation Archive
Apple Open Source
查看runtime开源文件 arm64 位
objc-msg-arm64.s - ARM64 code to support objc messaging
将Object-C 语言转换为C++:
xcrun -sdk iphonesimulator clang -rewrite-objc main.m
以上指令 会生成.cpp文件 查看cpp文件代码 与官方源码 得知
objc_msgSend 是用汇编写的。
C语言不能通过写一个函数,去跳转到任意的指针,汇编可以利用寄存器实现,C语言使用“静态绑定”,也就是说,在编译时就能决定运行时所应调用的函数,如果待调用的函数地址无法硬编码在指令之中,那就要在运行期读取出来,使用“动态绑定”,我们都知道c语言是面向过程,由编译器进行处理,显然无法实现这样的需求。而runtime是运行时,在运行的时候会进行特殊操作访问不同的内存空间,因此oc具备动态特性。
1.对象及方法本质
@autoreleasepool {
YJPerson * P = [YJPerson new];
[P run];
}
//编译后 (环境依赖部分代码暂不考虑 此处没有粘贴出来)
#pragma clang assume_nonnull begin
#ifndef _REWRITER_typedef_YJPerson
#define _REWRITER_typedef_YJPerson
typedef struct objc_object YJPerson;
typedef struct {} _objc_exc_YJPerson;
#endif
struct YJPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
YJPerson * P = ((YJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YJPerson"), sel_registerName("new"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)P, sel_registerName("run"));
}
void runImp (id self ,SEL _cmd){
}
将.m文件转换为C++文件 即可得出
- 对象的本质:结构体, 占用内存大小
- 方法的本质就是 _objc_msgSend 发消息
接下来 看下 消息传递与转发。
2.消息传递
消息发送 _objc_msgSend
void objc_msgSend(id self, SEL cmd, ...)
//接受两个或两个以上的参数,第一个参数代表接收者,第二个参数代表SEL(SEL是选择子的类型),后续参数就是消息中的那些参数.编译器会进行转换
id returnValue = objc_msgSend(someObject,@selector(messageName:), parameter);
以下几个概念需要搞清
- objc_class
重要成员(也都是结构体 建议看下源码 很有意思的)
- objc_method_list($)
方法列表 - objc_cache($)
缓存列表 method_name:method_imp 。 key:value的形式 - 结构体里保存了指向父类的指针、类的名字、版本、实例大小、实例变量列表、方法列表、缓存、遵守的协议列表等
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
struct objc_method_list {
struct objc_method_list *obsolete;
int method_count;
#ifdef __LP64__
int space;
#endif
/* variable length structure */
struct objc_method method_list[1];
};
struct objc_method {
SEL method_name;
char *method_types; /* a string representing argument/return types */
IMP method_imp;
};
- 源码部分分析
调用 objc_msgSend 后 系统会进行一系列的复杂操作
- 首先,通过 obj 的 isa 指针找到它的 class ;
- 在 class 的 method list 找 对应的 func ;
- 如果 class 中没到 func,继续往它的 superclass 中找 ;
- 一旦找到 func 这个函数,就去执行它的实现IMP .
由于每个消息都需要遍历一次,效率会比较低。
objc_class 中另一个重要成员 objc_cache把经常被调用的函数缓存下来,大大提高函数查询的效率。把 func 的 method_name 作为 key ,method_imp 作为 value 给存起来。
当再次收到 func 消息的时候,直接在 cache 里找到,避免去遍历 objc_method_list
接下来看下详细的具体流程
- ENTRY _objc_msgSend 入口
判断接收者recevier是否为空,为空则返回,不为空,就处理isa。
Objective-C 是一门面向对象的语言,对象又分为实例对象、类对象、元类对象以及根元类对象。它们是通过一个叫 isa 的指针来关联起来,具体关系如下图:
案例点击可查看
// _objc_msgSend 入口
ENTRY _objc_msgSend
// 窗口
UNWIND _objc_msgSend, NoFrame
// tagged pointer 特殊 数据类型 数据非常小
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
建议仔细的看下这个完整的文件 相信自己能看懂 就行。官方代码逻辑非常的清晰。
首先进行缓存检查和类型判断
LNilOrTagged
taggedPoint:存储小值类型,地址中包含值和类型数据,能进行快速访问数据,提高性能
LGetIsaDone
通过汇编指令b LGetIsaDone跳转到CacheLookup,对缓存进行快速的查找,如果有缓存就直接返回,由于这一步是通过汇编执行,所以是快速查找,效率很高(这里存在查找的过程)
CacheLookup 分为三种
- CacheHit
找到了,则调用CacheHit进行call or return imp - CheckMiss
找不到 __objc_msgSend_uncached - add
别的地方找到了这imp就进行add操作,方便下一次快速的查找。
MethodTableLookup
如果来到这里 说明在缓存里面不存在
先找自己,如果自己没有IMP,然后找父类的缓存,如果没有,循环递归查找父类的IMP,一直找到NSObject,如果还是没有,接下来就开始动态方法解析,如果动态方法解析没有实现,接下来再调用消息转发,流程如下,核心方法----- lookUpImpOrForward
底层源码如下
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup 缓存中有IMP,直接返回
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
//runtimeLock 加锁 保证线程安全(数据安全) 保证 旧数据不再重新填充
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertLocked();
// Try this class's cache. 缓存中有IMP,直接返回
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists. 1 找自己的IMP,找到加入方法缓存
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists. 2 找父类 这一步大致与上一步相同 只是找的是 父类 上一步是自己
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.内存溢出
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
// 没有IMP,调用一次resolver动态方法解析,通过triedResolver变量来控制该方法只走一次
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.消息转发
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
消息传递:底层的确很复杂,涉及到寄存器位运算,下面是流程图
流程图
func没有找到,通常情况下,程序会在运行时挂掉并抛出 unrecognized selector sent to … 的异常。但在异常抛出前,Objective-C 的运行时会有三次拯救程序的机会。继续看消息转发的过程
3.消息转发
对象在收到消息无法处理,将调用resolver解析
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
1.+ (BOOL)resolveInstanceMethod:(SEL)selector;
对象在收到无法解读的消息后调用此函数,参数就是那个未知的SEL(字符编码),其返回值为Boolean类型,表示这个类是否能新增一个处理此SEL的方法。让你有机会提供一个函数实现。
如果你添加了函数并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。此方法常用来实现@dynamic属性、访问CoreData框架中NSManagedObjects对象
如果 resolve 方法返回 NO ,运行时就会移到下一步:消息转发
+(BOOL)resolveInstanceMethod:(SEL)sel{
//方法名
NSString *selStr = NSStringFromSelector(sel);
if ([selStr isEqualToString:@"name"]) {
//增加name方法的实现
class_addMethod(self, sel, (IMP)nameGetter, "@@:");
return YES;
}
if ([selStr isEqualToString:@"setName:"]) {
class_addMethod(self, sel, (IMP)nameSetter, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
// 或者 runtime api
IMP fooIMP = imp_implementationWithBlock(^(id _self) {
NSLog(@"Doing foo");
});
class_addMethod([self class], aSEL, fooIMP, "v@:");
2.- (id)forwardingTargetForSelector:(SEL)aSelector
接收者有第二次机会处理未知的SEL,就是把这条消息转给其他接收者来处理,这一步无法操作转发的消息。如要修改或者处理的话就需要触发完整的消息转发机制
-(id)forwardingTargetForSelector:(SEL)aSelector{
NSString *selStr = NSStringFromSelector(aSelector);
//companyName,则处理转发
if ([selStr isEqualToString:@"companyName"]) {
//返回处理这个转发的对象
return self.companyModel;
}else{
return [super forwardingTargetForSelector:aSelector];
}
}
3.- (void)forwardInvocation:(NSInvocation *)anInvocation
这一步是 Runtime 最后一次给你挽救的机会,启用完整的消息转发机制,创建NSInvocation对象:(SEL、目标及参数).
首先它会发送 -methodSignatureForSelector: 消息获得函数的参数和返回值类型如果返回了一个函数签名,Runtime 就会创建一个 NSInvocation 对象并发送 -forwardInvocation: 消息给目标对象,
触发NSInvocation对象时,“消息派发系统”将亲自出马,把消息指派给目标对象.
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *sig = nil;
NSString *selStr = NSStringFromSelector(aSelector);
//判断你要转发的SEL
if ([selStr isEqualToString:@"deptName"]) {
//此处返回的sig是方法forwardInvocation的参数anInvocation中的methodSignature
//为你的转发方法手动生成签名
sig = [self.companyModel methodSignatureForSelector:@selector(deptName:)];
}else{
sig = [super methodSignatureForSelector:aSelector];
}
return sig;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSString *selStr = NSStringFromSelector(anInvocation.selector);
if ([selStr isEqualToString:@"deptName"]) {
//设置处理转发的对象
[anInvocation setTarget:self.companyModel];
//设置转发对象要用的方法
[anInvocation setSelector:@selector(deptName:)];
BOOL hasCompanyName = YES;
//第一个和第二个参数是target和sel
[anInvocation setArgument:&hasCompanyName atIndex:2];
[anInvocation retainArguments];
[anInvocation invoke];
}else{
[super forwardInvocation:anInvocation];
}
}
细节:
resolveInstanceMethod 此函数会调用俩次。
第一次:先走方法 _objc_msgSend_uncached,然后走方法 lookUpImpOrForward,再走方法 _class_resolveInstanceMethod,从这个大致的流程可以知道,这个流程,就是上面所分析的流程,寻找 imp的过程,没有找到就动态解析
第二次:消息转发流程