知識點概要
引入
alloc干了什么聊疲?
為什么要有init茬底?
為什么要有new?直接使用有何缺陷获洲?
[NSObject alloc]為什么沒有進(jìn)入alloc方法阱表?
引入
Person *p1 = [[Person alloc]init];
Person *p2 = [p1 init];
Person *p3 = [p1 init];
NSLog(@"%@ - %p - %p",p1,p1,&p1);
NSLog(@"%@ - %p - %p",p2,p2,&p2);
NSLog(@"%@ - %p - %p",p3,p3,&p3);
p1、p2贡珊、p3結(jié)果相同嗎最爬?&p1、&p2门岔、&p3結(jié)果相同嗎爱致?
答案是:前者相同,后者不同寒随,輸出結(jié)果如下:
我們來回顧C語言指針的知識:
int temp = 3;//定一個int類型變量
int *a = &temp;//聲明一個int類型的指針仇轻,指向的地址是temp的內(nèi)存地址
//直接輸出a和直接輸出&temp的結(jié)果一樣,都是temp的內(nèi)存地址萤晴,而&a表示的是a的內(nèi)存地址载碌,因為指針類型也是一個變量,需要空間去存儲
printf("%p,%p,%p\n",a,&a,&temp);
那么蒲讯,對于&p1忘朝、&p2、&p3結(jié)果不相同就可以明白了判帮,話不多說局嘁,上圖:
至于p1溉箕、p2、p3調(diào)用init之后結(jié)果相同悦昵,是接下來要探索的問題肴茄。
alloc干了什么?
怎么查看但指?
1.下符號斷點
1)我們知道會調(diào)用alloc方法寡痰,那么,先打上alloc符號斷點
可以看到棋凳,會調(diào)用NSObject類中的alloc方法拦坠,因為我們自定義的類繼承自NSObject
2)在上一步中,可以看到j(luò)mp指令剩岳,會進(jìn)入_objc_rootAlloc贞滨,所以,繼續(xù)為_objc_rootAlloc打上斷點
3)繼續(xù)按照之前的方式拍棕,符號斷點_objc_rootAllocWithZone
4)可以看到會調(diào)用callq指令晓铆,調(diào)用了runtime中的instanceSize、calloc方法
調(diào)用instanceSize方法绰播,計算需要開辟多大的內(nèi)存骄噪,接著會調(diào)用calloc開辟這樣一個大小的空間。最后蠢箩,會調(diào)用initInstanceIsa方法链蕊,將對象與這個空間關(guān)聯(lián)
2.根據(jù)符號斷點查找?guī)欤缓竺γⅲヌO果開源庫下載源碼示弓,看源碼,下面介紹三種方法查看呵萨。
1)第一種就是符號斷點
2)在你要進(jìn)入的位置奏属,打上斷點,然后潮峦,按住control鍵囱皿,點擊調(diào)試按鈕的“step into”,多點幾次忱嘹,會跳入另外一個斷點
打上符號斷點嘱腥,點擊下一步,如下圖拘悦,我們就知道庫名是objc
3)打上斷點齿兔,debug選為匯編模式,找到對應(yīng)的方法objc_alloc,打符號斷點
總結(jié):
1.通過符號斷點分苇,可以看到alloc的流程是:
alloc-->_objc_rootAlloc-->_objc_rootAllocWithZone-->retrun obj
_objc_rootAllocWithZone方法會調(diào)用核心的三個方法:
instanceSize:計算空間大小添诉,并進(jìn)行16字節(jié)對齊
calloc:根據(jù)大小,開辟一個這樣的內(nèi)存空間
initInstanceIsa:將開辟的內(nèi)存空間與對象關(guān)聯(lián)
注意:上述的三個核心方式其實不在_objc_rootAllocWithZone里面調(diào)用医寿,而是在_class_createInstanceFromZone方法中調(diào)用的栏赴,原因稍后再說。
2.通過源碼靖秩,調(diào)用流程如下:
alloc—>_objc_rootAlloc—>callAlloc—>_objc_rootAllocWithZone-->_class_createInstanceFromZone
可以看到须眷,callAlloc、_class_createInstanceFromZone沒有被調(diào)用沟突。因為在編譯時花颗,編譯器會根據(jù)指令進(jìn)行優(yōu)化,直接進(jìn)入_objc_rootAllocWithZone
從代碼及注釋中都可以看到惠拭,調(diào)用時捎稚,會被優(yōu)化
3.一個對象的大小,與其屬性有關(guān)求橄,屬性越多,占用的空間越多葡公,且按照16自字節(jié)的方式對齊(現(xiàn)在的新版本都是16字節(jié)對齊罐农,之前是8字節(jié)對齊。為什么要對齊催什?讀取按照一定字節(jié)讀取涵亏,不去計算,會更加快速安全)蒲凶。注意气筋,沒有屬性的對象,最小占用16字節(jié)大小旋圆,
init干了什么宠默?為什么要有init?
我們直接上源碼
可以看到灵巧,直接將對象返回搀矫,啥也沒有做,不是我們想的會初始化空間值刻肄。
為什么要有init瓤球?
1.提供一個方法,對外給開發(fā)者使用敏弃,可以讓開發(fā)者定義自己個性的初始化內(nèi)容卦羡。
2.單一職責(zé)原則,與開辟內(nèi)存空間功能分開
為什么要有new?有何缺陷绿饵?
new是一個類方法欠肾,調(diào)用簡單,實際是alloc+init蝴罪。
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
缺陷是:我們自定義了init董济,使用new不會調(diào)用我們自定義的init方法,造成一個對象的創(chuàng)建出問題要门。例如
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
self.className = name;
}
return self;
}
探討[NSObject alloc]為什么沒有進(jìn)入alloc方法虏肾?
其實,無論是NSObject還是它的子類欢搜,調(diào)用alloc或者[[XXX alloc]init],優(yōu)先調(diào)用以下代碼:
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
// Calls [[cls alloc] init].
id
objc_alloc_init(Class cls)
{
return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init];
}
那么封豪,我么進(jìn)入callAlloc方法看看
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
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));
}
我們知道,現(xiàn)在是objc2.0炒瘟,所以會進(jìn)入第一個分支吹埠。首先不為nil,其次調(diào)用cls->ISA()->hasCustomAWZ()疮装,看源碼:
#if FAST_CACHE_HAS_DEFAULT_AWZ
bool hasCustomAWZ() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
void setHasDefaultAWZ() {
cache.setBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
void setHasCustomAWZ() {
cache.clearBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
#else
bool hasCustomAWZ() const {
return !(bits.data()->flags & RW_HAS_DEFAULT_AWZ);
}
void setHasDefaultAWZ() {
bits.data()->setFlags(RW_HAS_DEFAULT_AWZ);
}
void setHasCustomAWZ() {
bits.data()->clearFlags(RW_HAS_DEFAULT_AWZ);
}
#endif
判斷本類是有默認(rèn)alloc/allocWithZone方法缘琅。很顯然,NSObject實現(xiàn)了廓推,所以刷袍,直接調(diào)用_objc_rootAllocWithZone方法創(chuàng)建了對象。當(dāng)然了樊展,如果是子類呻纹,而子類沒有默認(rèn)alloc/allocWithZone方法,則會走消息發(fā)送objc_msgSend专缠,調(diào)用alloc方法雷酪,源碼如下:
+ (id)alloc {
return _objc_rootAlloc(self);
}
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
走消息發(fā)送,最終會調(diào)用父類NSObject的alloc方法涝婉。此時哥力,流程和之前流程一樣,callAlloc->_objc_rootAllocWithZone
總結(jié)
1.為什么明明調(diào)用是alloc方法嘁圈,實際調(diào)用的是objc_alloc方法省骂?此時此刻,不要在心里一萬頭草泥馬飄過最住,冷靜下來钞澳。自己沒有更改,源碼中也沒有更改涨缚,那么轧粟,答案就只有一種了策治,編譯器做了優(yōu)化。在這里可以在LLVM源碼中找到答案兰吟,具體過程就不說了通惫,太難了,知道就行混蔼。
2.在不考慮優(yōu)化的情況下履腋,alloc的詳細(xì)流程如下: