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);
打印結(jié)果
<MyPerson: 0x600001d86230> - 0x600001d86230 - 0x7ffeea0e50b8
<MyPerson: 0x600001d86230> - 0x600001d86230 - 0x7ffeea0e50b0
<MyPerson: 0x600001d86230> - 0x600001d86230 - 0x7ffeea0e50a8
為什么p1,p2,p3指向同一個對象呢预麸,又為什么指針地址不一樣呢滔驶。
探索底層的方法
我們知道蘋果有很多的庫,如果我們想探索對象形成的過程什燕,我們就要知道對象形成在哪里庫里粘勒,下面有三種方法
1.下符號斷點(diǎn)的形式直接跟流程
2.通過按住control+step into
3.匯編查看跟流程
1.下符號斷點(diǎn)的形式直接跟流程
輸入alloc
我們在MyPerson的初始化方法處打斷點(diǎn),另外我們在斷點(diǎn)走到MyPerson初始化方法之前先Disable Breakpoint,當(dāng)走到初始化方法時在Enable Breakpoint.因?yàn)樵贛yPerson初始化之前屎即,項目中有其他的alloc的調(diào)用庙睡,如果UIViewController等等
如下圖我們找到了alloc的庫。
2.通過按住control+step into
在MyPerson初始化方法處打斷點(diǎn)技俐,然后control+step into乘陪,我們可見
然后objc_alloc下符號斷點(diǎn),可見開源庫
3.匯編查看跟流程
通過下圖開啟匯編
在MyPerson初始化方法處打斷點(diǎn)雕擂,可見
然后取objc_alloc下符號斷點(diǎn)啡邑,下面操作和 通過按住control+step into相同.
定位objc源碼
蘋果開源源碼匯總: https://opensource.apple.com
這個地址用的更直接 https://opensource.apple.com/tarballs/
編譯源碼,可參考https://github.com/LGCooci/objc4_debug
alloc init到底做了什么
alloc 關(guān)鍵方法分析
id _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引入的谤逼,作用是允許程序員將最有可能執(zhí)行的分支告訴編譯器。這個指令的寫法為:__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)
類或超類具有默認(rèn)的alloc / allocWithZone:實(shí)現(xiàn) 注意,這存儲在元類中纹坐。
因?yàn)闆]有實(shí)現(xiàn)所以!cls->ISA()->hasCustomAWZ())判斷為true
計算申請內(nèi)存大小
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的內(nèi)建函數(shù) __builtin_constant_p 用于判斷一個值是否為編譯時常數(shù)枝冀,如果參數(shù)EXP 的值是常數(shù),函數(shù)返回 1耘子,否則返回 0
bool hasFastInstanceSize(size_t extra) const
{
//因?yàn)閑xtra不是常數(shù)果漾,所以 _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);
}
開辟內(nèi)存空間
obj = (id)calloc(1, size);
我們在這里打印obj ,po obj 結(jié)果是內(nèi)存地址0x000000010070cd40
關(guān)聯(lián)類
obj->initInstanceIsa(cls, hasCxxDtor);
我們在這里打印obj 片林,po obj 結(jié)果 <LGPerson: 0x10070cd40>
探索NSObject 初始化方法
[NSObject alloc] 沒有調(diào)用alloc 方法是調(diào)用
objc_alloc(Class cls) ->callAlloc(cls, true, false);
[[NSObject alloc] init]調(diào)用也是沒有調(diào)用alloc方法
objc_alloc_init->[callAlloc(cls, true, false) init]
new 源碼探索
一般在開發(fā)中端盆,初始化除了init怀骤,還可以使用new,兩者本質(zhì)上并沒有什么區(qū)別焕妙,以下是objc中new的源碼實(shí)現(xiàn)蒋伦,通過源碼可以得知,new函數(shù)中直接調(diào)用了callAlloc函數(shù)(即alloc中分析的函數(shù))焚鹊,且調(diào)用了init函數(shù)痕届,所以可以得出new 其實(shí)就等價于 [alloc init]的結(jié)論
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
一般開發(fā)中并不建議使用new,主要是因?yàn)橛袝r會重寫init方法做一些自定義的操作末患,用new初始化可能會無法走到自定義的部分