探索的准备工作
- 准备好objc-781编译成功的源码工程;
- 新建调试Target -> YYTest;
- 在YYTest的main函数中,调用alloc方法创建一个NSObject实例,并打下断点;
开始调试探索NSObject的alloc底层实现
1. 首先会调用objc_alloc
方法;
id objc_alloc(Class cls){
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
2.内部调用的是callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/)
,传入了三个参数,来到callAlloc的函数实现;
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
//判断类是否为空
if (slowpath(checkNil && !cls)) return nil;
//判断当前类是否有自定义的+allocWithZone实现.
//默认是实现了allocWithZone方法
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));
}
-
slowpath
与fastpath
与编译器的优化相关,其可以提高编译器的工作效率,后面再做详细阐述; -
!cls->ISA()->hasCustomAWZ()
:判断当前类的cache_t中是否存在alloc/allocWithZone方法;
bool hasCustomAWZ() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
- 其中
#define FAST_CACHE_HAS_DEFAULT_AWZ (1<<14)
表示当前类,或者其父类是否有alloc/allocWithZone
方法, - 由于是第一次调用alloc方法,所以缓存cache_t中不存在alloc/allocWithZone方法,但NSObject是系统类,缓存中是存在alloc/allocWithZone方法的,但自定义类缓存中是不存在的;
3. 接着调用 _objc_rootAllocWithZone
函数,进入其函数实现;
id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
4. 调用 _objc_rootAllocWithZone
函数,内部调用 _class_createInstanceFromZone
函数,其函数实现如下:
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)
{
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.计算实例对象所占内存大小
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
//2.为实例对象申请内存空间并返回地址指针
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
//3.为实例对象创建一个isa,并将isa与其所属的类class进行绑定
if (!zone && fast) {
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);
}
-
_class_createInstanceFromZone
是实例化对象的核心函数
,其内部实现包含三个主要步骤: -
第一步:计算实例对象所占内存大小(instanceSize);
size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
//CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
-
instanceSize()的函数实现:
- 首先去缓存
cache_t
中去获取实例对象的内存大小,调用cache.hasFastInstanceSize()
判断能否从缓存中快速获取内存大小,若可以则调用cache.fastInstanceSize()
,返回内存大小; - 缓存未命中,调用
alignedInstanceSize()
函数去获取内存大小; -
若对象的内存大小 小于16字节,则修正为16字节,表明一个OC对象至少会占用16个字节的内存空间
;
- 首先去缓存
经调试主流程进入第一步,调用
cache.fastInstanceSize()
去获取实例对象的内存大小;
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
//remove the FAST_CACHE_ALLOC_DELTA16 that was added
//by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
- 然后进入
align16()
函数,此函数使用位运算
算法完成16字节对齐
,表明OC对象的内存大小的计算采用的是16字节对齐的算法
;
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
- 未进入缓存时的逻辑,会调用
alignedInstanceSize()
函数其实现如下:
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
define WORD_MASK 7UL
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
可以看出此函数使用
位运算
算法完成8字节对齐
;-
第二步:为实例对象申请内存空间并返回地址指针
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
主流程走的是
obj = (id)calloc(1, size)
调用calloc函数申请内存空间,并返回地址指针,从 iOS底层系列08 -- malloc与calloc源码分析,可知计算实例对象分配的内存空间
采用的是16字节对齐算法;-
第三步:为实例对象创建一个isa,并将isa与其所属的类class进行绑定
if (!zone && fast) {
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);
}
- 主流程走
obj->initInstanceIsa(cls, hasCxxDtor)
其内部实现如下:
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
- 最后来到
initIsa(cls, true, hasCxxDtor)
,其创建了isa_t对象然后与class建立联系,最后赋值给实例对象的isa属性;
inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
//创建一个isa_t对象
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
//isa_t属性与class的绑定关系建立
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
//将创建isa_t赋值给对象的isa属性,实现了对象isa的初始化,即实现了对象与class的绑定
isa = newisa;
}
}
- 上述探索的是
objc_781版本
[NSObject alloc]底层执行逻辑,后来又研究了objc_818版本
的源码,存在一些差异,下面是objc_818版本 [NSObject alloc]
的底层实现流程图:
计算实例对象的内存大小的逻辑总结
- 首先在计算
实例对象实际所占内存空间
存在两个分支逻辑:- 第一个分支:直接从
cache_t
中获取,采用的是16字节对齐算法; - 第二个分支:调用
alignedInstanceSize()
方法,采用的是8字节对齐算法;
- 第一个分支:直接从
- 其次是根据实例对象实际所占内存空间大小,去计算实例对象分配的内存空间大小,调用
calloc
函数,采用的是16字节对齐算法;
探索init的底层实现
- 源码如下所示:
// Replaced by CF (throws an NSException)
+ (id)init {
return (id)self;
}
- (id)init {
return _objc_rootInit(self);
}
- 类方法,直接返回本身;
- 实例方法,内部调用了_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;
}
- 也是返回其自身;
- 从这里可看出alloc方法计算对象内存大小,分配内存创建实例对象,实例对象绑定类关系完成了大部分工作,而
init方法仅仅只是返回实例对象本身而已
探索new的底层实现
- 源码如下所示:
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
- 看到new方法内部调用了alloc底层方法+init方法,所以
new = alloc + init
;
图解16字节对齐算法
- 代码实现如下:
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}