Objective-C代碼底層實現其實都是C\C++代碼。面試
結構體
這種數據結構實現的。
clang -rewrite-objc main.m -o main.cppxcode
在main.m
文件所在的目錄執行上面的命令,將Objective-C代碼轉爲C++代碼,輸出文件爲main.cpp
。這個命令並無指定編輯的平臺與架構,針對iOS系統,咱們能夠用下面這個命令:bash
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp數據結構
指定sdk爲iphoneos
,針對iOS系統,-arch arm64
表示針對arm64架構進行編譯,輸出文件爲main-arm64.cpp
。架構
注: iOS架構:模擬器i386
32bitarmv7
64bitarm64
app
xcrun
是Xcode的命令,若是沒法執行多是由於裝了兩個版本的Xcode。在命令行指定一下Xcode路徑:iphone
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer/函數
在NSObject.h
文件中,咱們能夠看到NSObject的聲明以下圖左, 在main-arm64.cpp
中能夠發現NSObject的實現以下圖右。能夠得出結論,NSObject的底層是使用了C/C++的結構體來實現的。佈局
接下來,咱們繼續查看Class
的聲明,以下,能夠看到,Class是一個指向結構體變量的指針。由於在64位的系統中,一個指針佔用8個字節,全部上圖中的isa
佔用8個字節。ui
typedef struct objc_class *Class;
複製代碼
爲了獲取NSOject對象所佔的內存空間,咱們能夠經過malloc_size
函數獲取對象所佔用內存空間的大小。注意,須要導入庫 malloc/malloc.h
#import <malloc/malloc.h>
NSObject *obj = [[NSObject alloc] init];
//得到obj指針所指向內存的大小 輸出結果爲 16
NSLog(@"%zd", malloc_size((__bridge const void*)obj));
複製代碼
另外,咱們還能夠經過class_getInstanceSize
方法獲取對象的全部成員變量所佔用的內存空間。注意,須要導入庫objc/runtime.h
。在上面的分析中,咱們已經確認NSObject內部只有一個isa
成員變量,它是一個指向Class
結構體的指針變量。
#import <objc/runtime.h>
//得到NSObject實例對象的成員變量所佔用的大小。 輸出結果爲 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
複製代碼
咱們也能夠分析一下class_getInstanceSize
的源碼實現,以下:
//源碼版本:objc4-723
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
//Class's ivar size rounded up to a pointer-size boundary. //類的成員變量的大小,word_align 對齊操做 uint32_t alignedInstanceSize() { return word_align(unalignedInstanceSize()); } // May be unaligned depending on class's ivars.
// 類的成員變量的未對齊的大小
uint32_t unalignedInstanceSize() {
return data()->ro->instanceSize;
}
//對齊函數實現
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
複製代碼
alloc
源碼:
//源碼版本:objc4-723
+ (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*/);
}
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
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;
}
}
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
//建立實例
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 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; obj->initIsa(cls); } if (cxxConstruct && hasCxxCtor) { obj = _objc_constructOrFree(obj, cls); } return obj; } size_t instanceSize(size_t extraBytes) { //alignedInstanceSize 就是 class_getInstanceSize 也就是內部變量所佔用的空間大小 size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. //最小爲16 if (size < 16) size = 16; return size; } 複製代碼
咱們能夠畫出[[NSObject alloc] init]
執行後的內存結構圖。
至此,咱們分析獲得結論,NSObject對象佔用16個字節的內存空間,其中前8個字節是isa
變量,它是一個指向Class
結構體的指針。
經過memory read
指令能夠讀取內存地址。簡寫爲x
。其後面能夠跟上數量/格式/字節數。
如:x/4xg 0x10312b010 表示 讀寫每次讀取8個字節,讀寫4組數據,並以16進制的形式表示。
格式:x
是16進制,f
是浮點數,d
是10進制。
字節大小:b
byte是1字節, h
half world 是2字節,w
world是4字節, g
giant world是8字節。
(lldb) po obj
<NSObject: 0x10312b010>
(lldb) memory read 0x10312b010
0x10312b010: 19 91 62 98 ff ff 1d 00 00 00 00 00 00 00 00 00 ..b.............
0x10312b020: 2d 5b 4e 53 54 61 62 50 69 63 6b 65 72 56 69 65 -[NSTabPickerVie
(lldb) x 0x10312b010
0x10312b010: 19 91 62 98 ff ff 1d 00 00 00 00 00 00 00 00 00 ..b.............
0x10312b020: 2d 5b 4e 53 54 61 62 50 69 63 6b 65 72 56 69 65 -[NSTabPickerVie
(lldb) x/3xg 0x10312b010
0x10312b010: 0x001dffff98629119 0x0000000000000000
0x10312b020: 0x50626154534e5b2d
(lldb) x/4xg 0x10312b010
0x10312b010: 0x001dffff98629119 0x0000000000000000
0x10312b020: 0x50626154534e5b2d 0x65695672656b6369
使用 memory write
命令能夠修改內存中某個位置所表示的內容。下面命令是要修改0x10312b010起始第9個字節,將其內容從0改成8。
(lldb) memory read 0x10312b010
0x10312b010: 19 91 62 98 ff ff 1d 00 00 00 00 00 00 00 00 00 ..b.............
0x10312b020: 2d 5b 4e 53 54 61 62 50 69 63 6b 65 72 56 69 65 -[NSTabPickerVie
(lldb) memory write 0x10312b018 8
(lldb) memory read 0x10312b010
0x10312b010: 19 91 62 98 ff ff 1d 00 08 00 00 00 00 00 00 00 ..b.............
0x10312b020: 2d 5b 4e 53 54 61 62 50 69 63 6b 65 72 56 69 65 -[NSTabPickerVie
定義一個類繼承自NSObject
。
@interface Student : NSObject {
@public
int _no;
int _age;
}
@end
@implementation Student
@end
複製代碼
將該類編譯成C/C++代碼,其實現以下:
struct NSObject_IMPL {
Class isa;
};
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _no;
int _age;
};
複製代碼
Student
將其父類NSObject
的成員變量都繼承過來了,至關於:
struct Student_IMPL {
Class isa;
int _no;
int _age;
};
複製代碼
在這個結構中,isa
佔用8個字節,_no
與_age
各佔用4個字節,因此,Student內部的成員變量一共佔用16個字節的空間。
即:NSLog(@"%zd", class_getInstanceSize([Student class]));
輸出結果爲16。
NSLog(@"%zd", malloc_size((__bridge const void *)student));
輸出結果也爲16,由於內部成語變量的大小爲16,恰好爲alloc建立出來的最小空間相等。
Student
的內存結構以下:
咱們能夠構造一個結構體,將student
對象強制轉換爲結構體,以下:咱們就能夠經過結構體正確的訪問到內部變量。
struct Student_Impl {
Class isa;
int _no;
int _age;
};
struct Student_Impl *impl = (__bridge struct Student_Impl *)student;
NSLog(@"no=%d, age=%d", impl->_no, impl->_age);
複製代碼
咱們能夠進一步證明isa
的起始地址就是student
指向的地址。
(lldb) po &impl->isa
<Student: 0x103905a40>
(lldb) po student
<Student: 0x103905a40>
@interface Person : NSObject {
@public
int _age;
}
@end
@implementation Person
@end
@interface Student : Person {
@public
int _no;
}
@end
@implementation Student
@end
複製代碼
轉換爲C/C++爲:
struct Student_IMPL {
struct Person_IMPL Person_IVARS;
int _no;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
};
struct NSObject_IMPL {
Class isa;
};
複製代碼
內存分佈:
Person *person = [[Person alloc] init];
//16 雖然真實大小爲12,因爲內存對齊,爲16
NSLog(@"%zd", class_getInstanceSize([person class]));
NSLog(@"%zd", malloc_size((__bridge const void *)person)); //16
Student *student = [[Student alloc] init];
NSLog(@"%zd", class_getInstanceSize([student class])); //16
NSLog(@"%zd", malloc_size((__bridge const void *)student)); //16
複製代碼
注,內存對齊:結構體的大小必須是最大成員大小的倍數。
@interface Person : NSObject {
@public
int _age;
int _no;
}
@end
@implementation Person
@end
@interface Student : Person{
@public
int data;
}
@end
複製代碼
在上面這種類的結構中,咱們打印出變量空間大小和內存大小。
NSLog(@"%zd", class_getInstanceSize([Student class])); //24
NSLog(@"%zd", malloc_size((__bridge const void *)student)); //32
複製代碼
class_getInstanceSize
這個大小是16+8(int 4, 內存對齊爲8) = 24,應該不難理解。
malloc_size
這個爲32 就有點難理解了,緣由是操做系統在開闢內存的時候不是你須要多少就開闢多少,而是要根據必定的規則,爲了方便內存管理與尋址,iOS系統開闢內存的空間都是16的倍數,全部咱們須要24個字節的大小,操做系統給咱們分配了32個字節。具體開闢方式能夠參考資料一。
參考一:https://yq.aliyun.com/articles/3065
參考二:MJ底層原理