iOS-OC底层01:alloc init到底做了什么
OC对象
简单案例来开启我们的探索之旅
MyPerson *p1 = [MyPerson alloc];
MyPerson *p2 = [p1 init];
MyPerson *p3 = [p1 init];
LGNSLog(@"%@ - %p - %p",p1,p1,&p1);
LGNSLog(@"%@ - %p - %p",p2,p2,&p2);
LGNSLog(@"%@ - %p - %p",p3,p3,&p3);
打印结果
<MyPerson: 0x600001d86230> - 0x600001d86230 - 0x7ffeea0e50b8
<MyPerson: 0x600001d86230> - 0x600001d86230 - 0x7ffeea0e50b0
<MyPerson: 0x600001d86230> - 0x600001d86230 - 0x7ffeea0e50a8
为什么p1,p2,p3指向同一个对象呢,又为什么指针地址不一样呢。
MyPerson.png
探索底层的方法
我们知道苹果有很多的库,如果我们想探索对象形成的过程,我们就要知道对象形成在哪里库里,下面有三种方法
1.下符号断点的形式直接跟流程
2.通过按住control+step into
3.汇编查看跟流程
1.下符号断点的形式直接跟流程
image.png输入alloc
我们在MyPerson的初始化方法处打断点,另外我们在断点走到MyPerson初始化方法之前先Disable Breakpoint,当走到初始化方法时在Enable Breakpoint.因为在MyPerson初始化之前,项目中有其他的alloc的调用,如果UIViewController等等
如下图我们找到了alloc的库。
image.png
2.通过按住control+step into
在MyPerson初始化方法处打断点,然后control+step into,我们可见
image.png
然后objc_alloc下符号断点,可见开源库
image.png
3.汇编查看跟流程
通过下图开启汇编
image.png
在MyPerson初始化方法处打断点,可见
image.png
然后取objc_alloc下符号断点,下面操作和 通过按住control+step into相同.
定位objc源码
苹果开源源码汇总: https://opensource.apple.com
这个地址用的更直接 https://opensource.apple.com/tarballs/
编译源码,可参考https://github.com/LGCooci/objc4_debug
alloc init到底做了什么
123.pngalloc 关键方法分析
image.pngid _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
//此时allocWithZone为true, checkNil为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));
}
slowpath(x):(__builtin_expect(bool(x), 0))
fastpath(x):(__builtin_expect(bool(x), 1))
__builtin_expect 这个指令是gcc引入的,作用是允许程序员将最有可能执行的分支告诉编译器。这个指令的写法为:__builtin_expect(EXP, N)。
意思是:EXP==N的概率很大。
cls->ISA()->hasCustomAWZ()
bool hasCustomAWZ() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
bool getBit(uint16_t flags) const {
return _flags & flags;
}
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_CACHE_HAS_DEFAULT_AWZ (1<<14)
类或超类具有默认的alloc / allocWithZone:实现 注意,这存储在元类中。
因为没有实现所以!cls->ISA()->hasCustomAWZ())判断为true
计算申请内存大小
extraBytes为0
size = cls->instanceSize(extraBytes);
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;
}
// __builtin_constant_p.
// Gcc的内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0
bool hasFastInstanceSize(size_t extra) const
{
//因为extra不是常数,所以 _flags & FAST_CACHE_ALLOC_MASK
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
}
return _flags & FAST_CACHE_ALLOC_MASK;
}
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);
}
}
16禁止对齐。如果是17,(17 +15)%16*16 = 32,如果是16
(16 +15)%16*16 = 16
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
开辟内存空间
obj = (id)calloc(1, size);
我们在这里打印obj ,po obj 结果是内存地址0x000000010070cd40
关联类
obj->initInstanceIsa(cls, hasCxxDtor);
我们在这里打印obj ,po obj 结果 <LGPerson: 0x10070cd40>
探索NSObject 初始化方法
[NSObject alloc] 没有调用alloc 方法是调用
objc_alloc(Class cls) ->callAlloc(cls, true, false);
[[NSObject alloc] init]调用也是没有调用alloc方法
objc_alloc_init->[callAlloc(cls, true, false) init]
new 源码探索
一般在开发中,初始化除了init,还可以使用new,两者本质上并没有什么区别,以下是objc中new的源码实现,通过源码可以得知,new函数中直接调用了callAlloc函数(即alloc中分析的函数),且调用了init函数,所以可以得出new 其实就等价于 [alloc init]的结论
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
一般开发中并不建议使用new,主要是因为有时会重写init方法做一些自定义的操作,用new初始化可能会无法走到自定义的部分