以下內(nèi)容以至少你已經(jīng)理解OC內(nèi)萬(wàn)物皆對(duì)象
的概念為基礎(chǔ),當(dāng)然你還得有一份可以跑得objc源碼
1. Obj before born
在我們還沒(méi)有書寫代碼創(chuàng)建對(duì)象時(shí),內(nèi)存內(nèi)已經(jīng)滿是對(duì)象(類,元類)了.
2. Obj 誕生 alloc
2.1 申請(qǐng)堆空間
//C code
typedef struct{
char name[21];
char age;
}CustomStruct;
typedef CustomStruct * CustomStructPointer;
int main(int argc, const char * argv[]) {
CustomStructPointer stu = (CustomStructPointer)malloc(sizeof(CustomStruct));
stu->age = 10;
strcpy(stu->name, "pogong");
printf("stack address %p\n",&stu);
printf("heap address %p\n",stu);
free(stu);
return 0;
}
打印:
stack address 0x7fff5fbff708
heap address 0x100403ff0
//OC code
//PGCustomClass.h
@interface PGCustomClass : NSObject
@property(nonatomic,copy)NSString * name;
@property(nonatomic,assign)int age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
PGCustomClass * obj = [[PGCustomClass alloc]init];
NSLog(@"stack address %p",&obj);
NSLog(@"heap address %p",obj);
}
return 0;
}
打印:
stack address 0x7fff5fbff728
heap address 0x101a02bc0
以上是C語(yǔ)言的一個(gè)棧上的結(jié)構(gòu)體指針
指向堆上的結(jié)構(gòu)體實(shí)例
的代碼+內(nèi)存示意圖和OC的一個(gè)棧上的對(duì)象指針
指向堆上的對(duì)象實(shí)例
的代碼+內(nèi)存示意圖.
因?yàn)镺C的對(duì)象說(shuō)到底還是個(gè)結(jié)構(gòu)體實(shí)例,所以O(shè)C的對(duì)象生成的結(jié)果和C語(yǔ)言生成結(jié)構(gòu)體指針指向結(jié)構(gòu)體實(shí)例
的結(jié)果是一樣的.當(dāng)然OC的對(duì)象生成過(guò)程會(huì)比較復(fù)雜,因?yàn)镺C可是優(yōu)雅的動(dòng)態(tài)語(yǔ)言誒!以下就是曲折的誕生過(guò)程:
alloc
像內(nèi)的調(diào)用棧大概如上圖所示,看代碼的捋很久,也不需要全都記住,主要知道幾個(gè)關(guān)鍵參數(shù),關(guān)鍵條件和關(guān)鍵實(shí)現(xiàn)就可以了.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
1.hasCustomAWZ
存在于類的元類中,標(biāo)識(shí)這個(gè)類有沒(méi)有復(fù)寫alloc/allocWithZone:
;
2.canAllocFast
是否支持快速創(chuàng)建.
可以看出最終都調(diào)用了_class_createInstanceFromZone
static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
assert(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
}
else {
if (zone) {
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (!obj) return nil;
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
幾大判斷條件:
hasCxxCtor
:類及父類是否有自己的構(gòu)造函數(shù);
hasCxxDtor
:類及父類是否有自己的析構(gòu)函數(shù)(這個(gè)條件在后面講對(duì)象dealloc
的時(shí)候也會(huì)說(shuō)到,與對(duì)象是否有實(shí)例變量有關(guān),這條件會(huì)記錄在對(duì)象的isa
內(nèi));
fast:類是否用了是優(yōu)化的isa
;
canAllocNonpointer and SUPPORT_NONPOINTER_ISA
兩個(gè)都帶nonpointer,
SUPPORT_NONPOINTER_ISA是來(lái)標(biāo)識(shí)當(dāng)前平臺(tái)是否支持優(yōu)化的isa
,但即使平臺(tái)支持,具體到某一個(gè)類卻是不一定的,要具體的去驗(yàn)證類的元類的信息.不過(guò)可以放心大多系統(tǒng)的類的isa
都是支持優(yōu)化的,我們自定義的類的isa
也是支持優(yōu)化的.
canAllocNonpointer
則是具體標(biāo)記某個(gè)類是否支持優(yōu)化的isa
.
在閱讀源碼時(shí)還有會(huì)各種帶nonpointer
字樣的針對(duì)優(yōu)化isa的標(biāo)記,除SUPPORT_NONPOINTER_ISA外,全是針對(duì)某個(gè)類而言的.
優(yōu)化的isa
是什么?接下來(lái)會(huì)說(shuō).
zone:老版本中要先去看看zone是否有空間,在OBJC2下巩梢,忽略zone參數(shù).
size:這由外圍傳入,size存儲(chǔ)在類的元類內(nèi)
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
size_t size = cls->instanceSize(extraBytes);
data()->ro->instanceSize;
上方的注解May be unaligned depending on class's ivars.
.類實(shí)例的instanceSize
取決于類的中成員變量的個(gè)數(shù):
再看看最后的調(diào)用:calloc
或者malloc_zone_calloc
就和C語(yǔ)言在堆中申請(qǐng)空間如出一轍了.
2.2 isa init
-
isa
沒(méi)那么簡(jiǎn)單,因?yàn)閮?yōu)化了
在part1內(nèi)已經(jīng)提過(guò)了多遍的isa
,當(dāng)然只要知道OC內(nèi)萬(wàn)物皆對(duì)象
,也肯定知道類實(shí)例->類->元類
用isa串聯(lián)起來(lái)的關(guān)系:
但具體到真實(shí)的應(yīng)用場(chǎng)景下,isa
的串聯(lián)會(huì)比上圖描繪更復(fù)雜更具體一些,特別是在64位系統(tǒng)上.所有用了64位系統(tǒng)的電子產(chǎn)品都沒(méi)有用全64位來(lái)表示地址.
因?yàn)檫@不現(xiàn)實(shí):32位==>4G內(nèi)存,64位==>你算算看.
64位不全拿來(lái)表示地址,這就給64位的isa
留下了很大的優(yōu)化空間(32位時(shí)對(duì)象的isa
只是指向類而已).
我們截取類實(shí)例到類的過(guò)程來(lái)說(shuō)明優(yōu)化的isa
先找到關(guān)于
id
的定義:
typedef struct objc_object *id;
然后objc_object又是什么:
struct objc_object {
private:
isa_t isa;
}
然后再看isa_t是什么(這里只看arm64的):
union isa_t
{
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
};
}
這個(gè)union isa_t
新奇了,聯(lián)合體
少見吧!更奇怪的是聯(lián)合體還嵌套了結(jié)構(gòu)體
,有不明白的請(qǐng)戳.
簡(jiǎn)單的說(shuō)就是:Class cls
+uintptr_t bits
+struct{......}
共用一塊64位的內(nèi)存空間,當(dāng)然只有一個(gè)有效,在SUPPORT_NONPOINTER_ISA
為1的情況下,仍然有一些類不支持優(yōu)化的isa
,所以這樣的union isa_t
就支持多用:
Class cls->為未優(yōu)化版的isa指向一個(gè)類
uintptr_t bit+struct{......}
uintptr_t bit用于對(duì)64位統(tǒng)一賦值,
struct{......}做細(xì)化讀取與細(xì)化賦值
請(qǐng)注意這聯(lián)合體內(nèi)結(jié)構(gòu)體內(nèi)的這個(gè)字段shiftcls
,shiftcls
=shift class
,短的類地址.union isa_t
共計(jì)64位,shiftcls
占33位.這就是一個(gè)操作系統(tǒng)地址變量?jī)?yōu)化的細(xì)節(jié).在64位iPhone上只拿33位表示地址的,也就是說(shuō)這的shiftcls
就存儲(chǔ)了類實(shí)例歸屬的類的地址.如圖:
當(dāng)然類對(duì)象
指向元類對(duì)象
也是一樣的道理.
除了shiftcls
之外,isa_t
內(nèi)的各個(gè)字段均有用處,這些也就是64位的isa
具體優(yōu)化的地方:
nonpointer:1->表示使用優(yōu)化的isa指針
has_assoc:1->是否包含關(guān)聯(lián)對(duì)象
has_cxx_dtor:1->是否包含析構(gòu)函數(shù)
shiftcls:33->類的指針
magic:6->固定值,用于判斷是否完成初始化
weakly_referenced:1->對(duì)象是否指向一個(gè)弱引用對(duì)象
deallocating:1->對(duì)象是否正在銷毀
has_sidetable_rc:1->在extra_rc存儲(chǔ)引用計(jì)數(shù)將要溢出的時(shí)候,借助sidetable(散列表)存儲(chǔ)引用計(jì)數(shù),has_sidetable_rc設(shè)置成1
extra_rc:19->存儲(chǔ)引用計(jì)數(shù)
后面章節(jié)的文章會(huì)細(xì)說(shuō)關(guān)于這些字段所實(shí)現(xiàn)和優(yōu)化的功能.
- 初始化對(duì)象的isa
初始化對(duì)象的isa
要么initInstanceIsa->initIsa
,要么直接調(diào)用initIsa->initIsa
.
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
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;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
SUPPORT_NONPOINTER_ISA
前面已經(jīng)說(shuō)過(guò),而SUPPORT_INDEXED_ISA 為 1
是另外一種優(yōu)化,用isa內(nèi)indexcls存儲(chǔ)著類在類列表內(nèi)的索引,這個(gè)用在watch
上,手機(jī)和電腦上沒(méi)有這么用.
所以再看objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
的實(shí)現(xiàn)就簡(jiǎn)單多了.
不支持nonpointer
的,isa.cls = cls;
支持nonpointer
的,
對(duì)newisa.bits
賦值,即對(duì)isa
的64位統(tǒng)一初始化賦值,(統(tǒng)一初始化賦值)
newisa.has_cxx_dtor
記錄傳入的has_cxx_dtor
,(細(xì)化賦值)
newisa.shiftcls
記錄下cls
的地址.(細(xì)化賦值)
newisa.shiftcls = (uintptr_t)cls >> 3;(為什么右移3位?)
拿手機(jī)舉例子:shiftcls:33;(shiftcls會(huì)分配到33位),在64位的手機(jī)上拿33位保存類的地址,但因?yàn)槲粚?duì)齊的緣故,所有地址都是8的倍數(shù),所有地址書寫的成二進(jìn)制數(shù)最后3位全是0,所以才如上見到:cls >> 3;(消除了3個(gè)沒(méi)有影響的0)
3. Obj 裝扮 init
- (id)init {
return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
return obj;
}
在沒(méi)有復(fù)寫init
方法的情況下,init
的實(shí)現(xiàn)特別簡(jiǎn)單.
- (instancetype)init
{
self = [super init];
if (self) {
_name = @"pogong";
_age = 28;
}
return self;
}
復(fù)寫init
的情況下能做的也只是對(duì)類實(shí)例
的成員變量
的初始化裝扮
.
當(dāng)然這樣的工作不在init
內(nèi)部也能完成.
4. Obj 怪胎 Tagged Pointer
事情是是要從32位系統(tǒng)轉(zhuǎn)向64系統(tǒng)說(shuō)起.
32位系統(tǒng)下:
NSNumber * num = [[NSNumber alloc]initWithInt:1];
棧上
4個(gè)字節(jié)的對(duì)象指針
指向堆上
8個(gè)字節(jié)(存儲(chǔ)isa4個(gè)字節(jié)+存儲(chǔ)值4個(gè)字節(jié))的對(duì)象實(shí)例
,共計(jì)12個(gè)字節(jié).
64位系統(tǒng)下:
NSNumber * num = [[NSNumber alloc]initWithInt:1];
棧上
8個(gè)字節(jié)的對(duì)象指針
指向堆上
16個(gè)字節(jié)(存儲(chǔ)isa8個(gè)字節(jié)+存儲(chǔ)值8個(gè)字節(jié))的對(duì)象實(shí)例
,共計(jì)24個(gè)字節(jié).
保存一個(gè)int
要用8個(gè)字節(jié),包裝成對(duì)象要24字節(jié),有點(diǎn)太浪費(fèi)了.
所以Tagged Pointer
應(yīng)運(yùn)而生,
NSNumber *number1 = @1;
NSNumber *number2 = @2;
NSNumber *number3 = @3;
NSNumber *numberFFFF = @(0xFFFF);
NSLog(@"number1 pointer is %p", number1);
NSLog(@"number2 pointer is %p", number2);
NSLog(@"number3 pointer is %p", number3);
NSLog(@"numberffff pointer is %p", numberFFFF);
打印:
number1 pointer is 0xb000000000000012
number2 pointer is 0xb000000000000022
number3 pointer is 0xb000000000000032
numberffff pointer is 0xb0000000000ffff2
我們前面已經(jīng)講過(guò),因?yàn)?4位系統(tǒng)上8位對(duì)齊
,16進(jìn)制打印出的地址最后一位不是8就是0(2進(jìn)制打印后三位全是0),而這里最后一位是2,很怪異,這就是對(duì)Tagged Pointer
的標(biāo)記.再將標(biāo)記位的前面的數(shù)值和對(duì)象本身的值進(jìn)行比較一模一樣.
Tagged Pointer
就是將值
與Tagged Pointer的標(biāo)記
混在一塊64位的內(nèi)存內(nèi).看上去是對(duì)象,但卻沒(méi)有isa
(一個(gè)沒(méi)有靈魂的對(duì)象==>Tagged Pointer
).但索性現(xiàn)在的isa
也不能直接被調(diào)用,所以不會(huì)造成什么不便.
棧上
8個(gè)字節(jié)的對(duì)象指針
指向堆上
8個(gè)字節(jié)(Tagged Pointer)的對(duì)象實(shí)例
,共計(jì)16個(gè)字節(jié).
Tagged Pointer
的引入,節(jié)約了64位系統(tǒng)的內(nèi)存,提高了運(yùn)行效率.
除NSNumber
外,NSDate
,NSString
都應(yīng)用到Tagged Pointer
.