以下內(nèi)容以至少你已經(jīng)理解OC內(nèi)萬物皆對象的概念為基礎(chǔ),當(dāng)然你還得有一份可以跑得objc源碼
1. Obj before born
在我們還沒有書寫代碼創(chuàng)建對象時,內(nèi)存內(nèi)已經(jīng)滿是對象(類,元類)了.
2. Obj 誕生 alloc
2.1 申請堆空間
//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語言的一個棧上的結(jié)構(gòu)體指針指向堆上的結(jié)構(gòu)體實例的代碼+內(nèi)存示意圖和OC的一個棧上的對象指針指向堆上的對象實例的代碼+內(nèi)存示意圖.
因為OC的對象說到底還是個結(jié)構(gòu)體實例,所以O(shè)C的對象生成的結(jié)果和C語言生成結(jié)構(gòu)體指針指向結(jié)構(gòu)體實例的結(jié)果是一樣的.當(dāng)然OC的對象生成過程會比較復(fù)雜,因為OC可是優(yōu)雅的動態(tài)語言誒!以下就是曲折的誕生過程:

alloc像內(nèi)的調(diào)用棧大概如上圖所示,看代碼的捋很久,也不需要全都記住,主要知道幾個關(guān)鍵參數(shù),關(guān)鍵條件和關(guān)鍵實現(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)識這個類有沒有復(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ù)(這個條件在后面講對象dealloc的時候也會說到,與對象是否有實例變量有關(guān),這條件會記錄在對象的isa內(nèi));
fast:類是否用了是優(yōu)化的isa;
canAllocNonpointer and SUPPORT_NONPOINTER_ISA
兩個都帶nonpointer,
SUPPORT_NONPOINTER_ISA是來標(biāo)識當(dāng)前平臺是否支持優(yōu)化的isa,但即使平臺支持,具體到某一個類卻是不一定的,要具體的去驗證類的元類的信息.不過可以放心大多系統(tǒng)的類的isa都是支持優(yōu)化的,我們自定義的類的isa也是支持優(yōu)化的.
canAllocNonpointer則是具體標(biāo)記某個類是否支持優(yōu)化的isa.
在閱讀源碼時還有會各種帶nonpointer字樣的針對優(yōu)化isa的標(biāo)記,除SUPPORT_NONPOINTER_ISA外,全是針對某個類而言的.
優(yōu)化的isa是什么?接下來會說.
zone:老版本中要先去看看zone是否有空間,在OBJC2下,忽略zone參數(shù).
size:這由外圍傳入,size存儲在類的元類內(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..類實例的instanceSize取決于類的中成員變量的個數(shù):

再看看最后的調(diào)用:calloc或者malloc_zone_calloc就和C語言在堆中申請空間如出一轍了.
2.2 isa init
-
isa沒那么簡單,因為優(yōu)化了
在part1內(nèi)已經(jīng)提過了多遍的isa,當(dāng)然只要知道OC內(nèi)萬物皆對象,也肯定知道類實例->類->元類用isa串聯(lián)起來的關(guān)系:

但具體到真實的應(yīng)用場景下,isa的串聯(lián)會比上圖描繪更復(fù)雜更具體一些,特別是在64位系統(tǒng)上.所有用了64位系統(tǒng)的電子產(chǎn)品都沒有用全64位來表示地址.
因為這不現(xiàn)實:32位==>4G內(nèi)存,64位==>你算算看.
64位不全拿來表示地址,這就給64位的isa留下了很大的優(yōu)化空間(32位時對象的isa只是指向類而已).
我們截取類實例到類的過程來說明優(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;
};
}
這個union isa_t新奇了,聯(lián)合體少見吧!更奇怪的是聯(lián)合體還嵌套了結(jié)構(gòu)體,有不明白的請戳.
簡單的說就是:Class cls+uintptr_t bits+struct{......}共用一塊64位的內(nèi)存空間,當(dāng)然只有一個有效,在SUPPORT_NONPOINTER_ISA為1的情況下,仍然有一些類不支持優(yōu)化的isa,所以這樣的union isa_t就支持多用:
Class cls->為未優(yōu)化版的isa指向一個類
uintptr_t bit+struct{......}
uintptr_t bit用于對64位統(tǒng)一賦值,
struct{......}做細化讀取與細化賦值
請注意這聯(lián)合體內(nèi)結(jié)構(gòu)體內(nèi)的這個字段shiftcls,shiftcls=shift class,短的類地址.union isa_t共計64位,shiftcls占33位.這就是一個操作系統(tǒng)地址變量優(yōu)化的細節(jié).在64位iPhone上只拿33位表示地址的,也就是說這的shiftcls就存儲了類實例歸屬的類的地址.如圖:

當(dāng)然類對象指向元類對象也是一樣的道理.
除了shiftcls之外,isa_t內(nèi)的各個字段均有用處,這些也就是64位的isa具體優(yōu)化的地方:
nonpointer:1->表示使用優(yōu)化的isa指針
has_assoc:1->是否包含關(guān)聯(lián)對象
has_cxx_dtor:1->是否包含析構(gòu)函數(shù)
shiftcls:33->類的指針
magic:6->固定值,用于判斷是否完成初始化
weakly_referenced:1->對象是否指向一個弱引用對象
deallocating:1->對象是否正在銷毀
has_sidetable_rc:1->在extra_rc存儲引用計數(shù)將要溢出的時候,借助sidetable(散列表)存儲引用計數(shù),has_sidetable_rc設(shè)置成1
extra_rc:19->存儲引用計數(shù)
后面章節(jié)的文章會細說關(guān)于這些字段所實現(xiàn)和優(yōu)化的功能.
- 初始化對象的isa
初始化對象的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)說過,而SUPPORT_INDEXED_ISA 為 1是另外一種優(yōu)化,用isa內(nèi)indexcls存儲著類在類列表內(nèi)的索引,這個用在watch上,手機和電腦上沒有這么用.
所以再看objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)的實現(xiàn)就簡單多了.
不支持nonpointer的,isa.cls = cls;
支持nonpointer的,
對newisa.bits賦值,即對isa的64位統(tǒng)一初始化賦值,(統(tǒng)一初始化賦值)
newisa.has_cxx_dtor記錄傳入的has_cxx_dtor,(細化賦值)
newisa.shiftcls記錄下cls的地址.(細化賦值)
newisa.shiftcls = (uintptr_t)cls >> 3;(為什么右移3位?)
拿手機舉例子:shiftcls:33;(shiftcls會分配到33位),在64位的手機上拿33位保存類的地址,但因為位對齊的緣故,所有地址都是8的倍數(shù),所有地址書寫的成二進制數(shù)最后3位全是0,所以才如上見到:cls >> 3;(消除了3個沒有影響的0)
3. Obj 裝扮 init
- (id)init {
return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
return obj;
}
在沒有復(fù)寫init方法的情況下,init的實現(xiàn)特別簡單.
- (instancetype)init
{
self = [super init];
if (self) {
_name = @"pogong";
_age = 28;
}
return self;
}
復(fù)寫init的情況下能做的也只是對類實例的成員變量的初始化裝扮.

當(dāng)然這樣的工作不在init內(nèi)部也能完成.

4. Obj 怪胎 Tagged Pointer
事情是是要從32位系統(tǒng)轉(zhuǎn)向64系統(tǒng)說起.
32位系統(tǒng)下:
NSNumber * num = [[NSNumber alloc]initWithInt:1];
棧上4個字節(jié)的對象指針指向堆上8個字節(jié)(存儲isa4個字節(jié)+存儲值4個字節(jié))的對象實例,共計12個字節(jié).
64位系統(tǒng)下:
NSNumber * num = [[NSNumber alloc]initWithInt:1];
棧上8個字節(jié)的對象指針指向堆上16個字節(jié)(存儲isa8個字節(jié)+存儲值8個字節(jié))的對象實例,共計24個字節(jié).
保存一個int要用8個字節(jié),包裝成對象要24字節(jié),有點太浪費了.
所以Tagged Pointer應(yīng)運而生,
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)講過,因為64位系統(tǒng)上8位對齊,16進制打印出的地址最后一位不是8就是0(2進制打印后三位全是0),而這里最后一位是2,很怪異,這就是對Tagged Pointer的標(biāo)記.再將標(biāo)記位的前面的數(shù)值和對象本身的值進行比較一模一樣.
Tagged Pointer就是將值與Tagged Pointer的標(biāo)記混在一塊64位的內(nèi)存內(nèi).看上去是對象,但卻沒有isa(一個沒有靈魂的對象==>Tagged Pointer).但索性現(xiàn)在的isa也不能直接被調(diào)用,所以不會造成什么不便.
棧上8個字節(jié)的對象指針指向堆上8個字節(jié)(Tagged Pointer)的對象實例,共計16個字節(jié).
Tagged Pointer的引入,節(jié)約了64位系統(tǒng)的內(nèi)存,提高了運行效率.
除NSNumber外,NSDate,NSString都應(yīng)用到Tagged Pointer.