淺談對Objective-C的對象本質的理解

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底層實現原理

NSObject底層原理.png

Class 定義為 :typedef struct objc_class *Class;也就是說Class是個結構體指針.
代碼中[NSObject alloc]開辟空間給NSObject。obj的指針指向了isa的地址.isa的地址就是結構體的地址,原因是結構體的地址就是結構體中第一個成員的地址,而結構體只有一個成員,即isa指針的地址.

一. 例:student底層的原理

Student普通的結構.png

答:因為Student繼承NSObject,也就繼承了NSObject的數據結構,所以繼承NSObject的8個字節,也就是NSobject中的isa的大小。

思考題:Student繼承Person的結構.png

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對象,分為三種:

  1. instance對象(實例對象).
  2. class對象(類對象).
  3. 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
類方法信息
.............

objectMetaClassNSObject的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
isa的指向關系圖.png

①instance的isa指向class,當調用對象方法時,通過instance的isa找到class,最后找到對象方法的實現進行調用.
②class的isa指向meta-class,當調用類方法時,通過class的isa找到meta-class,最后找到類方法的實現進行調用.

2. superClass
類對象的指向關系.png

當Student的instance對象要調用Person的對象方法時,會先通過isa找到Student的class,然后通過superclass找到Person的class,最后找到對象方法的實現進行調用.


元類對象的指向關系.png

當Student的class要調用Person的類方法時,會先通過isa找到Student的meta-class,然后通過superclass找到Person的meta-class,最后找到類方法的實現進行調用.

3. 經典的isa和superclass圖譜
經典圖片.png
  1. isa總結
  • instance的isa都是指向class.
  • class的isa都是指向meta-class.
  • meta-class的isa指向基類的meta-class.
  1. superClass總結
  • class的superClass指向父類的class.
  • 如果沒有父類,superClass指針為nil.
  • meta-class的superClass指向父類的meta-class.
  • 基類meta-class的superClass指向基類的class.
  1. instance的調用軌跡
  • isa找到class,方法不存在,就通過superclass找父類.
  1. class調用類方法的軌跡
  • isa找meta-class,方法不存在,就通過superclass找父類.
  • 基類的meta-class方法不存在,就通過superclass找基類的class,如果沒有找到就是nil.
4. isa地址運算
isa.png
isa的MASK地址.png

從64bit開始,isa需要進行一次位運算,才能計算出真實地址,superClass存儲的地址值,直接就是父類的地址值,不用做位運算.


一個對象完整的結構.png

實例對象里只有成員變量沒有方法,為什么實例對象的方法要存在類對象里,原因是只要存一份就夠了,實例對象會創建多個.

                      想了解更多iOS學習知識請聯系:QQ(814299221)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容