这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战
在我们日常开发过程中,对象初始化应该是我们每个人经常遇到的,但是对象初始化过程中具体做了哪些事情,也许很多人不明所以,所以我们来探究一下对象初始化的过程。
首先,我们通过一个案例看一下
我们创建了一个 LGPerson
对象,对 p1
做 alloc
操作,对 p2
、p3
分别做 init
操作,然后分别打印 p1
、p2
、p3
及它们的地址与指针的地址,然后我们可以看到p1
、p2
、p3
的地址都一样,但是他们的指针地址却不一样。可以得出结论:
1.LGPerson
类在执行完 alloc
方法之后就开辟了内存空间,有了指针的指向。
2.p2
、p3
与p1
指向的内存相同,说明 init
方法没有对指针做操作。
3.在执行完init
方法后我们通过对p1
、p2
、p
3指针取地址打印,可以看到指针地址并不相同,且是以8个字节的间隔连续存储在栈空间。
现在我们想点进去看看 alloc
跟 init
方法的具体实现,我们可以看到都是只有声明没有实现。这里我们对 alloc
方法打断点,通过 Debug Workflow 生成汇编代码可以看到,这里调用的是 objc_alloc
方法。
allco 方法源码调用流程
我们知道底层调用的是objc_alloc
方法,我们下载objc
的开源库来看下具体的实现。
[LGPerson alloc]
首先第一步在起始位置打上断点。
[NSObject alloc]
第二步我们看到来到了NSObject
类的alloc
方法,由于所有类都是继承于NSObject
类。
objc_alloc
这一步我们会遇到一些问题,因为第二步命名调用的是 _objc_rootAlloc
放到,但是这里却来到了 objc_alloc
方法里面。那么为什么会走到这里了呢?我们在如下代码中可以找到答案。
callAlloc
这里可以看到在 1931 行有个判断,第一次调用的时候并没有走到 1932 行,而是在 1940 行对 cls
也就是 LGPerson
类发送了 alloc
方法。
_objc_rootAlloc
这里就会先进入 alloc
方法,然后调用 _objc_rootAlloc
方法。
_objc_rootAllocWithZone
现在就再次进入了 callAlloc
方法,这也是 callAlloc
方法调用两次的原因。这次会走到 1932 行,调用 _objc_rootAllocWithZone
方法。
_class_createInstanceFromZone
在 _objc_rootAllocWithZone
方法之后会进入到 _class_createInstanceFromZone
方法,在这个方法里面我们着重关注几个地方,在 7977 行这个会计算出 LGPerson
类相应的内存大小;7984 行会在堆区开辟出 size
大小的内存空间;7994 会绑定 isa
与 LGPerson
类相关联, 并赋值了 hasCxxDtor
c++ 相关的方法与函数;8002 行返回 obj
对象。
那么问题来了,是如何知道该开辟多大的内存空间呢?具体我们来看一下 instanceSize
方法。
**
inline size_t instanceSize(size_t extraBytes) const {
// 如果有缓存会返回缓存的 size 大小
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
// 如果没有缓存,会通过 alignedInstanceSize() + extraBytes 这一步来计算 size 大小
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
通过 instanceSize
方法我们可以看到,如果有缓存会返回缓存的 size 大小, 如果没有就会通过 alignedInstanceSize() + extraBytes 这一步来计算 size 大小。开辟空间的大小与类的成员变量有关。继承于 NSObject
的类都会有一个 isa
成员变量。如果当前类没有其他成员变量,返回的 size 大小就是 isa
的大小8 字节。但是因为 8 自己小于 16 字节,根据字节对齐原则,最后返回的是 16 字节。
**
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
通过代码也可以看到,isa
是 Class
类型,objc_class
是一个结构体类型。isa
是结构体指针类型,所以是 8 个字节。objc_class
继承与最原始的类型 objc_object
。
最后我们通过打印可以看到对象内存的首字节就 isa
及当前对象的内存情况。
allco 方法调用流程图
可以看到依次走入顺序
objc_alloc
→ callAlloc
→ objc_msgSend
→ alloc
→ _objc_rootAlloc
→ callAlloc
→ _objc_rootAllocWithZone