OC 是面向对象的语言,开发中一切的基础,首先需要一个对象,😆,没有的话,可以 alloc 一个。这篇文章就记录一下探索alloc 内部流程和实现。
objc源码官方地址,本次下载的是objc4-787.1版本。
一、 外层
从工程中 alloc
点击 jump to definition
之后,发现 alloc
的.h
声明,再点击就进不去了,这时可以发现是在objc
的代码下的。如下图所见:
可以知道 objc
的代码是开源的,可以在苹果的开发者网站去下载(找不到的话,可以搜索)。下载完成后,我一打开,懵逼了啊,那么多代码,我是完全找不到alloc的实现啊。o(╥﹏╥)o
还回到原来的工程中,找一个类的 alloc
方法,打个 alloc
的符号断点,如下图:
在类的的实例化的时候也打个普通断点,这样让代码先运行到alloc的地方再进入符号断点,具体流程:
- disabled掉
alloc
符号断点 - 运行程序,代码会停到类的
alloc
地方 eg:GLPerson *p = [GLPerson alloc];
- enable
alloc
符号断点 - 运行,停到
alloc
的符号断点 -
step into instruction
,每次点击可以看到在libobjc
中调用的方法名称
第一次点击 step into instruction
会看到[NSObject alloc]
第二次点击 step into instruction
会看到_objc_rootAlloc
, 下面又有个jmp,对应_objc_rootAllocWithZone
持续点击 step into instruction
可以继续查看...
现在可以根据 _objc_rootAlloc
和 _objc_rootAllocWithZone
去探索一下objc的源码工程,看能不能找到alloc的实现信息
二、源码层
直接搜索 _objc_rootAlloc
看看有什么
搜索结果里面有个NSObject.mm文件--- _objc_rootAlloc(Class cls)
方法的实现,
里面直接返回 callAlloc
调用。
2.1 _objc_rootAlloc -> callAlloc
然后咱们分析如下:
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
// 会进入 __OBJC2__ 因为现在OC版本就是2.0
#if __OBJC2__
// slowpath和fastpath可以理解为语气助词,对理解代码没有关系
// checkNil传过来的是false(其实不用管这个),只要cls是有值的,就不会返回nil
if (slowpath(checkNil && !cls)) return nil;
// 该类是否实现了allocWithZone方法,否,会调用_objc_rootAllocWithZone
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// No shortcuts available.
if (allocWithZone) { // allocWithZone为nil,所以不会走这里
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
// 如果已经开辟过内存了,就会来到这里
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
最后分析结果会走进 _objc_rootAllocWithZone
方法
2.2 _objc_rootAllocWithZone
先搜索下找到函数实现,结果如下
_objc_rootAllocWithZone搜索.png发现有2个实现,一个在 objc-class-old.mm
中,一个在 objc-runtime-new.mm
中,看新的吧,因为咱们用的是OC2.0版本,跟着苹果的步伐走。从上图中注释也能看出来
// allocWithZone under __OBJC2__ ignores the zone parameter
2.3 _class_createInstanceFromZone
_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(); // 判断当前class或者superclass 是否有.cxx_construct构造方法的实现
bool hasCxxDtor = cls->hasCxxDtor(); // 判断当前class或者superclass 是否有.cxx_destruct析构方法的实现
bool fast = cls->canAllocNonpointer(); // 是对 isa 的类型的区分,如果一个类和它父类的实例不能使用isa_t 类型的 isa 的话,fast 就为 false,但是在 Objective-C 2.0 中,大部分类都是支持的
size_t size; // 声明内存空间大小
size = cls->instanceSize(extraBytes); // 获取内存大小
if (outAllocatedSize) *outAllocatedSize = size; // outAllocatedSize没传默认是nil
id obj;
if (zone) { // zone是nil
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size); // 分配内存空间,calloc( )函数会默认的把申请出来的空间初始化为0或者nil
}
if (slowpath(!obj)) { // 判断是否分配成功
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) { // zone是nil,fast是true
obj->initInstanceIsa(cls, hasCxxDtor); // 初始化实例的isa指针为cls类对象
} 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);
}
总结:通过上面查找分析,现在可以明白alloc的内部流程,
alloc内部调用流程.png其实主要是 _class_createInstanceFromZone
的实现流程,可以分3步:
- 获取实例的内存空间大小:
cls->instanceSize()
- 根据内存大小,分配内存空间,让实例指向内存开始地址:
calloc
- 关联isa,实例的isa指向类:
obj->initInstanceIsa(cls, hasCxxDtor)
三、_class_createInstanceFromZone内部
3.1 cls->instanceSize()
获取实例的内存大小,怎么获取呢?先找下内部调用流程
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
// 这些方法都在struct objc_class : objc_object 的结构体中,
// data()和ro()的数据会在map_Images的时候完成,instanceSize会根据类有多少属性,返回相加之后的结果
return data()->ro()->instanceSize;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
// word_align是字节对齐方法,下面有分析
return word_align(unalignedInstanceSize());
}
size_t instanceSize(size_t extraBytes) const {
// 应为在map_Images的时候会调用realizeClassWithoutSwift,这里面会调用setInstanceSize,setInstanceSize里面cache.setFastInstanceSize
// 所以会走if, 通过cache.fastInstanceSized进行16字节对齐, extraBytes 为0
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;
},
// 注意:setInstanceSize内部设置了cache.setFastInstanceSize(newSize)
void setInstanceSize(uint32_t newSize) {
ASSERT(isRealized());
ASSERT(data()->flags & RW_REALIZING);
auto ro = data()->ro();
if (newSize != ro->instanceSize) {
ASSERT(data()->flags & RW_COPIED_RO);
*const_cast<uint32_t *>(&ro->instanceSize) = newSize;
}
cache.setFastInstanceSize(newSize);
}
因为在 map_images
的时候会调用 realizeClassWithoutSwift
方法,该方法内部会调用 setInstanceSize
,就会设置 cache.setFastInstanceSize(newSize)
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
所以分析出 instanceSize
里面的调用流程,instanceSize
→ cache.fastInstanceSize(extraBytes)
→ align16(size + extra - FAST_CACHE_ALLOC_DELTA16)
可以知道最后分配的内存都是以16字节对齐的,
可以简单验证一下:
@interface GLPerson : NSObject
@end
---
#import <objc/runtime.h> // class_getInstanceSize
#import <malloc/malloc.h> // malloc_size
int main(int argc, char * argv[]) {
GLPerson *p = [GLPerson alloc];
NSLog(@"%zd %zd", class_getInstanceSize([GLPerson class]), malloc_size((__bridge const void *)(p)));
}
class_getInstanceSize // 返回某个类对象至少需要多少内存空间
malloc_size // 返回实际分配的内存空间
console: 8 16
通过上面代码,GLPerson
本身不带属性,因为 NSObject
自带有默认 isa
,所以一个继承于 NSObject
的类对象至少需要8个字节,但是返回的是16字节。可验证上面的16字节对齐。
现在可以知道,根据类的属性,在计算各个属性所占用的大小后相加可知总内存,总内存最后再16字节对齐,返回的就是这个类对象真正开辟的内存大小。
但是这时,有个疑问,系统是怎么计算,然后相加的,这块可能比较复杂,专门写一篇介绍下。
3.2 calloc
开辟给定大小的内存空间,返回首地址,calloc内部流程怎么走的 --> iOS底层探索之内存对齐和calloc
3.3 obj->initInstanceIsa
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
// 会继续调用 initIsa
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
// nonpointer 传过来的是true,会走else
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
// 这里声明并初始化一个newisa
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;
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; // 这个地方把cls和newisa做了关联
#endif
isa = newisa;
}
}
关联isa的流程:initInstanceIsa
→initIsa
→newisa.shiftcls = (uintptr_t)cls >> 3;
三、init & new
如上分析 alloc
一样,可在 objc
中找到 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
直接就是返回了 self
。
new的调用
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
总结:new
的调用省略了alloc
、_objc_rootAlloc
,直接调用 callAlloc
,然后init
。isa的后续分析。
网友评论