1. Objective-C的本質
我們平時編寫的OC代碼,其實底層實現都是C/C++代碼,類主要是基于C/C++的結構體的數據結構實現的,因為對象或者類有各種類型(NSArray *,NSDictionary *,CFfloat
等),因為可以存儲不同種類的數據,能夠滿足的這樣的結構就是結構體.
為了證明OC的結構,所以可以轉換成C++的代碼,窺探內部的結構(有時候C++的代碼也不一定能完全表示源碼的情況,需要調試到匯編代碼或源碼查看).
我們可以通過終端進入到要窺探所在文件的位置,使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
.(如果電腦上面安裝了多個版本的Xcode,轉換為C++代碼的時候會提示各種框架找不到的錯誤,一般是因為多個版本的Xcode路徑沖突導致的,我們需要在終端指定一個Xcode的路徑,例:sudo xcode-select --switch/Applications/Xcode10.0.app/Contents/Developer/).
注釋:解釋各種參數的翻譯
xc就是Xcode的縮寫。
xcrun是Xcode的一種工具。
-sdk iphoneos規定sdk需要運行在iOS系統上面。
clang是Xcode內置的llvm編譯器前端,也是編譯器的一種。
-arch xxx(arm64、i386、armv7...)指出iOS設備的架構。
參數 -rewrite-objc xx.m 是重寫objc代碼的指令(即重寫xx.m文件) 。
-o newFileName.cpp 表示輸出新的.cpp文件。
2. NSObject底層實現原理
Class 定義為 :typedef struct objc_class *Class
;也就是說Class是個結構體指針.
代碼中[NSObject alloc]
開辟空間給NSObject
。obj的指針指向了isa的地址.isa的地址就是結構體的地址,原因是結構體的地址就是結構體中第一個成員的地址,而結構體只有一個成員,即isa指針的地址.
一. 例:student底層的原理
答:因為Student
繼承NSObject
,也就繼承了NSObject
的數據結構,所以繼承NSObject
的8個字節,也就是NSobject
中的isa的大小。
Person占據class_getInstanceSize=16 malloc_size=16
, Student占據class_getInstanceSize=16 malloc_size=16
,Person的變量實際用了12,但是由于內存對齊所以占用16.
二. 兩種方法看內存大小
我們有這種方法在OC中表達一個類內存的大小.
<objc/runtime.h>文件提供class_getInstanceSize(Class _Nullable cls)方法,返回我們一個OC對象
的實例所占用的內存大小(可以說是結構體內存對齊之后的大小,8的倍數);
<malloc/malloc.h>文件提供 size_t malloc_size(const void *ptr)方法返回系統為這個對象分配的
內存大小(16的倍數)。
三. 內存對齊的原理(不全,后期添加)
我們先來看一些內存的例子,更加方便我們去理解內存分配和內存對齊原理:
- 看一個沒有成員變量的類的實例(以NSObject為例)
NSObject *obj = [[NSObject alloc] init];
NSLog(@"NSObject實例大小--> %zd",class_getInstanceSize([obj class]));
NSLog(@"obj實際分配的內存%zd",malloc_size((const void *)obj));
// NSObject實例大小--> 8
// obj實際分配的內存16
- 一個普通的類的實例,并且實例有且僅有唯一的成員變量(如:Student只有一個name屬性)
@interface Student: NSObject
@property (nonatomic, copy) NSString *name;
@end;
@implementation Student
@end;
Student *stu = [[Student alloc] init];
stu.name = @"Object-C";
NSLog(@"Student實例大小--> %zd",class_getInstanceSize([stu class]));
NSLog(@"stu實際分配的內存%zd",malloc_size((const void *)stu));
// Student實例大小--> 16
// stu實際分配的內存16
- 一個普通的類的實例,并且實例有自己的成員變量(如:Student類,為其添加屬性age、name等)
@interface Student: NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end;
@implementation Student
@end;
Student *stu = [Student new];
stu.name = @"Object-C";
stu.age = 25;
NSLog(@"Student實例大小--> %zd",class_getInstanceSize([stu class]));
NSLog(@"stu實際分配的內存%zd",malloc_size((const void *)stu));
// Student實例大小-->24
// stu實際分配的內存32
由以上三次測試:一個OC對象所占用的內存取決于這個對象成員變量的多少.但是同時,系統為其分配內存時,默認會分配最少16個字節的大小.OC中對象的內存小于16就等于16(是Core Foundation的規定),下面是Core Foundation的源碼.
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;
}
內存對齊的原則:結構體的大小必須是最大成員的倍數.
更多的內存對齊的知識--內存對齊
補充:sizeof不是個函數是個運算符,傳入的時候是類型不是具體的對象,sizeof是在編譯的時候進行計算的.
3. OC對象的分類
objective-C的對象,簡稱為OC對象,分為三種:
- instance對象(實例對象).
- class對象(類對象).
- meta-class(元類對象).
一. 實例對象
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
object1、object2是NSObject的instance對象(實例對象),它們是不同的兩個對象,分別占據著兩塊不同的內存。instance對象是通過類alloc出來的對象,每次調用alloc都會產生新的instance對象.instance對象在內存中存儲的信息包括:isa指針,其他成員變量。
實例對象存放的內容包含:
實例對象 |
---|
isa |
成員變量信息 |
二. 類對象
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = [NSObject class];
Class objectClass4 = object_getClass(object1);//Runtime API
Class objectClass5 = object_getClass(object2);//Runtime API
objectClass1 ~ objectClass5都是NSObject的class對象(類對象).它們是同一個對象,每個類在內存中有且只有一個class對象.
類對象存放的內容包含:
類對象 |
---|
isa |
superclass |
屬性信息 |
對象方法信息 |
協議信息 |
成員變量信息 |
............. |
class對象在內存中存儲的信息主要包括:isa指針,superclass指針,類的屬性信息(@property)、類的對象方法信息(instance method),類的協議信息(protocol)、類的成員變量信息(ivar).
三. 元類對象
獲取一個類對象的元類對象的方法.
Class objectMetaClass = object_getClass([NSObject class]);//Runtime API
元類對象 |
---|
isa |
superclass |
類方法信息 |
............. |
objectMetaClass
是NSObject的meta-class
對象(元類對象).每個類在內存中有且只有一個meta-class對象.
meta-class對象和class對象的內存結構是一樣的,但是用途不一樣,在內存中存儲的信息主要包括:isa指針,superclass指針,類的類方法信息(class method).
補充:
查看Class是否為meta-class:
BOOL result = class_isMetaClass([NSObject class]);
以下代碼獲取的objectClass是class對象,并不是meta-class對象
Class objectClass = [[NSObject class] class];
objcget-Class和object-getClass區別
objc_getClass | 傳入字符串類名返回類對象. | 傳入字符串類名返回類對象. | 傳入字符串類名返回類對象. |
---|---|---|---|
object_getClass | 傳入實例對象返回類對象. | 傳入類對象返回元類對象. | 傳入元類對象返回還是元類對象 |
四. isa和superClass
1. isa
①instance的isa指向class,當調用對象方法時,通過instance的isa找到class,最后找到對象方法的實現進行調用.
②class的isa指向meta-class,當調用類方法時,通過class的isa找到meta-class,最后找到類方法的實現進行調用.
2. superClass
當Student的instance對象要調用Person的對象方法時,會先通過isa找到Student的class,然后通過superclass找到Person的class,最后找到對象方法的實現進行調用.
當Student的class要調用Person的類方法時,會先通過isa找到Student的meta-class,然后通過superclass找到Person的meta-class,最后找到類方法的實現進行調用.
3. 經典的isa和superclass圖譜
- isa總結
- instance的isa都是指向class.
- class的isa都是指向meta-class.
- meta-class的isa指向基類的meta-class.
- superClass總結
- class的superClass指向父類的class.
- 如果沒有父類,superClass指針為nil.
- meta-class的superClass指向父類的meta-class.
- 基類meta-class的superClass指向基類的class.
- instance的調用軌跡
- isa找到class,方法不存在,就通過superclass找父類.
- class調用類方法的軌跡
- isa找meta-class,方法不存在,就通過superclass找父類.
- 基類的meta-class方法不存在,就通過superclass找基類的class,如果沒有找到就是nil.
4. isa地址運算
從64bit開始,isa需要進行一次位運算,才能計算出真實地址,superClass存儲的地址值,直接就是父類的地址值,不用做位運算.
實例對象里只有成員變量沒有方法,為什么實例對象的方法要存在類對象里,原因是只要存一份就夠了,實例對象會創建多個.
想了解更多iOS學習知識請聯系:QQ(814299221)