OC底层原理(7)---objc_msgSend

200 阅读3分钟

Cache读取流程分析

通过对cache_t的学习,我们了解到类或对象的方法是通过insert操作来加入缓存的,具体什么时候进行insert操作,我们在insert的地方加入断点,查看函数调用栈: image.png

log_and_fill_cache调用了insert: image.png

lookUpImpOrForward调用了log_and_fill_cache

image.png

到此为止,便是C++cache的调用流程。

Runtime的运行时理解

1、Runtime在OC框架中的位置:

image.png

2、调用Runtime的三种方式:

image.png

3、编译OC代码,通过clang 生成 .cpp文件.

xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main.cpp
LGPerson *person = [LGPerson alloc];
[person sayNB];
[person sayHello];

//编译后的结果对照

LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"),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(消息的接受者,消息的主体(sel+参数))

苹果文档地址: developer.apple.com/documentati…

objc_msgSend流程

查找objc_msgSend 汇编源码

image.png

分享个使用的快捷键: command + 鼠标点击一个左侧的箭头 折叠所有项

找到 ENTRY入口,以下是_objc_msgSend的汇编实现以及具体分析:

_objc_msgSend

               ······
	ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
        //p0是消息接受者的地址,这里判断消息接收者是否存在
	cmp	p0, #0			// nil check and tagged pointer check
        // 判断是否支持Taggedpointer类型
#if SUPPORT_TAGGED_POINTERS
        // 如果支持Taggedpointer类型,按照LNilOrTagged处理
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else
        // 如果不支持Taggedpointer类型,直接返回LReturnZero
	b.eq	LReturnZero
#endif
        // p13 = isa [x0]存放的是class
	ldr	p13, [x0]		// p13 = isa
	GetClassFromIsa_p16 p13, 1, x0	// p16 = class
        
  //receiver -> class -> objc_msgSend -> cache
LGetIsaDone:
	// calls imp or objc_msgSend_uncached
	CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
	b.eq	LReturnZero		// nil check
	GetTaggedClass
	b	LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

                ······

上面分析到 GetClassFromIsa_p16 p13, 1, x0 这一步,需要查看这个方法具体是什么,做了哪些事情?

GetClassFromIsa_p16
// GetClassFromIsa_p16 p13, 1, x0;   src=p13=isa   needs_auth=1  auth_address=x0
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */

#if SUPPORT_INDEXED_ISA
	// Indexed isa
	mov	p16, \src			// optimistically set dst = src
	tbz	p16, #ISA_INDEX_IS_NPI_BIT, 1f	// done if not non-pointer isa
	// isa in p16 is indexed
	adrp	x10, _objc_indexed_classes@PAGE
	add	x10, x10, _objc_indexed_classes@PAGEOFF
	ubfx	p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
	ldr	p16, [x10, p16, UXTP #PTRSHIFT]	// load class from array
1:

#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
	mov	p16, \src
.else
	// 64-bit packed isa
        // 操作结束之后,p16是接收者的class
	ExtractISA p16, \src, \auth_address
.endif
#else
	// 32-bit raw isa
	mov	p16, \src

#endif

.endmacro
ExtractISA
.macro ExtractISA
        // $0=p16,$1=src=isa,解释:$1与ISA_MASK进行`与`操作得到的class,存到$0=p16
	and    $0, $1, #ISA_MASK
.endmacro

知识点:

1、p $3[1]$3是指针的时候,代表指针平移一个单位,相当于 $3+1

面试题

下面代码的打印结果:

- (instancetype)init{
    self = [super init];
    if (self) {
        NSLog(@"%@",NSStringFromClass([self class]));
        NSLog(@"%@",NSStringFromClass([super class]));

    }
    return self;
}

如果类是Person,那么打印结果如下:

Person
Person

解析

1、oc和cpp代码对比

使用clang生成cpp文件, clang -rewrite-objc LGPerson.m

上面OC代码对应的C++代码:

static instancetype _I_LGPerson_init(LGPerson * self, SEL _cmd) {
    if (self = ((LGPerson *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGPerson"))}, sel_registerName("init"))) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_vj_4mx0r3r94cb6lxl6pxdh225r0000gn_T_LGPerson_088d3d_mi_0,NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_vj_4mx0r3r94cb6lxl6pxdh225r0000gn_T_LGPerson_088d3d_mi_1,NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGPerson"))}, sel_registerName("class"))));
    }
    return self;
}

精简一下,方便阅读

objc_msgSend(self, sel_registerName("class"));
objc_msgSendSuper({self,class_getSuperclass(objc_getClass("LGPerson"))}, sel_registerName("class"));
2、objc_msgSendobjc_msgSendSuper方法的定义
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

objc_super 结构体

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
3、总结

super 调用 objc_msgSendSuper 告诉系统直接去父类方法列表里面去找,但是调用者主体还是 self,和 self 的区别只是省去了在本类的方法列表中查找的步骤。

[self class][super class]class 这个方法都是 在 NSObject 对象中找到的,所以都相当于 调用 [self class], 输出都是 一样的。