前言
相关文章:
iOS底层探索九(方法的本质下objc_msgSend慢速及方法转发初探)
开发中我们经常会用到XZPerson *p= [[XZPerson alloc]init],我们只知道,这样我们就新建了一个对象,可以直接使用这个对象,可以对这个对象进行赋值使用,但是我们没有关心过alloc方法底层到底是通过什么方式进行实现的;今天我准备对alloc方法进行一次尝试性底层挖掘;首先我们需要先准备一份可编译的objc4_752代码,可以直接进行下载探索;
首先,先上一张我探索完成的alloc流程图,供大家进行参考
然后我们直接使用能运行 objc-752
源码后,在 main.m
中写入如下代码就开始了 alloc
的探索。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
XZPerson *object = [XZPerson alloc];
NSLog(@"Hello, World! %@",object);
}
return 0;
}
打断点直接这里 alloc
点击进入的为 _objc_rootAlloc
其实我们断点后发现进入的是 objc_alloc
。
这里我们稍作解释:我们可以看到源码中有这么一个方法fixupMessageRef,这里应该是对进行符号绑定将alloc绑定为objc_alloc方法,具体是通过LLVM期进行符号绑定,我们这篇文章先不进行详细介绍;
fixupMessageRef(message_ref_t *msg)
{
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == SEL_alloc) {
msg->imp = (IMP)&objc_alloc;
} else if (msg->sel == SEL_allocWithZone) {
msg->imp = (IMP)&objc_allocWithZone;
} else if (msg->sel == SEL_retain) {
msg->imp = (IMP)&objc_retain;
} else if (msg->sel == SEL_release) {
msg->imp = (IMP)&objc_release;
} else if (msg->sel == SEL_autorelease) {
msg->imp = (IMP)&objc_autorelease;
} else {
msg->imp = &objc_msgSend_fixedup;
}
}
else if (msg->imp == &objc_msgSendSuper2_fixup) {
msg->imp = &objc_msgSendSuper2_fixedup;
}
else if (msg->imp == &objc_msgSend_stret_fixup) {
msg->imp = &objc_msgSend_stret_fixedup;
}
else if (msg->imp == &objc_msgSendSuper2_stret_fixup) {
msg->imp = &objc_msgSendSuper2_stret_fixedup;
}
#if defined(__i386__) || defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fpret_fixup) {
msg->imp = &objc_msgSend_fpret_fixedup;
}
#endif
#if defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fp2ret_fixup) {
msg->imp = &objc_msgSend_fp2ret_fixedup;
}
#endif
}
1.首先我们可以看到进入的是 alloc 内部调用_objc_rootAlloc方法绩效调用callAlloc方法
+ (id)alloc {
return _objc_rootAlloc(self);
}
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
这里我们可以看到进入_objcrootAlloc方法里面有参数为Class的一个参数
Class 类底层其实是objc_class类结构体
typedef struct objc_class *Class;
objc_class结构体中
struct objc_class : objc_object {
// Class ISA; //isa指针
Class superclass; //父类
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
//...下面还有很多方法就不在这里贴了,可以自行下代码进行查看
objc_class
又继承于 objc_object
,objc_object
存储了 isa
的一个 Class
结构体
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;
这里如果有使用过RunTime的同学就可以看到一些熟悉的东西 ivars, methodLists ,protocols,这个后期文章我们再进行赘述,此篇文章先进行稍微引入
2.当进入callAlloc方法中
这里我都有备注判断的原因,如有不对还请看到的各位,评论留言我们可以一起探讨,这里如果我们自己打断点的话,可以看到大部分会直接走到class_createInstance方法中;
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
/**
fastpath(x)表示x很可能不为0,希望编译器进行优化;
slowpath(x)表示x很可能为0,希望编译器进行优化——这里表示cls大概率是有值的,编译器可以不用每次都读取 return nil 指令
*/
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
/**
实际意义是hasCustomAllocWithZone——这里表示有没有alloc / allocWithZone的实现(只有不是继承NSObject/NSProxy的类才为true)
*/
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
/**
内部调用了bits.canAllocFast后分为2种情况
if(FAST_ALLOC){
}else{
默认为false,
}
当我们查看FAST_ALLOC宏时,发现它的上方有个else 1 之后才是else中定义这个宏,
也就是说这个宏一直是不存在的
可以自行点击进入查看
*/
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
3.class_createInstance方法中,跟踪class_createInstance(cls, 0)方法,其内部调用了_class_createInstanceFromZone方法,并在其中进行size计算,内存申请,以及isa初始化
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
_class_createInstanceFromZone这个方法是核心方法,进行了开辟内存和关联对象
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
assert(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
// 获取需要开辟的内存大小
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {
//这里是真正的创建类的方法但是这个calloc源码是在malloc中
obj = (id)calloc(1, size);
if (!obj) return nil;
//将内存指向给objc进行关联
obj->initInstanceIsa(cls, hasCxxDtor);
}
else {
if (zone) {
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (!obj) return nil;
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
其中cls->instanceSize(extraBytes)此方法为计算应该开辟多大内存方法,计算出size,
其中64位系统下,对象大小采用8字节对齐,但是实际申请的内存最低为16字节,也就是说系统分配内存按照16字节对齐分配
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;//最小返回16字节
return size;
}
uint32_t alignedInstanceSize() {
//字节对齐
return word_align(unalignedInstanceSize());
}
# define WORD_MASK 7UL //这个宏标示数字7
static inline uint32_t word_align(uint32_t x) {
//WORD_MASK 7
//7+8 = 15
//0000 0111 2进制表示7
//0000 1111 2进制表示15
//1111 1000 2进制表示~7
//0000 1000 15 & ~7 值 16
//这里主要就是做8进制对齐,这里相当于8倍数
//x + 7
//x=1 8
//x=2 16
return (x + WORD_MASK) & ~WORD_MASK;
}
字节对齐核心算法:
例如传值进来为7 已知 WORD_MASK为7
//WORD_MASK 7
7+8 = 15
0000 0111 2进制表示7
0000 1111 2进制表示15
1111 1000 2进制表示~7
---------------------------------
/0000 1000 (15 & ~7 ) 值 16
这里主要就是做8进制对齐,这里相当于8倍数,当然这里的WORD_MASK如果是15那么这个就是16字节对齐
根据不同的条件,使用calloc或者malloc_zone_calloc进行内存申请,并且初始化isa指针,至此size大小的对象obj已经申请完成,并且返回;
4.这就OC中alloc底层的调用方式,我们都看了alloc的源码了,顺便看下init方法吧;
- (id)init {
return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}
我们可以看出来,在init方法中苹果开发人员没有做任何东西,只是将对象直接返回,说明这只是苹果开发人员为我们提供的一个工厂方法,让我们方便进行重写;
5.顺便把New方法也看下
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
仔细看过alloc源码的话,这里就很清晰了,callAlloc不就是调用了下alloc方法的底层么,顺便也调用了下init,这就是说,new方法底层也是调用alloc的,
总结
这是个人的实际追踪源码得出的一些结论,源码中还有一些分支代码可以自行查看initInstanceIsa下篇文章在进行分析,如果有错误的地方还请指正,大家一起讨论,开发水平一般,还望大家体谅,欢迎大家点赞,关注我的CSDN,我会定期做一些技术分享!
写给自己:
研究源码枯燥的,但是面对源码不用害怕,一步步把它拆分开来研究,多看看官方给的注释/Github大神的注释,慢慢来,总有一自己也会厉害的。