准备工作
- macOS 10.15.6
- Xcode 11.7
- 最新官方源码objc4-781无报错调试版
开始探究
创建对象:
添加断点,ctrl+strp into进入后,可以看到
alloc
实际上调用了objc_alloc
函数继续向下分析,就用到了官方下载的源码objc4-781,源码下载后再进行debug调试的时候,也是花了很久的功夫,这里就不详细说明。有关编译和调试的问题可以留言或私信
来到源码中,我们可以很快定位到alloc
的上层代码如下
+ (id)alloc {
return _objc_rootAlloc(self);
}
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
找到第一个核心方法callAlloc
,代码如下
#define ALWAYS_INLINE inline __attribute__((always_inline))
#define NEVER_INLINE __attribute__((noinline))
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
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));
}
这里想要继续往下,我们只有添加断点通过调试才能确定判断下一步,笔者经过断点确定了是_objc_rootAllocWithZone
,跟_objc_rootAllocWithZone
最后来到_class_createInstanceFromZone
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);
}
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;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
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;
}
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);
}
这个时候,我们已经接近真相了,alloc
究竟为我们做了什么
*initInstanceIsa
计算申请的内存大小
*calloc
申请内存地址指针
*initInstanceIsa
将申请的内存地址指针和isa指针即我们的类绑定起来
探究过程的细节思考和知识联想
在学习知识的时候,我们往往都是碎片化的理解和记忆,不妨每次给自己几分钟做一些思考和联想,让知识串联起来。如果文章中出现细节错误,可以留言指出来或者私信给我。思考出错不可怕,可怕的是我们思考了吗?
1,关于callAlloc方法中的条件判断,slowpath
和fastpath
,这里应该是编译器的优化,我们在项目target > Build Setting > Apple Clang - Code Generation可以发现其踪迹。
2,关于initInstanceIsa
中计算申请的内存大小,我们可以通过调试得知申请的地址都是16的倍数,这里字节对齐的单元是16,至于为什么要字节对齐,应该是为了降低cpu的读取性能损耗和访问的安全性。给类加了属性,利用lldb中x/4gx来打印类的地址,又可以发现申请的内存大小跟类的属性关联。
3,init 和 new ,在源码中init做的事情是return _objc_rootInit(self)
,init把我们alloc 申请的类的指针给了对象,并且返回对象自己。init构造方法,并且可以重写。new做的事情是return [callAlloc(self, false/*checkNil*/) init]
,new就相当于 alloc+init
,但是new不能像init一样进行重写。