MJiOS底層筆記--OC物件本質

小碼哥iOS底層原理班--MJ老師的課確實不錯,強推一波。
OC物件本質
基於C與C++結構體實現
OC語言如何被編譯器編譯:
OC ==> C++ ==> 彙編 ==> 機器語言
而在C++中只有 struct(結構體)
才能容納不同型別的內容( 比如不同屬性
)。
將Objective-C程式碼轉換為C\C++程式碼
-
clang -rewrite-objc OC原始檔 -o 輸出的CPP檔案
將原始檔轉寫成通用的cpp檔案
-
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC原始檔 -o 輸出的CPP檔案
通過Xcode將原始檔轉寫成arm64架構下的iphoneos檔案,檔案內容比第一種要少
- 如果需要連結其他框架,使用-framework引數。比如-framework UIKit
NSObject的OC與C++定義
- 在OC中的定義
@interface NSObject <NSObject> { Class isa; } 複製程式碼
- 轉成C++之後的定義
struct NSObject_IMPL { Class isa; }; 複製程式碼
其中 isa
是指向 objc_class
結構體的 指標
// 指標 typedef struct objc_class *Class; 複製程式碼
而一個指標在64位系統中所佔的記憶體為8位元組
所以一個OC物件所佔的記憶體至少為8位元組
NSObject物件所佔用記憶體的大小
上面的結論通過 class_getInstanceSize
函式也可以佐證:
#import <objc/runtime.h> /* 獲得NSObject例項物件的 `成員變數` 所佔用的大小 >> 8 */ NSLog(@"%zd", class_getInstanceSize([NSObject class])); //runtime原始碼中 size_t class_getInstanceSize(Class cls) { if (!cls) return 0; return cls->alignedInstanceSize(); } // Class's ivar size rounded up to a pointer-size boundary. uint32_t alignedInstanceSize() { return word_align(unalignedInstanceSize()); } 複製程式碼
需要注意這個 word_align
返回的是記憶體對齊後的大小,以 unalignedInstanceSize
(為對齊的)大小作為引數。
而對於 NSObject *obj
指標,我們有另一個函式可以檢視其實際被分配的記憶體大小
#import <malloc/malloc.h> // 獲得obj指標所指向記憶體的大小 >> 16 NSLog(@"%zd", malloc_size((__bridge const void *)obj)); 複製程式碼
為什麼8位元組的結構體會被分配16位元組
繼續看runtime
+ (id)alloc { return _objc_rootAlloc(self); } id _objc_rootAlloc(Class cls) { return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); } 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]; } // Replaced by ObjectAlloc + (id)allocWithZone:(struct _NSZone *)zone { return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone); } id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) { id obj; #if __OBJC2__ // allocWithZone under __OBJC2__ ignores the zone parameter (void)zone; obj = class_createInstance(cls, 0); #else if (!zone) { obj = class_createInstance(cls, 0); } else { obj = class_createInstanceFromZone(cls, 0, zone); } #endif if (slowpath(!obj)) obj = callBadAllocHandler(cls); return obj; } id class_createInstance(Class cls, size_t extraBytes) { return _class_createInstanceFromZone(cls, extraBytes, nil); } 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; } 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; } 複製程式碼
alloc函式最終會根據 instanceSize
返回的 size
,然後使用 calloc(1, size);
函式去分配記憶體。
在 instanceSize
函式中, alignedInstanceSize
方法為成員變數所佔記憶體大小(
上面已經貼過一次
). extraBytes
引數(
據我所見
)都為0。
而 CoreFoundation
框架在 instanceSize
函式中硬性規定不足16位元組的記憶體地址會被補成16位位元組。
但實際上, NSObject
物件只使用了 8位元組
用來儲存 isa
指標
Student物件的本質
@interface Student : NSObject { @public int _no; int _age; } @end 複製程式碼
重寫成C++之後
struct Student_IMPL { struct NSObject_IMPL NSObject_IVARS; int _no; int _age; }; struct NSObject_IMPL { Class isa; }; //其實就是 struct Student_IMPL { Class isa; //8位元組 int _no; //4位元組 int _age; //4位元組 }; 複製程式碼
所以一個 OC物件的本質
實際上是一個包含了 所有父類成員變數
+ 自身成員變數
的結構體
Student的記憶體佈局及大小
可以通過Debug->Debug workflow->View momory檢視指定地址的結構來查證

對於Student例項物件所佔記憶體地址的大小,我們同樣可以通過 malloc_size
函式來確定。
結果是16。8位元組父類的isa指標、4位元組_age的int、4位元組_no的int。

當然如果有興趣可以用 memory write (stu地址+8偏移量) 8
的方式,通過直接修改記憶體的方式對成員變數 _no
的值進行修改。
需要注意的一點是:
Student_IMPL
結構體中的 NSObject_IMPL
結構體就已經佔據了16個位元組。 _no
與 _age
兩個成員變數只是將 NSObject_IMPL
未利用的8個位元組分別利用了而已。
記憶體對齊原則下的OC物件記憶體分配
alignedInstanceSize()函式的記憶體對齊
alignedInstanceSize()
函式會按照所有成員變數中記憶體最長的一個做記憶體對齊。比如
@interface Animal: NSObject { int weight; int height; int age; } 複製程式碼
實際上只需要 8+4+4+4=20
個位元組長度即可,但是記憶體對其之後會返回 8*3=24