1.oc對(duì)象的本質(zhì)
-
我們平時(shí)編寫的Objective-C代碼,底層實(shí)現(xiàn)其實(shí)都是C\C++代碼。
編譯過程
所以O(shè)bjective-C的面向?qū)ο蠖际腔贑\C++的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的。如果我們想將研究其底層就要先轉(zhuǎn)換為C/C++文件。
- 將Objective-C代碼轉(zhuǎn)換為C\C++代碼
- 打開終端,cd到你所要轉(zhuǎn)成C/C++語言文件的目錄里;
-
ls/ls-l
列舉查看一下文件夾下是否有對(duì)應(yīng)的OC文件; -
clang -rewrite-objc OC源文件 -o 輸出的CPP文件
(編譯器重寫oc文件并輸出c++文件),如下:
hejundeMBP:~ hejun$ cd /Users/hejun/Desktop/OC本質(zhì)/OC本質(zhì)
hejundeMBP:OC本質(zhì) hejun$ ls
main.m
hejundeMBP:OC本質(zhì) hejun$ clang -rewrite-objc main.m -o main.cpp
這樣就會(huì)在該文件夾下生成一個(gè)c++文件,但是這樣生成的文件太大,因?yàn)榫幾g器轉(zhuǎn)化代碼要看什么平臺(tái),沒有直達(dá)平臺(tái)架構(gòu)所以生成文件包含了所有的架構(gòu)下的代碼,所以一般不這么轉(zhuǎn)化。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 輸出的CPP文件
(Xcode指定是iphoneos然后編譯器告訴是arm64架構(gòu)下重寫oc文件并輸出c++文件)如下:
hejundeMBP:~ hejun$ cd /Users/hejun/Desktop/OC本質(zhì)/OC本質(zhì)
hejundeMBP:OC本質(zhì) hejun$ ls
hejundeMBP:OC本質(zhì) hejun$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm64.cpp
hejundeMBP:OC本質(zhì) hejun$
這樣生成的文件就小很多了
如果需要鏈接其他框架,使用-framework參數(shù)。(比如-framework UIKit)。
為何不在不生成c文件而生成c++文件,因?yàn)樯娴腸++文件里面包含c,并且c++是支持c的。
不同平臺(tái)支持的代碼肯定是不一樣,模擬器(i386)、32bit(armv7)、64bit(arm64)。
- Objective-C的對(duì)象、類主要是基于C\C++的什么數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的?
進(jìn)入NSObject
頭文件可以看到,里面有一個(gè)Class
類型的isa
成員變量,點(diǎn)擊進(jìn)Class
可以發(fā)現(xiàn)其是一個(gè)指向結(jié)構(gòu)體的指針,在main
函數(shù)中創(chuàng)建一個(gè)NSObject
轉(zhuǎn)成C++代碼里面會(huì)生存一個(gè)NSObject_IMPL
(NSObject_implementation即NSObject的底層實(shí)現(xiàn))結(jié)構(gòu)體。
struct NSObject_IMPL {
Class isa;
};。
- 自定義一個(gè)
Student
類繼承NSObject
,添加_no
,_age
屬性,分析一下結(jié)構(gòu)和所占內(nèi)存打小。示例如下:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "malloc/malloc.h"
struct Student_IMPL {
Class isa;
int _no;
int _age;
};
@interface Student : NSObject{
@public
int _no;
int _age;
}
@end
@implementation Student
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc] init];
stu->_no = 10;
stu->_age = 10;
//獲得類的實(shí)例對(duì)象大小
NSLog(@"%zd", class_getInstanceSize([Student class]));
//獲得stu指針?biāo)赶虻膬?nèi)存大小
NSLog(@"%zd", malloc_size((__bridge const void *)stu));
//因?yàn)镾tudent_IMPL結(jié)構(gòu)體和Student結(jié)構(gòu)一樣,將stu的對(duì)象強(qiáng)轉(zhuǎn)為指向Student_IMPL結(jié)構(gòu)體的指針
struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
//通過結(jié)構(gòu)體指針訪問結(jié)構(gòu)體變量
NSLog(@"no is %d, age is %d", stuImpl->_no, stuImpl->_age);
}
return 0;
}
打斷點(diǎn)分析內(nèi)存(方法:Debug -> Debug Workfllow -> View Memory (Shift + Command + M)
然后將對(duì)象指針地址輸入Address
輸入框):
結(jié)果可見:內(nèi)存占了16個(gè)字節(jié),前面8為是是isa
指針?biāo)純?nèi)存,后8為中的兩個(gè)OA
則是no和age的值為10(注意Mac是高位向地位尋址。
打印結(jié)果如下:
結(jié)果可見確實(shí)是占了16個(gè)字節(jié)(8+4+4),但是如果打印NSObject
則class_getInstanceSize
獲得的是8位,malloc_size
是16位,因?yàn)榍罢呤谦@得類的實(shí)例對(duì)象大小只有一個(gè)指針?biāo)允?位,后者是獲得指針指向的內(nèi)存大小,蘋果歸檔如何小于16位則是16位,所以最少是16位(具體可以去看源碼,這里不再一一列舉)。
具體分析示意圖如下:
所以O(shè)bjective-C的對(duì)象、類主要是基于C\C++的結(jié)構(gòu)體實(shí)現(xiàn)的。
2.instance、class、meta-class.
- OC對(duì)象的分類:
instance
對(duì)象(實(shí)例對(duì)象)、class
對(duì)象(類對(duì)象)、meta-class
對(duì)象(元類對(duì)象)。
-
instance
對(duì)象就是通過類alloc
出來的對(duì)象,每次調(diào)用alloc
都會(huì)產(chǎn)生新的instance
對(duì)象。 -
instance
對(duì)象在內(nèi)存中存儲(chǔ)的信息包括:
- qq
isa
指針、 - 其他成員變量。
- 同一個(gè)類
alloc
出兩個(gè)實(shí)例對(duì)象是兩個(gè)不同的對(duì)象分別占用兩塊內(nèi)存:
instance.png
- 每個(gè)類在內(nèi)存中有且只有一個(gè)class對(duì)象
- class對(duì)象在內(nèi)存中存儲(chǔ)的信息主要包括:
- isa指針
- superclass指針
- 類的屬性信息(@property)、類的對(duì)象方法信息(instance method)
-
類的協(xié)議信息(protocol)、類的成員變量信息(ivar)
meta-class.png
objectMetaClass是NSObject的meta-class對(duì)象(元類對(duì)象),每個(gè)類在內(nèi)存中有且只有一個(gè)meta-class對(duì)象
獲取元類方法
Class object_getClass(id obj)
傳入類名時(shí)獲取元類,runtimeApi。-
meta-class對(duì)象和class對(duì)象的內(nèi)存結(jié)構(gòu)是一樣的,但是用途不一樣,在內(nèi)存中存儲(chǔ)的信息主要包括:
- isa指針
- superclass指針
-
類的類方法信息(class method)
metaclass.png
注意:runtime有兩個(gè)獲取類的方法
Class objc_getClass(const char *aClassName)
、Class object_getClass(id obj)
,前者傳入字符串類名返回返回對(duì)應(yīng)的類對(duì)象;后者傳入的obj可能是instance對(duì)象、class對(duì)象、meta-class對(duì)象,如果是instance對(duì)象,返回class對(duì)象、如果是class對(duì)象,返回meta-class對(duì)象,如果是meta-class對(duì)象,返回NSObject(基類)的meta-class對(duì)象。判斷是否元類方函數(shù):
BOOL class_isMetaClass(Class cls)
。
3.isa、superclass.
-
isa
-
instance
的isa
指向class
,當(dāng)調(diào)用對(duì)象方法時(shí),通過instance
的isa
找到class
,最后找到對(duì)象方法的實(shí)現(xiàn)進(jìn)行調(diào)用。 -
class
的isa
指向meta-class
,當(dāng)調(diào)用類方法時(shí),通過class
的isa
找到meta-class
,最后找到類方法的實(shí)現(xiàn)進(jìn)行調(diào)用
isa.png
-
-
superclass
:當(dāng)子類的instance
對(duì)象要調(diào)用父類的對(duì)象方法時(shí),會(huì)先通過isa
找到子類的class
,然后通過superclass
找到父類的class
,最后找到對(duì)象方法的實(shí)現(xiàn)進(jìn)行調(diào)用。
superclass.png -
meta-class
對(duì)象的superclass
指針:當(dāng)子類的class要調(diào)用父類的類方法時(shí),會(huì)先通過isa找到子類的meta-class
,然后通過superclass
找到父類的meta-class
,最后找到類方法的實(shí)現(xiàn)進(jìn)行調(diào)用(注意如果直到基類的meta-class也找不該方法就會(huì)去基類中去找同名的對(duì)象方法,找到了就會(huì)調(diào)用,找不到就報(bào)該類找不到此方法的錯(cuò)誤
)
meta-class的superclass.png isa
、uperclass
總結(jié)
- 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)用對(duì)象方法的軌跡
isa找到class,方法不存在,就通過superclass找父類class
class調(diào)用類方法的軌跡
-
isa找meta-class,方法不存在,就通過superclass找父類meta-class,如果找到基類的meta-class還沒有找到就去找基類的class,示意圖如下:
oc方法調(diào)用流程.png -
class、meta-class對(duì)象的本質(zhì)結(jié)構(gòu)都是
struct objc_class
oc類對(duì)象的本質(zhì).png
4.面試題
1.一個(gè)NSObject對(duì)象占用多少內(nèi)存?
系統(tǒng)分配了16個(gè)字節(jié)給NSObject對(duì)象(通過malloc_size 函數(shù)獲得)
但NSObject對(duì)象內(nèi)部只使用了8個(gè)字節(jié)的空間(64bit環(huán)境下,可以通過class_getInstanceSize函數(shù)獲得)
2.對(duì)象的isa指針指向哪里?
instance對(duì)象的isa指向class對(duì)象
class對(duì)象的isa指向meta-class對(duì)象
meta-class對(duì)象的isa指向基類的meta-class對(duì)象
3.OC的類信息存放在哪里?
成員變量的具體值,存放在instance對(duì)象
對(duì)象方法、屬性、成員變量、協(xié)議信息,存放在class對(duì)象中
類方法,存放在meta-class對(duì)象中