一、類的結構
OC 中的代碼在底層實現,使用的是 C、C++,所以要研究 OC 中的類結構,可以將 OC 的代碼轉成 C++的代碼即可。首先看一下 NSObject 的結構是什么樣子的,創建一個文件并簡單的編寫如下代碼:
// CustomFile.m#import <Foundation/Foundation.h>void test() { [NSObject alloc];}
進入終端,輸入指令:
clang -rewrite-objc CustomFile.m
默認生成一個 CustomFile.cpp 文件。這個指令生成的代碼會很多,也可以使用 xcrun 指令來指定一個特定的架構,這樣的:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc CustomFile.m -o CustomFile_arm64.cpp
這樣在 CustomFile_arm64.cpp 文件中會生成一個 真機下的運行代碼。相比之下 CustomFile_arm64.cpp 文件會比 CustomFile.cpp 小了很多,但是對于查看 NSObject 的實際結構都是可以的。
打開任意一個 .cpp 文件,都可以找到這樣的定義:
struct NSObject_IMPL { Class isa;};
其中 Class 的定義如下:
typedef struct objc_class *Class;
再來看一下在實際中的 NSObject 類的聲明是什么樣的:
@interface NSObject <NSObject> {#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wobjc-interface-ivars" Class isa OBJC_ISA_AVAILABILITY;#pragma clang diagnostic pop}
簡化后是這樣的:
@interface NSObject { Class isa;}
總之Class 是一個指針,NSObject_IMPL是一個結構體,與 NSObject 在結構上極為相似。
二、類繼承后的結構
創建一個 Person.m 文件,弄一個繼承于 NSObject 的 Person 類。代碼編寫如下:
// Person.m#import <Foundation/Foundation.h>// 類的申明@interface Person : NSObject@end// 類的實現@implementation Person@end// 類的申明@interface Student : Person@end// 類的實現@implementation Student@end
其中 Person 繼承于 NSObject,Student 繼承于 Person 于是在 .cpp 文件中找到這樣的定義:
struct Person_IMPL { struct NSObject_IMPL NSObject_IVARS;};struct Student_IMPL { struct Person_IMPL Person_IVARS;};
NSObject_IVARS 看著這個命名就可以猜到是將父類的所有 ivar 都繼承過來了。
似乎明白了一個套路
在 NSObject 中只有一個 Class 類型的成員變量 isa,在沒有自定義任何的成員屬性的情況下,繼承的子類中的 ivar 都來自于父類。
如果說給 Person 與 Student 都定義一個成員變量,是這樣的:
struct Person_IMPL { struct NSObject_IMPL NSObject_IVARS; int _no;};struct Student_IMPL { struct Person_IMPL Person_IVARS; int _age;};
終于對 Class 的一些套路有進一步的理解了。
三、添加方法后的結構
創建一個 FunClass.m 文件,編寫代碼如下:
// FunClass.m#import <Foundation/Foundation.h>// 類的申明@interface FunClass : NSObject- (void)testInstance;+ (void)testClass;@end// 類的實現@implementation FunClass- (void)testInstance { }+ (void)testClass { }@end
最后發現在 .cpp 中類的結構沒有任何的改變,是這樣的:
struct FunClass_IMPL { struct NSObject_IMPL NSObject_IVARS;};
但是我們會發現另外一個問題,在 OC 中的方法變成這樣的了:
// 實例方法_OBJC_$_INSTANCE_METHODS_FunClass __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 1, {{(struct objc_selector *)"testInstance", "v16@0:8", (void *)_I_FunClass_testInstance}}static void _I_FunClass_testInstance(FunClass * self, SEL _cmd) {}// 類方法_OBJC_$_CLASS_METHODS_FunClass __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 1, {{(struct objc_selector *)"testClass", "v16@0:8", (void *)_C_FunClass_testClass}}static void _C_FunClass_testClass(Class self, SEL _cmd) {}
發現這幾個特點:
1、實例方法有這個:_INSTANCE_METHODS_FunClass,類方法的是這個:_CLASS_METHODS_FunClass
2、兩個方法都是 static 方法
3、方法都多了兩個參數:self 與_cmd,這也回答了為什么 self 與 _cmd 只能在方法中有的根本原因。
關于 方法 的這部分先介紹到這里,后期會有專門的專題。
四、自定義一個 Class 與對應的結構體
上面啰嗦了這么多,到底是對不對呢?!那就來親自試一下吧。
這里的自定義是指不再繼承于 NSObject 了,自己搞一個結構體。為了證明其正確性,分別定義一個 HGNObject 類 與 HGNObject_IMPL 結構體。編寫的代碼如下:
// ==== 類的定義部分 ====// 類的申明@interface HGNObject : NSObject { @public int _no; int _age;}@end// 類的實現@implementation HGNObject@end// ==== 結構體 ====struct HGNObject_IMPL { Class isa_hg; int _no_hg; int _age_hg;};
做兩個試驗:
1、類轉結構體
2、結構體轉類
1、類轉結構體
示例代碼如下:
// 類轉結構體- (void)class2Struct { // 創建一個對象 HGNObject* nObj = [[HGNObject alloc] init]; // 成員變量賦值 nObj->_no = 771722918; nObj->_age = 18; { // 類對象直接轉成一個結構體 struct HGNObject_IMPL* nObj_s = (__bridge struct HGNObject_IMPL*)nObj; // 打印結構體中的值 NSLog(@"%zd, %zd", nObj_s->_no_hg, nObj_s->_age_hg); // 打印結果: 771722918, 18 }}
通過結構體指針能打印出在類對象中設置的值,說明在 類轉結構體的過程是有效的。
2、結構體轉類
示例代碼如下:
// 結構體轉類- (void)struct2Class { NSLog(@"結構體轉類"); // 生成一個結構體 struct HGNObject_IMPL nObj_s = {0, 771722918, 20}; // 結構體中的值打印 NSLog(@"isa_hg = %zd, _no_hg = %zd, _age_hg = %zd", nObj_s.isa_hg, nObj_s._no_hg, nObj_s._age_hg); struct HGNObject_IMPL* nObj_sPointer = &nObj_s; // 結構體轉成對象 HGNObject* nObj = (__bridge HGNObject *)(nObj_sPointer); NSLog(@"_no_hg = %zd, _age_hg = %zd", nObj->_no, nObj->_age);}
運行代碼,直接 crash 了:
由于 Block 解開多年來的誤解 的慘痛教訓,所以對遇到的 crash 就會很敏感。看一下上面的這張圖,有一個關鍵的點是不可以忽視的,就是這里的值:
簡單的分析(我這里的分析都是猜的,暫時我也不知道,【抱歉抱歉抱歉】)
nObj_s 是有正確的值的,說明 nObj_sPointer 指針也是沒有問題的,但是為什么會報一個壞地址訪問錯誤呢?并且 address 的值每次都是一樣的 0x20。我猜想:在轉化的過程中不僅僅是一個簡單的賦值操作、可能還做了其他的地址訪問操作,這個操作很有可能與 +alloc 方法中操作有關,畢竟在 OC 中正常的創建一個對象必先 +alloc方法,對于 +alloc中都做了什么事,暫時我還不清楚,所以這里我是蒙的。各位大神若有新的理解,望指教!
所以在第一個實驗中的 類轉結構體 中是有效的,也許是一個偶然。畢竟我們在上面的 .cpp 文件中查看數據結構的時候也只是看了一個大概,并沒有看全部的。
OK,忘記本節(自定義一個 Class 與對應的結構體)中遇到的不愉快, 至少 類轉結構體 是有效的,也能說明一些問題。
本系列的文章,有:
1、Objective-C 中類的數據結構
2、Objective-C 中實例所占內存的大小
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網的支持。
新聞熱點
疑難解答