我們平時(shí)編寫的OC代碼帽驯,底層實(shí)現(xiàn)其實(shí)都是C\C++代碼,所以O(shè)C的對象和類主要是基于C和C++的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的书闸,這里的數(shù)據(jù)結(jié)構(gòu)就是結(jié)構(gòu)體尼变,下面請看詳細(xì)報(bào)道。
OC代碼轉(zhuǎn)換為C和C++
有兩種方式浆劲,可以用clang(xcode內(nèi)置的llvm編譯器)直接將.m輸出為.cpp嫌术,但是不能指定平臺(tái),一個(gè)主函數(shù)中只new一個(gè)對象的方法牌借,轉(zhuǎn)換出來的代碼會(huì)有近十萬行度气,命令:clang -rewrite-objc main.m -o main.cpp 。但實(shí)際上編譯器在編譯的時(shí)候會(huì)考慮平臺(tái)膨报,所以我們使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp命令磷籍,指定編譯平臺(tái)和架構(gòu)适荣,這樣轉(zhuǎn)換出來的代碼只有三分之一不到。這是轉(zhuǎn)換前后的代碼院领。因?yàn)檗D(zhuǎn)換后的代碼太長弛矛,所以只摘取了千百行。
在關(guān)于OC的本質(zhì)比然,通常需要來查看NSObject類的結(jié)構(gòu)丈氓,因?yàn)镹SObject是所有類的基類,我們從cpp文件中搜索NSObject强法,找到一個(gè)代碼万俗,就能看到OC類底層實(shí)現(xiàn)其實(shí)就是C的結(jié)構(gòu)體。
struct NSObject_IMPL {
Class isa; // 因?yàn)閏lass是個(gè)指針拟烫,所以占8個(gè)字節(jié)(64位系統(tǒng))
};
typedef struct objc_class *Class;
// 這里要提一嘴该编,實(shí)際上NSObject的類初始化時(shí)候是占16字節(jié),
//使用malloc_size((__bridge const void *)obj)方法就能獲得obj指針?biāo)赶騼?nèi)存的大小>>16硕淑。
//class_getInstanceSize([NSObject class])是獲得NSObject實(shí)例對象的成員變量(isa)所占用的大小 >>8字節(jié)
// OC源碼课竣,可以看出申請內(nèi)存,不能小于16字節(jié)
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;
}
從上面代碼可以看出一個(gè)NSObject類申請占16字節(jié)置媳,通過lldb調(diào)試查看內(nèi)存空間于樟,前8個(gè)字節(jié)為成員變量isa的8個(gè)字節(jié),后8個(gè)字節(jié)為占位拇囊。
<NSObject: 0x100573f70>
(lldb) memory read 0x100573f70
0x100573f70: 41 d1 a9 8d ff ff 1d 00 00 00 00 00 00 00 00 00 A...............
下面我們new一個(gè)student類
@interface Student : NSObject
{
@public
int _no;
int _age;
}
@end
通過上邊的描述迂曲,我們生成一個(gè).cpp文件,可以查看這個(gè)student的對象類型是一個(gè)結(jié)構(gòu)體指針寥袭,源碼描述為:
struct Student_IMPL {
//這個(gè)繼承關(guān)系的NSObject就是
//struct NSObject_IMPL {
// Class isa; // 因?yàn)閏lass是個(gè)指針路捧,所以占8個(gè)字節(jié)(64位系統(tǒng))
//};
struct NSObject_IMPL NSObject_VARS;
int _no;
int _age;
};
通過源碼很容易理解,生成的對象大小其實(shí)就是16字節(jié)传黄,isa占8個(gè)字節(jié)杰扫,int占4個(gè)字節(jié)。最后我們通過代碼驗(yàn)證一下膘掰,具體結(jié)果可以使用lldb查看內(nèi)存空間占的值章姓,這里就不貼出來了:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc] init];
stu->_no = 4;
stu->_age = 5;
NSLog(@"%zd", class_getInstanceSize([Student class])); //返回這個(gè)對象至少需要多少存儲(chǔ)空間
NSLog(@"%zd", malloc_size((__bridge const void *)stu));
struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
NSLog(@"no is %d, age is %d", stuImpl->_no, stuImpl->_age);
}
return 0;
}
內(nèi)存對齊:
這里我們要說一下內(nèi)存對齊,來更深入的探討OC對象的內(nèi)存分配识埋。
大學(xué)里我們學(xué)過內(nèi)存對齊在結(jié)構(gòu)體中的體現(xiàn)是結(jié)構(gòu)體的大小必須是最大成員的倍數(shù)凡伊。
iOS操作系統(tǒng)分配內(nèi)存的時(shí)候也會(huì)有內(nèi)存對齊,大小是16字節(jié)的倍數(shù)窒舟,那么為什么是16的倍數(shù)系忙,其實(shí)就是操作系統(tǒng)認(rèn)為16的倍數(shù)是對操作系統(tǒng)訪問最快的方式,我們看看代碼怎么說
//new一個(gè)person類辜纲,
@interface Person : NSObject
{
int _age;
int _height;
int _no;
}
//通過xrun生成cpp代碼為
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
int _height;
int _no;
};
//打印內(nèi)存大小
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
// sizeof是編譯階段確認(rèn)類型占用內(nèi)存的大小笨觅,如果傳入的是p拦耐,這為8個(gè)字節(jié)耕腾,因?yàn)閜是一個(gè)指針類型见剩,在編譯階段就已經(jīng)確定。
NSLog(@"%zd", sizeof(struct Person_IMPL)); // 24
// class_getInstanceSize 是在類的創(chuàng)建時(shí)候至少需要多少存儲(chǔ)空間
// malloc_size是最終分配了多少內(nèi)存空間
NSLog(@"%zd %zd",
class_getInstanceSize([Person class]), // 24
malloc_size((__bridge const void *)(p))); // 32
}
return 0;
}
我們在看看malloc的源碼扫俺,去GNU查找最新的C語言源碼苍苞,有一個(gè)叫g(shù)libc的庫,可以看到malloc-alignment.h
這個(gè)函數(shù)就是C函數(shù)申請一個(gè)對齊后的內(nèi)存狼纬,這里在這里我們可以在iOS工程內(nèi)用sizeof計(jì)算出alignof_ (long double) 和 SIZE_SZ的值分別是16和8羹呵,所以在iOS的環(huán)境下,內(nèi)存對其的最小是16字節(jié)疗琉。
#define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \
? __alignof__ (long double) : 2 * SIZE_SZ)
OC對象的分類
Objective-C中的對象冈欢,簡稱OC對象,主要可以分為3種
instance對象(實(shí)例對象)
上邊我們描述的都是實(shí)類對象
instance對象就是通過類alloc出來的對象盈简,每次調(diào)用alloc都會(huì)產(chǎn)生新的instance對象
instance對象在內(nèi)存中存儲(chǔ)的信息包括
isa指針
其他成員變量
class對象(類對象)
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];
objectClass1 ~ objectClass5都是NSObject的class對象(類對象)
它們是同一個(gè)對象凑耻。每個(gè)類在內(nèi)存中有且只有一個(gè)class對象
class對象在內(nèi)存中存儲(chǔ)的信息主要包括:
- isa指針
- superclass指針
- 類的屬性信息(@property)、類的對象方法信息(instance method)
- 類的協(xié)議信息(protocol)柠贤、類的成員變量信息(ivar)
meta-class對象(元類對象)
Class objectMetaClass = object_getClass([NSObject class];);
objectMetaClass是NSObject的meta-class對象(元類對象)
每個(gè)類在內(nèi)存中有且只有一個(gè)meta-class對象
meta-class對象和class對象的內(nèi)存結(jié)構(gòu)是一樣的香浩,但是用途不一樣,在內(nèi)存中存儲(chǔ)的信息主要包括:
- isa指針
- superclass指針
- 類的類方法信息(class method)
- ......
總結(jié)幾個(gè)方法
從打印結(jié)果看臼勉,對象方法的內(nèi)存不同
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
獲取類對象的打印相同
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];
獲取元類的方法邻吭,傳遞的是類對象參數(shù)。
Class objectMetaClass = object_getClass([NSObject class];);
NSLog(@"%p %p",
object1,
object2);
NSLog(@"%p %p %p %p %p",
objectClass1,
objectClass2,
objectClass3,
objectClass4,
objectClass5);
打友绨浴:
2019-12-28 21:46:18.859724+0800 Interview02-class對象[5114:373716] 0x103a1e570 0x103a1f360
2019-12-28 21:46:18.860065+0800 Interview02-class對象[5114:373716] 0x7fff9cc7a118 0x7fff9cc7a118 0x7fff9cc7a118 0x7fff9cc7a118 0x7fff9cc7a118
源碼:
獲取類對象囱晴、元類對象的方法,在objc中實(shí)際就是返回isa指針瓢谢。
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
這里不要跟這個(gè)方法混淆畸写,objc_getClass是傳遞了個(gè)類的名稱。最后通過map查找到對應(yīng)的class恩闻,下邊是精簡后的代碼艺糜。
Class objc_getClass(const char *aClassName)
{
if (!aClassName) return Nil;
// NO unconnected, YES class handler
return look_up_class(aClassName, NO, YES);
}
Class
look_up_class(const char *name,
bool includeUnconnected __attribute__((unused)),
bool includeClassHandler __attribute__((unused)))
{
getClass(name);
}
static Class getClass(const char *name)
{
Class result = getClass_impl(name);
}
static Class getClass_impl(const char *name)
{
Class result = (Class)NXMapGet(gdb_objc_realized_classes, name);
}
調(diào)用流程
- instance的isa指向class
- class的isa指向meta-class
meta-class的isa指向基類的meta-class - class的superclass指向父類的class
如果沒有父類,superclass指針為nil - meta-class的superclass指向父類的meta-class
基類的meta-class的superclass指向基類的class - instance調(diào)用對象方法的軌跡
isa找到class幢尚,方法不存在破停,就通過superclass找父類 - class調(diào)用類方法的軌跡
isa找meta-class,方法不存在尉剩,就通過superclass找父類
isa指針
- 從64bit開始真慢,isa需要進(jìn)行一次位運(yùn)算,才能計(jì)算出真實(shí)地址
- class理茎、meta-class對象的本質(zhì)結(jié)構(gòu)都是struct objc_class
- objc4源碼下載 https://opensource.apple.com/tarballs/objc4/
// 因?yàn)镃lass的isa沒有設(shè)置成可以供外部訪問黑界,所以我們自己創(chuàng)建一個(gè)結(jié)構(gòu)體管嬉,跟Class一樣,方便我們下邊獲取isa的值朗鸠。
struct gkt_objc_class {
Class isa;
Class superclass;
};
int main(int argc, const char * argv[]) {
GKTTest *test = [GKTTest new];
Class testClass = [GKTTest class];
Class mateClass = object_getClass(testClass);
struct gkt_objc_class * testClass2 = (__bridge struct gkt_objc_class *)testClass;
struct gkt_objc_class * mateClass2 = (__bridge struct gkt_objc_class *)mateClass;
return 0;
}
(lldb) p/x test->isa
(Class) $0 = 0x001d800100001179 GKTTest
(lldb) p/x testClass
(Class) $1 = 0x0000000100001178 GKTTest
(lldb) p/x 0x001d800100001179 & 0x00007ffffffffff8
(long) $2 = 0x0000000100001178
(lldb) p/x testClass2->isa
(Class) $3 = 0x0000000100001150
(lldb) p/x mateClass2
(gkt_objc_class *) $4 = 0x0000000100001150
(lldb) p/x 0x0000000100001150 & 0x00007ffffffffff8
(long) $5 = 0x0000000100001150
(lldb)