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