1. 程式人生 > 實用技巧 >Objective-C Runtime2.0(-)

Objective-C Runtime2.0(-)

連結: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048-CH1-SW1
Objective-C執行時的改進: https://developer.apple.com/videos/play/wwdc2020/10163/

簡介

Objective-C一門動態語言, 它能在編譯的時候轉換為對應的C函式, 而訊息的接受者 (reciever) 和接受者執行的方法 selector , 只有在函式執行時才能確定, 所以說它是一門執行時語言. 通過在訊息傳送之前對函式的兩個引數 recever

(具體的執行物件)和 selector (執行的方法)進行不同的處理, 就可以實現各種黑魔法效果, 常見的有方法實現體(IMP)替換, 訊息轉發(Forward message), 同時為了配合執行時的特性, 它為每個方法和型別定義一套編碼規則, Objective-C的物件和型別可以轉換為Rumtime中指定的型別或這是標誌符號, 保證他們都能有一套對映標準。

版本和平臺

Runtime一共有2個版本, Objective-C 1.0和Objective-C 2.0, 它們的一個主要的區別就是:
舊版本執行時中,如果更改類中例項變數的佈局,則必須重新編譯從該類繼承的類。
新版本執行時中,如果更改類中例項變數的佈局,則不必重新編譯繼承自該類的類。
支援平臺包括 iOS

, OS X , 而這些僅使用於蘋果相關的應用

互動

  • Objective-C程式在三個不同的層次與執行時系統互動:

通過Objective-C原始碼;
通過在基礎框架的NSObject類中定義的方法;
通過直接呼叫執行時函式。

  • 通過編譯器生成對應的原始碼

在大多數情況下執行時胸在後臺自動工作, 只需要編寫和編譯原始碼就能使用它, 通常這些都是由Xcode來完成, 編譯包含Objective-C類和方法的程式碼時,編譯器將建立實現語言動態特性的資料結構和函式呼叫。資料結構捕獲類和類別定義以及協議宣告中的資訊;它們包括在用Objective-C程式語言定義類和協議時討論的類和協議物件,以及方法選擇器、例項變數模板和從原始碼中提取的其他資訊。主執行時函式是傳送訊息的函式,如訊息傳遞中所述。它由原始碼訊息表示式呼叫。

  • 直接呼叫NSObject中相關的方法

在Cocoa中的絕大部分物件都是基於 NSObject 生成的子類, 因此大部分類都繼承了它的基本方法(NSProxy是一個另外), 同時也提供依稀誒抽象的模版方法, 如 description , 則需要子類去實現. 因為每個類的具體內容不大一樣, 主要是為了方便 GDB print object命令 , 預設情況下它只包括一個類名稱和地址。部分的 NSObject 方法只是查詢執行時的系統資訊, 這些方法可以對型別進行安全性檢查

isKindOfClass
isMemenberOfClass
respondsToSelecctor
cconformsToProtocol
methodForSelector
  • 通過Runtime提供的函式呼叫

Swift工程中的api介紹
Source Code
提供了封裝的函式呼叫,包括獲取類的資訊,新增類,獲取例項物件的相關資訊,獲取類的定義,例項變數操作,物件關聯,方法操作,庫操作,方法選擇器操作,協議操作,屬性操作,還有 Objective-C 語言特有的功能,如bloc,weak操作。

傳送訊息

  • 在Objective-C中的方法呼叫 [Object method] 編譯之後它objc_msgSend(receiver, selector) 這樣的,此外還有 objc_msgSend(receiver, selector, arg1, arg2, ...) 帶引數傳送.Object 作為 recevier , method 作為 selector 引數.

  • 首先它會在 recevier(object)中查詢 selector`(方法選擇器)的函式實現體,然後找到之後根據換入的引數執行該方法,最後將函式的返回值作為自己的返回值

  • 從上面的例子可以看出訊息傳遞的關鍵在於 reciever 的類和它的結構,一個類主要包括2個關鍵的結構父類的指標,一個class的排程表,此表中的維持了一個方法選擇器和它的實現函式關聯的索引. selector-address.

  • 它有一個預設隱藏引數: 在編譯時, reciverselector 會自動被捕獲做為 objc_msgSend 的兩個引數,在程式碼中可以通過提供的關鍵字 _cmd 代表當前程式碼執行的方法.裡用這些特性可以實現對方法和物件的校驗,下面的官方simple給出的一個例子,它用於校驗當前的method是否為 strange .以及target是否為當前物件本省.


* strange {

    id  target = getTheReceiver();
    SEL method = getTheMethod();
 
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}
  • 獲取方法的地址

規避動態繫結的唯一方法是獲取方法的地址, 然後像呼叫函式一樣去呼叫它, 當一個特定的方法被連續執行很多次的時候, 動態呼叫就會帶來一定訊息轉發的開銷, 會比較浪費效能, 我們可以通過獲取函式地址直接呼叫, 這樣和可以節省訊息轉發的開銷

void (*setter)(id, SEL, BOOL); 
int i; 

setter = (void (*)(id, SEL, BOOL))[target

    methodForSelector:@selector(setFilled:)]; //methodForSelector由Runtime System提供,獲取具體的方法地址,及函式指標

for ( i = 0 ; i < 1000 ; i++ ) {

    setter(targetList[i], @selector(setFilled:), YES); //通過直接執行函式,效率更高,減少迴圈帶來的開銷

}  

動態方法解析

  • 在某些特殊的場合我們可能需要實現方法的動態呼叫,利於在某些類不包含某個方法的時候,我們需要將其轉發給特定的類處理來做一些異常蒐集工作. Objective-C Runtime 提供了動態屬性的的申明指令( @dynamic dirctive)。它會在編譯的時候告訴編譯器需要動態的查詢和解析該屬性.此外還可以通過實現以下2個方法來動態解析對應的 selector .
@implementation MyClass

* (BOOL)resolveInstanceMethod:(SEL)aSEL //這裡也可以是例項方法,當這個類沒有對應 `selector` 實現的時候就會找到這個方法,通過攔截它就可以對該方法做一些預設的處理.

{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          //為該方法做一些預設的實現操作,將它的實現寫在 `dynamicMethodIMP` 方法中
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}
@end

  • 動態loading

Objective-C程式可以在執行時載入和連結新的類和類別。新程式碼被合併到程式中,並與開始時載入的類和類別相同。
在Cocoa環境中,通常使用動態載入來定製應用程式,其他人編譯的程式在執行時載入的模組,就像 Interface Builder 載入自己定義的調色盤和OSX新系統首選項應用程式載入自定義的首選模組以下.
儘管一個執行時函式可以在Mach-O檔案中(objc_loadModules,在objc/objcload.h中定義)中執行 Objective-C 模組動態載入,但是 Cocoa 的NSBundle類為動態載入提供了一個非常方便的介面,該介面面向物件並與相關的服務整合,可以參考 NSBundle相關使用方法,通過NSBundle我們可以動態的載入 framework 而不必在啟動時載入,可以大幅度的減少冷啟動的時候,但也有它自己的弊端,很多的方法需要對 selector 進行減少以防程式崩潰,使用時需要根據具體的場景考量。

常用的方法如下:


* (NSArray *)allBundles
* (NSArray *)allFrameworks
* (NSBundle *)bundleForClass:(Class)aClass
* (BOOL)load //動態的載入bundle的可執行檔案程式碼到引用程式中
* (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName

訊息轉發

想不處理訊息的物件傳送錯誤的訊息時是錯誤的。但是, 在宣佈錯誤之前, 執行時系統會給接收物件第二次處理訊息的機會。

  • 轉發訊息

當傳送一條訊息給它不能處理的物件時, Runtime會將該訊息內容進行包裝, 然後呼叫物件的 forwardInvocation: 方法, 訊息的引數是一個 NSInvocation 物件, 可以在當前物件中實現 forwardInvocation: 方法, 對這條不能處理的訊息進行攔截和其他處理, 或者返回一個預設的資訊, 在 NSObject 中有定義了 forwardInvocation: 方法的實現, 然鵝它只是簡單的呼叫了 doesNotRecognizeSelector: 並會丟擲一個錯誤, 繼承於 NSObject 的子類通過重寫 forwardInvocation: 這個方法可以對轉發的訊息進攔截。


* (void)forwardInvocation:(NSInvocation *)anInvocation

{

    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];

}
  • 多重繼承實現

利用訊息轉發這一特性,可以實現多重繼承。當子類本身繼承某個類之後它還想實現其它類的方法,那就可以通過 forwardInvocation: 來實現訊息轉發。可以利用 NSProxy 來封裝一個通用類,將該類繼承的其他物件的方法做一個封裝

NS_ROOT_CLASS
@interface NSProxy <NSObject> {
    Class    isa;
}

* (id)alloc;
* (id)allocWithZone:(NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE;
* (Class)class;

* (void)forwardInvocation:(NSInvocation *)invocation;
* (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
* (void)dealloc;
* (void)finalize;
* (NSString *)description;
* (NSString *)debugDescription;
* (BOOL)respondsToSelector:(SEL)aSelector;

* (BOOL)allowsWeakReference NS_UNAVAILABLE;
* (BOOL)retainWeakReference NS_UNAVAILABLE;

// - (id)forwardingTargetForSelector:(SEL)aSelector;

@end

* (void)forwardInvocation:(NSInvocation *)anInvocation

{
    [proxy forwardInvocation: anInvocation];
}

  • 訊息封裝

在物件轉發訊息之前, 還需要實現這個方法, 因為 NSInvocation 需要依賴於 NSMethodSignature 來建立。它的類方法是 + invocationWithMethodSignature: , 自帶一個 NSMethodSignature 引數。 NSMethodSignature 是對Objective-C的方法引數進行編碼。


* (NSMethodSignature*)methodSignatureForSelector:(SEL)selector

{

    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;

}
//包裝訊息
...

    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];  
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self;  //引數一
    invocation.selector = aSelector; //引數二
    invocation setArgument:&argument atIndex:2 //引數三  
    ... //其它引數

...

型別編碼

為了幫助執行時系統,編譯器為每個方法的返回和引數型別進行了編碼,並將編碼後的字串於方法選擇器進行關聯.通過關鍵字 @encode(type) 註解來實現,它的返回值是一個字串,runtime定義了他們的每個編碼型別如下列表所示,我們也可以通過在工程中使用 print(%s), @encode(type)) 來確認編碼後的結果.

Code Meaning
c char
i int
s short
l long
q
C unsigned char
I unsigned int
S unsigned short
L unsigned long
Q unsigned long long
f float
d double
B C++ bool or a C99 _Bool
v void
* char*, 一個字串
@ 一個objet,id物件型別
# 一個Class物件
: 一個方法選擇器(SEL)
arry type 一個數組
{name=type...} 結構體
bnum bit大小的列舉
type 一個子針型別
? 一個未知型別,除此之外也用於函式指標

特殊型別: {examplestruct=@*i} , {NSObject=#} ,型別大都比較有規律,按照首字母和大寫來區分。

Objective-C語言

Objective-C是一種獨特的計算機語言,以其獨特的方括號表示式讓很多學習其他的語言的開發者都很難理解,但它的本質是為了提供複雜的面向物件程式設計,在早起的C語言中提供的物件只有結構體,和陣列,使用操作起來不是很方便,它是對C語言的補充,並有一套自己特有的編譯器clang,lvvm旨在打造高效率的面向物件程式語言.並以一種簡單明瞭的方式實現,基本上所有的語法操作都是為繞這 sendMessage來展開的。
主要包括以下幾個部分,在此連結中可以找到具體的定義和例子
Objects, Classes, and Messaging
Defining a Class
Protocols
Declared Properties
Categories and Extensions
Associative References
Fast Enumeration
Enabling Static Behavior
Selectors
Exception Handling
Threading

Objective-C編譯

  • Objective-C之所以被稱為執行時語言,是因為它在編譯時會翻譯成對應的C函式,下面通過它的翻譯構建過程來看看它的具體實現過程層.
  • ViewController.m 中隨便寫些程式碼,然後執行執行,找到 Xcode 的構建歷史記錄可以看到如下訊息從上面的構建記錄可以看出,它主要是通過 clang 來完成程式碼的編譯組裝成最終的可以執行檔案 (.o) , clang在編譯時指定了一系列的引數,runtime環境配置,語法檢查,中間編譯產物的記錄。

  • clang包含了很多的命令,下面也只是構建中的部分,具體可以參考 clang幫助文件,網上也有很多中文的解釋,不過還是得結合具體的應用場景來理解。

...XcodeDefault.xctoolchain/usr/bin/clang  //編譯器的執行檔案路徑
-x objective-c  // 指定編譯檔案的語言
-target x86_64-apple-ios12.0-simulator //指定需要生成code target,會根據這個arm指令生成查詢對應的彙編
-fmessage-length=0 //只是為了控制控制檯輸出換行列印好看而已
-fdiagnostics-show-note-include-stack //診斷檢查資訊的提示包括對應的棧資訊,提示用
-fmacro-backtrace-limit=0 //限定堆疊資訊
-std=gnu11  //指定編譯的語言版本
-fobjc-arc //開啟Arc,buildSettings有指定,iOS5之後就支援了
-fmodules //Enable the 'modules' language feature,開啟指定module的語言支援
-gmodules //Generate debug info with external references to clang modules or precompiled headers,生成clang modules或預編譯標頭檔案的debug資訊 
...
-DDEBUG=1 //是否是debug環境
-DOBJC_OLD_DISPATCH_PROTOTYPES=0 
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk  // Set the system root directory (usually /)
 .... 
-MMD -MT dependencies -MF /x86_64/ViewController.d  //將依賴輸出到指定的 ViewController.d檔案,記錄的依賴檔案的路徑
--serialize-diagnostics /x86_64/ViewController.dia //診斷檔案
-c  ../ViewController.m  //預處理編譯組裝,不會自動執行run
-o  ../x86_64/ViewController.o //輸出最終的編譯產物檔案
  • 通過Xcode工程檔案clang執行的是 -c 命令, 直接輸出了目標檔案, 要想檢視生成的中間檔案則需要通過 -rewrite-objc 命令檢視, 結合上面的構建步驟挑選出幾個必選的命令

clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-12.0.0 -isysroot /Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m , 實際這個檔案一般不會產生, 不過它可以更好的幫助我們理解runtime的實現過程, 探究Runtime底層實現必不可少的工具, 下面是翻譯之後的 Objective-C (簡稱OC)程式碼, 它程式設計了C++的檔案, 二原來用 []Objective-C 寫的那部分程式碼全部程式設計了C函式。

...
//用於定義類和類物件的結構
struct __rw_objc_super { 

    struct objc_object *object; 
    struct objc_object *superClass; 
    __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 

}; 

// ViewController: controller物件本身
// _cmd代表當前viewController需要執行的方法

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {

    
    //這個函式執行分為以下幾個步驟
    //1. 建立了 `__rw_objc_super` 的結構體物件,引數1: viewController例項self,ViewController class
    //2. 並向 ViewController中註冊了一個 `viewDidLoad` 方法
    //3. 返回了2個引數: self對應的 `__rw_objc_super` 結構體 和 `viewDidLoad` 的 `SEL`
    //4. __rw_objc_super查詢viewDidLoad具體實現並執行
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)  ((__rw_objc_super) {
       (id)self, (id)class_getSuperclass(objc_getClass("ViewController")) 
    }, sel_registerName("viewDidLoad"));

}
...

objc4-750初探

  • NSObject: 它是絕大部分的Objective-C的基類,封裝了runtime最基本的實現,暴露了runtime最常用的介面,類和物件的安全檢查以及訊息轉發,它包含了一個isa指標.所以對於任何繼承於NSObject的子類,它們都可以轉化為一個objc_class型別,它們的例項物件就是objc_class的結構體指標,相應的方法,屬性,協議都能在objc_class和它相關的屬性中找到.
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
  • Class是一個objc_class結構體指標型別,在新版本的runtime中,它繼承於objc_object,內部提供一個class_rw_t用來記錄類初始化中會更新的資料(用class_data_bits_t來儲存),很多關鍵屬性通過RO_RW_開頭的巨集定義變數來定義,對於比較常的方法通過申明放式在外部單實現,它本省只負責管理一些class基本的操作,記憶體分配,型別檢查,而關於它的方法,屬性,協議相關的屬性定義class_rw_t中間,由class_data_bits_t專門負責讀寫。
`typedef struct objc_class *Class;`

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable

    //儲存`class_rw_t`的資訊(read and write), 以下方法都是通過`bits`來進行讀寫更新`class_rw_t`的相關資訊
    class_data_bits_t bits;   
    class_rw_t *data() {  ..
    void setData(class_rw_t *newData) { ...
    void setInfo(uint32_t set) { ...
    void clearInfo(uint32_t clear) { ...
    void changeInfo(uint32_t set, uint32_t clear) { ... 
    //引用計數相關的方法檢查設定 retain/release/autorelease/retainCount/
    bool hasCustomRR() { 
    void setHasDefaultRR() { //設定預設的引用計數方法
    void setHasCustomRR(bool inherited = false);
    void printCustomRR(bool inherited);
    //檢查和設定 alloc/allocWithZone
    bool hasCustomAWZ() {  ...
    void setHasDefaultAWZ() { ...
    void setHasCustomAWZ(bool inherited = false);
    void printCustomAWZ(bool inherited);  
    //requires raw isa
    bool instancesRequireRawIsa() { ..
    void setInstancesRequireRawIsa(bool inherited = false);
    void printInstancesRequireRawIsa(bool inherited);
    ...
    //ARC環境檢測
    bool hasAutomaticIvars() { ... 
    bool isARC() { ...
    //物件關聯
    bool instancesHaveAssociatedObjects() { ...
    void setInstancesHaveAssociatedObjects() { ... 
    //是否允許增加快取
    bool shouldGrowCache() {  return true;
    bool isInitializing() {  return getMeta()->data()->flags & RW_INITIALIZING ...
    void setInitializing() {  ISA()->setInfo(RW_INITIALIZING);
    bool isInitialized() {  return getMeta()->data()->flags & RW_INITIALIZED; 
    void setInitialized();
  
    //抽象的實現
    bool isLoadable() {  return true;   ...
    
    //方法申明,程式碼解耦
    IMP getLoadMethod();

    // Locking: To prevent concurrent realization, hold runtimeLock.
    bool isRealized() {  return data()->flags & RW_REALIZED;

    // Returns true if this is an unrealized future class.
    // Locking: To prevent concurrent realization, hold runtimeLock.
    bool isFuture() {   return data()->flags & RW_FUTURE; ...
   
    //元類檢測
    bool isMetaClass() { ... return data()->ro->flags & RO_META; ...
    
    //元類指向它本身,元類就是`objc_class`
    // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }
    //當`objc_class`沒有super時候的時候它就是`RootClass`
    bool isRootClass() {
        return superclass == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }
    //獲取class名稱
    const char *mangledName() { 
        // fixme can't assert locks here
        assert(this);

        if (isRealized()  ||  isFuture()) {
            return data()->ro->name;
        } else {
            return ((const class_ro_t *)data())->name;
        }
    }
    //
    const char *demangledName(bool realize = false);
    const char *nameForLogging();

    
    uint32_t unalignedInstanceStart() {
        assert(isRealized());
        return data()->ro->instanceStart;
    }

    // 指定指標對齊方式
    uint32_t alignedInstanceStart() { ...
    uint32_t unalignedInstanceSize() {  ...
    uint32_t alignedInstanceSize() {  ...
    //指定初始化物件的大小,至少要求16位元組以上
    size_t instanceSize(size_t extraBytes) { ...
    void setInstanceSize(uint32_t newSize) { ...
    void chooseClassArrayIndex();
    // 設定類索引
    void setClassArrayIndex(unsigned Idx) { ...
    unsigned classArrayIndex() { ...
};

  • class_rw_t: 維護一個class經常需要變動的內容
//新增propterties/protocols/methods時機
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    rw->methods.attachLists(mlists, mcount);  ...
    rw->properties.attachLists(proplists, propcount);  ...
    rw->protocols.attachLists(protolists, protocount); ...
    ...
static void methodizeClass(Class cls){ ...

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version; 
    //一個只讀的class資訊管理
    const class_ro_t *ro; 
    //定義動態更新的方法,屬性和協議
    method_array_t c;
    property_array_t properties;
    protocol_array_t protocols; 
    //subclass記錄,放便資料的快速讀取
    Class firstSubclass;
    Class nextSiblingClass; 
    char *demangledName; 
    //flags修改和清除
    void setFlags(uint32_t set)  
    void clearFlags(uint32_t clear)  
    void changeFlags(uint32_t set, uint32_t clear) ...
};
  • class_ro_t: 只讀class結構體,提供了class load時最原始的資訊
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize; 
    const uint8_t * ivarLayout; 
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};
  • cache_t: 記錄當前類常用的方法列表
struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask; //用於控制快取的擴容
    mask_t _occupied; //記錄方法的數量(_buckets快取數量)

public:
    //cache更新
    ...
};
struct bucket_t { //主要負責記錄常用的方法並快取
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    MethodCacheIMP _imp;
    cache_key_t _key;
#else
    cache_key_t _key;
    MethodCacheIMP _imp;
#endif 
  • objc的物件關聯,它是通過另外一種方式新增的,通過一個全域性的AssociationsHashMap來實現的。同樣採用了DISGUISE來包裝object指標,防止誤檢測記憶體洩漏
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { ...
        //hash_map<disguised_ptr_t, ObjectAssociationMap *>
        AssociationsHashMap &associations(manager.associations());
       
}

Metal Class

  • 元類其實就是一個剛剛初始化的class_rw_t結構體指標,它的flagsRO_META.
static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta){ ...
    //在初始化一個類的時候,會同時建立2個類物件,並同時分配了`class_rw_t`和`class_ro_t`
    class_ro_t *cls_ro_w, *meta_ro_w;
    cls->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
    meta->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
    cls_ro_w   = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
    //注意此處,元類因為不能被修改,所以它直接採用了`class_ro_t`定義,
    meta_ro_w  = (class_ro_t *)calloc(sizeof(class_ro_t), 1); 
    cls->data()->ro = cls_ro_w;
    meta->data()->ro = meta_ro_w;

    cls->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
    meta->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
    cls->data()->version = 0;
    meta->data()->version = 7;

    cls_ro_w->flags = 0;
    //指定了元類的flags
    meta_ro_w->flags = RO_META; ....

    ....
    //當前建立的類通過傳入了`meta`class來定義
    //此時的`meta`元類只是剛剛初始化的結構體指標,除了runtime定義的基本特徵,沒有附加任何的其他邏輯
    cls->initClassIsa(meta);
    if (superclass) {
        meta->initClassIsa(superclass->ISA()->ISA());
        cls->superclass = superclass;
        meta->superclass = superclass->ISA();
        addSubclass(superclass, cls);
        addSubclass(superclass->ISA(), meta);
    } else {
        meta->initClassIsa(meta);
        cls->superclass = Nil;
        meta->superclass = cls; //元類的超類就是 rootClass
        addRootClass(cls); 
        addSubclass(cls, meta);
    }
    cls->cache.initializeToEmpty();
    meta->cache.initializeToEmpty();
    
    addClassTableEntry(cls);
}

//結合`object_class`中的原來
    //元類檢測,此出flag由類物件分配的時候設定
    bool isMetaClass() { ... return data()->ro->flags & RO_META; ...

//上面執行` cls->initClassIsa(meta);`最終會進到這個方法來
inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa.cls = cls; //元類的isa就是它本身 `cls->initClassIsa(meta)`
    } else {
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif

        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

Tagged Point

  • Tagged Point是蘋果為了提高簡單資料型別分配效率和節省記憶體開銷而誕生的,舉個栗子64位的空間中如果儲存一個大小隻佔有32位或者空間更小的資料,這個資料是用指標表示,指標表示一個引用,指標比資料的記憶體大的多,這樣通過指標訪問就有點得不嘗失了,所以就乾脆直接把資料儲存到指標中.通過對指標的記憶體空間重新layout得到tagged point

  • 用一個標誌位tag來表示資料型別,

  • payload來指定它所容納的資料

  • extended為擴充套件欄位

適用於NSNumber、NSDate,小的NSString型別,從它的記憶體分佈來看.

__attribute__ 關鍵字

Attribute官方文件介紹

  • 在閱讀原始碼中有發現使用了大量的的 __attribute__ 關鍵字,它是是 GUN C 提供的,能編譯器可以根據 __attribute__ 的定義為函式,型別,物件根據約定的標誌進行額外的操作,這樣就可以在我們編寫程式碼時對編譯規則進行適當的修改.
 __attribute__ ((deprecated)) 
 __attribute__((unavailable))
 __attribute__((unused))
__attribute__((noreturn)) 

int x __attribute__ ((aligned (16))) = 0; //位元組對齊,用更小的只來標示該型別,減少空間,最大不能超過連結器的最大對齊位元組
struct student
{
    char name[7];
    uint32_t id;
    char subject[5];
} __attribute__ ((aligned(4)));  //總位元組應為 12

short array[3] __attribute__ ((aligned (__BIGGEST_ALIGNMENT__))); //最大的機器位元組碼對齊

struct my_packed_struct
{
     char c;
     int i;
     struct my_unpacked_struct s;
}__attribute__ ((__packed__)); //按型別具體大小對齊,char+int

__attribute__((format (printf, 1, 2))); //對可變引數的函式格式進行校驗,(1表示為格式化字串位置,2表示可變參位置)
attribute__((used))用於告訴編譯器在目標檔案中保留一個靜態函式或者靜態變數,即使它沒有被引用。
__attribute__((always_inline)) 實現函式內聯,對於簡短頻繁呼叫的函式,直接放入符號表,減少堆疊開銷
__attribute__((constructor)) 修飾的函式在main函式之前執行,配合`__attribute__((destructor))`使用,常用統計庫的時間 
__attribute__((objc_designated_initializer)) 指定某個函式為初始化構造器,限制物件的建立介面,這點對於初始化封裝內部必須實現的內部邏輯非常有

TODO: