001:对象原理上-alloc流程->cllass_createInstanceFromZone 1:cls->instanceSize 2:calloc 3: obj->initInstanceIsa
目录
1:底层原理探索方法
2:对象地址和指针地址 *
3:苹果源码
4:alloc的流程 ***
5:alloc的作用 **
6:属性影响分配内存大小 *
7:alloc init new 方法的区别
问题
1: p1、p2、p3对象和地址打印都一致, 为何&p打印不一致
alloc
是真正开辟内存和绑定对象的,p1、p2、p3共用1个alloc,所以他们都是指向同一目的地址
。但是他们本身也是对象,在init
时传入他们自身id
,&p
打印的是他们自身的地址。
通俗的说:
我有一个房子出售,A、B、C三个都是我员工,他们都领着客户来看我这套房子。但是他们三个虽然都是我公司员工,但工号(id)不一样。
如果客户问他们房子在哪(
等同于%@
和%p
打印),他们都会告诉我房子的具体位置
(三人说的一定相同)。
如果顾客问他们是谁
(等同于打印&p
),他们就会各自回答A、B、C。
2:alloc流程
1:alloc
2:_objc_rootAlloc
3:callAlloc
4:_objc_rootAllocWithZone
5:_class_createInstanceFromZone
5.1:cls->instanceSize
:计算需要开辟的内存空间大小
5.2:calloc
:申请内存
,返回地址指针
5.3:obj->initInstanceIsa
:将 类 与 isa 关联
6:有个问题:为什么一开始我们调用的alloc 的方法,底层却调用的是objc_alloc
正文
一:底层原理探索方法
1:底层原理探索方法
2:汇编查看流程
1:直接断点调试,bt 打印出堆栈信息
2:符号断点
3:断点+汇编调试
4:源码
二:对象地址和指针地址
%@
打印对象
%p
打印对象地址
&p
打印指针地址,&p1:取出指针地址
p1是对象地址,
&p1是指针地址。
备注
说明对象地址是同一个地址
指针地址&p,是三个不同的指针地址,用&p来取出指针地址。
*p是说明p是一个指针类型。
p是一个变量的名字。
三:苹果源码
四:alloc的流程
断点显示函数调用堆栈信息
1:alloc
2:_objc_rootAlloc
3:callAlloc
4:_objc_rootAllocWithZone
5:_class_createInstanceFromZone
5.1:cls->instanceSize
:计算需要开辟的内存空间大小
5.2:calloc
:申请内存
,返回地址指针
5.3:obj->initInstanceIsa
:将 类 与 isa 关联
1: 首先根据main函数中的LGPerson类的alloc方法进入alloc方法的源码实现(即源码分析开始)
//alloc源码分析-第一步 + (id)alloc { return _objc_rootAlloc(self); }
2:跳转至_objc_rootAlloc的源码实现
id _objc_rootAlloc(Class cls) { return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); }
3: 跳转至callAlloc的源码实现
callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { #if __OBJC2__ //有可用的编译器优化 // checkNil 为false,!cls 也为false ,所以slowpath 为 false,假值判断不会走到if里面,即不会返回nil if (slowpath(checkNil && !cls)) return nil; //判断一个类是否有自定义的 +allocWithZone 实现,没有则走到if里面的实现 if (fastpath(!cls->ISA()->hasCustomAWZ())) { return _objc_rootAllocWithZone(cls, nil); } #endif // No shortcuts available.
// 没有可用的编译器优化 if (allocWithZone) { return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil); } return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)); }
参考:
4:跳转至_objc_rootAllocWithZone
的源码实现
id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)// alloc 源码 第四步 { // allocWithZone under __OBJC2__ ignores the zone parameter //zone 参数不再使用 类创建实例内存空间 return _class_createInstanceFromZone(cls, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC); }
5:跳转至_class_createInstanceFromZone
的源码实现,这部分是alloc源码的核心操作,由下面的流程图及源码可知,该方法的实现主要分为三部分
5.1:cls->instanceSize
:计算需要开辟的内存空间大小
5.2:calloc
:申请内存
,返回地址指针
5.3:obj->initInstanceIsa
:将 类 与 isa 关联
static ALWAYS_INLINE id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, int construct_flags = OBJECT_CONSTRUCT_NONE, bool cxxConstruct = true, size_t *outAllocatedSize = nil)// alloc 源码 第五步 { ASSERT(cls->isRealized()); //检查是否已经实现 // Read class's info bits all at once for performance //一次性读取类的位信息以提高性能 bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); size_t size; //1:计算需要开辟的内存大小,传入的extraBytes 为 0 size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (zone) { obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else { //2:申请内存 obj = (id)calloc(1, size); } if (slowpath(!obj)) { if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { return _objc_callBadAllocHandler(cls); } return nil; } if (!zone && fast) { //3:将 cls类 与 obj指针(即isa) 关联 obj->initInstanceIsa(cls, hasCxxDtor); } else { // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } if (fastpath(!hasCxxCtor)) { return obj; } construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE; return object_cxxConstructFromClass(obj, cls, construct_flags); }
hasCxxCtor()
:当前class或者superclass是否有.cxx_construct 构造方法的实现。hasCxxDtor()
:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑,,如果没有,则可以更快的释放对象。canAllocNonpointer()
:表示是否对 isa 指针开启指针优化。0:纯isa指针;1:不⽌是类对象地址,isa 中还包含了类信息、对象的引⽤计数等。zone
:在iOS8之后,iOS就不再通过zone来申请内存空间了,所以zone传参为nil。1:类名
2:地址
3:类名和地址关联
五:alloc的作用
1:cls->instanceSize
先计算出需要的内存空间大小
2:calloc
向系统申请开辟内存,返回地址指针
3:obj->initInstanceIsa
关联到相应的类
instanceSize
方法计算需要申请多大的内存空间.calloc
方法里对申请的内存大小进行16字节对齐处理,然后按处理后的结果来给对象分配内存空间,并返回内存地址。instanceSize
方法里是按16字节对齐的,那实际分配的内存大小和申请的内存大小相同,如果是按8字节对齐,则不同。六:alloc init new 方法的区别
6.1:init是工程方式初始化方法
+ (id)init { return (id)self; } - (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 方法中是返回的当前对象,也就是自己本身。所以我们可以了解到 init 其实三个工厂方法,可以交给子类去自定义重写该方法
七:new方法:
+ (id)new { return [callAlloc(self, false/*checkNil*/) init]; }
但是我们推荐使用 [alloc init]方法,因为这样我们可以自定义 init方法,使我们的开发更加的灵活。
new方法是alloc和init方法的组合,但是自定义的初始化方法,new方法没有调用。
9: 有个问题:为什么一开始我们调用的alloc 的方法,底层却调用的是objc_alloc
/// Allocate the given objc object. /// call i8* \@objc_alloc(i8* %value) llvm::Value *CodeGenFunction::EmitObjCAlloc(llvm::Value *value, llvm::Type *resultType) { return emitObjCValueOperation(*this, value, resultType, CGM.getObjCEntrypoints().objc_alloc, "objc_alloc"); }
我们通过汇编在llvm编译器里里面找到相关代码
由此得出编译器阶段系统给我做了处理