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運(yùn)行時的改進(jìn): https://developer.apple.com/videos/play/wwdc2020/10163/

簡介

Objective-C一門動態(tài)語言, 它能在編譯的時候轉(zhuǎn)換為對應(yīng)的C函數(shù), 而消息的接受者 (reciever) 和接受者執(zhí)行的方法 selector , 只有在函數(shù)運(yùn)行時才能確定, 所以說它是一門運(yùn)行時語言. 通過在消息發(fā)送之前對函數(shù)的兩個參數(shù) recever (具體的執(zhí)行對象)和 selector (執(zhí)行的方法)進(jìn)行不同的處理, 就可以實(shí)現(xiàn)各種黑魔法效果, 常見的有方法實(shí)現(xiàn)體(IMP)替換, 消息轉(zhuǎn)發(fā)(Forward message), 同時為了配合運(yùn)行時的特性, 它為每個方法和類型定義一套編碼規(guī)則, Objective-C的對象和類型可以轉(zhuǎn)換為Rumtime中指定的類型或這是標(biāo)志符號, 保證他們都能有一套映射標(biāo)準(zhǔn)。

Runtime的版本和運(yùn)行平臺

Runtime一共有2個版本, Objective-C 1.0和Objective-C 2.0, 它們的一個主要的區(qū)別就是:
舊版本運(yùn)行時中,如果更改類中實(shí)例變量的布局,則必須重新編譯從該類繼承的類。
新版本運(yùn)行時中,如果更改類中實(shí)例變量的布局,則不必重新編譯繼承自該類的類。
支持平臺包括 iOS , OS X , 而這些僅使用于蘋果相關(guān)的應(yīng)用

與Runtime之間的交互

  • Objective-C程序在三個不同的層次與運(yùn)行時系統(tǒng)交互:

通過Objective-C源代碼;
通過在基礎(chǔ)框架的NSObject類中定義的方法;
通過直接調(diào)用運(yùn)行時函數(shù)。

  • 通過編譯器生成對應(yīng)的源代碼

在大多數(shù)情況下運(yùn)行時胸在后臺自動工作, 只需要編寫和編譯源代碼就能使用它, 通常這些都是由Xcode來完成, 編譯包含Objective-C類和方法的代碼時,編譯器將創(chuàng)建實(shí)現(xiàn)語言動態(tài)特性的數(shù)據(jù)結(jié)構(gòu)和函數(shù)調(diào)用。數(shù)據(jù)結(jié)構(gòu)捕獲類和類別定義以及協(xié)議聲明中的信息;它們包括在用Objective-C編程語言定義類和協(xié)議時討論的類和協(xié)議對象,以及方法選擇器、實(shí)例變量模板和從源代碼中提取的其他信息。主運(yùn)行時函數(shù)是發(fā)送消息的函數(shù),如消息傳遞中所述。它由源代碼消息表達(dá)式調(diào)用。

  • 直接調(diào)用NSObject中相關(guān)的方法

在Cocoa中的絕大部分對象都是基于 NSObject 生成的子類, 因此大部分類都繼承了它的基本方法(NSProxy是一個另外), 同時也提供依稀誒抽象的模版方法, 如 description , 則需要子類去實(shí)現(xiàn). 因?yàn)槊總€類的具體內(nèi)容不大一樣, 主要是為了方便 GDB print object命令 , 默認(rèn)情況下它只包括一個類名稱和地址。部分的 NSObject 方法只是查詢運(yùn)行時的系統(tǒng)信息, 這些方法可以對類型進(jìn)行安全性檢查

isKindOfClass
isMemenberOfClass
respondsToSelecctor
cconformsToProtocol
methodForSelector
  • 通過Runtime提供的函數(shù)調(diào)用

Swift工程中的api介紹
Source Code
提供了封裝的函數(shù)調(diào)用,包括獲取類的信息,添加類,獲取實(shí)例對象的相關(guān)信息,獲取類的定義,實(shí)例變量操作,對象關(guān)聯(lián),方法操作,庫操作,方法選擇器操作,協(xié)議操作,屬性操作,還有 Objective-C 語言特有的功能,如bloc,weak操作。

消息發(fā)送

  • 在Objective-C中的方法調(diào)用 [Object method] 編譯之后它是 objc_msgSend(receiver, selector) 這樣的,此外還有 objc_msgSend(receiver, selector, arg1, arg2, ...) 帶參數(shù)發(fā)送.

Object 作為 recevier , method 作為 selector 參數(shù).

首先它會在 recevier(object)中查找 selector`(方法選擇器)的函數(shù)實(shí)現(xiàn)體,
然后找到之后根據(jù)換入的參數(shù)執(zhí)行該方法
最后將函數(shù)的返回值作為自己的返回值

  • 從上面的例子可以看出消息傳遞的關(guān)鍵在于 reciever 的類和它的結(jié)構(gòu),一個類主要包括2個關(guān)鍵的結(jié)構(gòu)

父類的指針
一個class的調(diào)度表,此表中的維持了一個方法選擇器和它的實(shí)現(xiàn)函數(shù)關(guān)聯(lián)的索引. selector-address

  • 隱藏參數(shù)

在編譯時, reciverselector 會自動被捕獲做為 objc_msgSend 的兩個參數(shù),在代碼中可以通過提供的關(guān)鍵字 _cmd 代表當(dāng)前代碼執(zhí)行的方法.里用這些特性可以實(shí)現(xiàn)對方法和對象的校驗(yàn),下面的官方simple給出的一個例子,它用于校驗(yàn)當(dāng)前的method是否為 strange .以及target是否為當(dāng)前對象本省.


* strange {

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

規(guī)避動態(tài)綁定的唯一方法是獲取方法的地址, 然后像調(diào)用函數(shù)一樣去調(diào)用它, 當(dāng)一個特定的方法被連續(xù)執(zhí)行很多次的時候, 動態(tài)調(diào)用就會帶來一定消息轉(zhuǎn)發(fā)的開銷, 會比較浪費(fèi)性能, 我們可以通過獲取函數(shù)地址直接調(diào)用, 這樣和可以節(jié)省消息轉(zhuǎn)發(fā)的開銷

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

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

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

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

    setter(targetList[i], @selector(setFilled:), YES); //通過直接執(zhí)行函數(shù),效率更高,減少循環(huán)帶來的開銷

}  

動態(tài)方法解析

  • 在某些特殊的場合我們可能需要實(shí)現(xiàn)方法的動態(tài)調(diào)用,利于在某些類不包含某個方法的時候,我們需要將其轉(zhuǎn)發(fā)給特定的類處理來做一些異常搜集工作. Objective-C Runtime 提供了動態(tài)屬性的的申明指令( @dynamic dirctive)。它會在編譯的時候告訴編譯器需要動態(tài)的查找和解析該屬性.此外還可以通過實(shí)現(xiàn)以下2個方法來動態(tài)解析對應(yīng)的 selector .
@implementation MyClass

* (BOOL)resolveInstanceMethod:(SEL)aSEL //這里也可以是實(shí)例方法,當(dāng)這個類沒有對應(yīng) `selector` 實(shí)現(xiàn)的時候就會找到這個方法,通過攔截它就可以對該方法做一些默認(rèn)的處理.

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

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

  • 動態(tài)loading

Objective-C程序可以在運(yùn)行時加載和鏈接新的類和類別。新代碼被合并到程序中,并與開始時加載的類和類別相同。
在Cocoa環(huán)境中,通常使用動態(tài)加載來定制應(yīng)用程序,其他人編譯的程序在運(yùn)行時加載的模塊,就像 Interface Builder 加載自己定義的調(diào)色板和OSX新系統(tǒng)首選項(xiàng)應(yīng)用程序加載自定義的首選模塊以下.
盡管一個運(yùn)行時函數(shù)可以在Mach-O文件中(objc_loadModules,在objc/objcload.h中定義)中執(zhí)行 Objective-C 模塊動態(tài)加載,但是 Cocoa 的NSBundle類為動態(tài)加載提供了一個非常方便的接口,該接口面向?qū)ο蟛⑴c相關(guān)的服務(wù)集成,可以參考 NSBundle相關(guān)使用方法,通過NSBundle我們可以動態(tài)的加載 framework 而不必在啟動時加載,可以大幅度的減少冷啟動的時候,但也有它自己的弊端,很多的方法需要對 selector 進(jìn)行減少以防程序崩潰,使用時需要根據(jù)具體的場景考量。

常用的方法如下:


* (NSArray *)allBundles
* (NSArray *)allFrameworks
* (NSBundle *)bundleForClass:(Class)aClass
* (BOOL)load //動態(tài)的加載bundle的可執(zhí)行文件代碼到引用程序中
* (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName

消息轉(zhuǎn)發(fā)

想不處理消息的對象發(fā)送錯誤的消息時是錯誤的。但是, 在宣布錯誤之前, 運(yùn)行時系統(tǒng)會給接收對象第二次處理消息的機(jī)會。

  • 轉(zhuǎn)發(fā)消息

當(dāng)發(fā)送一條消息給它不能處理的對象時, Runtime會將該消息內(nèi)容進(jìn)行包裝, 然后調(diào)用對象的 forwardInvocation: 方法, 消息的參數(shù)是一個 NSInvocation 對象, 可以在當(dāng)前對象中實(shí)現(xiàn) forwardInvocation: 方法, 對這條不能處理的消息進(jìn)行攔截和其他處理, 或者返回一個默認(rèn)的信息, 在 NSObject 中有定義了 forwardInvocation: 方法的實(shí)現(xiàn), 然鵝它只是簡單的調(diào)用了 doesNotRecognizeSelector: 并會拋出一個錯誤, 繼承于 NSObject 的子類通過重寫 forwardInvocation: 這個方法可以對轉(zhuǎn)發(fā)的消息進(jìn)攔截。


* (void)forwardInvocation:(NSInvocation *)anInvocation

{

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

}
  • 多重繼承實(shí)現(xiàn)

利用消息轉(zhuǎn)發(fā)這一特性,可以實(shí)現(xiàn)多重繼承。當(dāng)子類本身繼承某個類之后它還想實(shí)現(xiàn)其它類的方法,那就可以通過 forwardInvocation: 來實(shí)現(xiàn)消息轉(zhuǎn)發(fā)。可以利用 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];
}

  • 消息封裝

在對象轉(zhuǎn)發(fā)消息之前, 還需要實(shí)現(xiàn)這個方法, 因?yàn)?NSInvocation 需要依賴于 NSMethodSignature 來創(chuàng)建。它的類方法是 + invocationWithMethodSignature: , 自帶一個 NSMethodSignature 參數(shù)。 NSMethodSignature 是對Objective-C的方法參數(shù)進(jìn)行編碼。


* (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;  //參數(shù)一
    invocation.selector = aSelector; //參數(shù)二
    invocation setArgument:&argument atIndex:2 //參數(shù)三  
    ... //其它參數(shù)

...

類型編碼

為了幫助運(yùn)行時系統(tǒng),編譯器為每個方法的返回和參數(shù)類型進(jìn)行了編碼,并將編碼后的字符串于方法選擇器進(jìn)行關(guān)聯(lián).通過關(guān)鍵字 @encode(type) 注解來實(shí)現(xiàn),它的返回值是一個字符串,runtime定義了他們的每個編碼類型如下列表所示,我們也可以通過在工程中使用 print(%s), @encode(type)) 來確認(rèn)編碼后的結(jié)果.

Code Meaning
c char
i int
s short
l long
q long long
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 一個數(shù)組
{name=type...} 結(jié)構(gòu)體
bnum bit大小的枚舉
^type 一個子針類型
? 一個未知類型,除此之外也用于函數(shù)指針

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

Objective-C語言

Objective-C是一種獨(dú)特的計(jì)算機(jī)語言,以其獨(dú)特的方括號表達(dá)式讓很多學(xué)習(xí)其他的語言的開發(fā)者都很難理解,但它的本質(zhì)是為了提供復(fù)雜的面向?qū)ο缶幊?在早起的C語言中提供的對象只有結(jié)構(gòu)體,和數(shù)組,使用操作起來不是很方便,它是對C語言的補(bǔ)充,并有一套自己特有的編譯器clang,lvvm旨在打造高效率的面向?qū)ο缶幊陶Z言.并以一種簡單明了的方式實(shí)現(xiàn),基本上所有的語法操作都是為繞這 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之所以被稱為運(yùn)行時語言,是因?yàn)樗诰幾g時會翻譯成對應(yīng)的C函數(shù),下面通過它的翻譯構(gòu)建過程來看看它的具體實(shí)現(xiàn)過程層.

  • ViewController.m 中隨便寫些代碼,然后執(zhí)行運(yùn)行,找到 Xcode 的構(gòu)建歷史記錄可以看到如下消息從上面的構(gòu)建記錄可以看出,它主要是通過 clang 來完成代碼的編譯組裝成最終的可以執(zhí)行文件 (.o) , clang在編譯時指定了一系列的參數(shù),runtime環(huán)境配置,語法檢查,中間編譯產(chǎn)物的記錄。

  • clang包含了很多的命令,下面也只是構(gòu)建中的部分,具體可以參考 clang幫助文檔,網(wǎng)上也有很多中文的解釋,不過還是得結(jié)合具體的應(yīng)用場景來理解。

...XcodeDefault.xctoolchain/usr/bin/clang  //編譯器的執(zhí)行文件路徑
-x objective-c  // 指定編譯文件的語言
-target x86_64-apple-ios12.0-simulator //指定需要生成code target,會根據(jù)這個arm指令生成查找對應(yīng)的匯編
-fmessage-length=0 //只是為了控制控制臺輸出換行打印好看而已
-fdiagnostics-show-note-include-stack //診斷檢查信息的提示包括對應(yīng)的棧信息,提示用
-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或預(yù)編譯頭文件的debug信息 
...
-DDEBUG=1 //是否是debug環(huán)境
-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  //預(yù)處理編譯組裝,不會自動執(zhí)行run
-o  ../x86_64/ViewController.o //輸出最終的編譯產(chǎn)物文件
  • 通過Xcode工程文件clang執(zhí)行的是 -c 命令, 直接輸出了目標(biāo)文件, 要想查看生成的中間文件則需要通過 -rewrite-objc 命令查看, 結(jié)合上面的構(gòu)建步驟挑選出幾個必選的命令

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 , 實(shí)際這個文件一般不會產(chǎn)生, 不過它可以更好的幫助我們理解runtime的實(shí)現(xiàn)過程, 探究Runtime底層實(shí)現(xiàn)必不可少的工具, 下面是翻譯之后的 Objective-C (簡稱OC)代碼, 它編程了C++的文件, 二原來用 []Objective-C 寫的那部分代碼全部編程了C函數(shù)。

...
//用于定義類和類對象的結(jié)構(gòu)
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代表當(dāng)前viewController需要執(zhí)行的方法

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

    
    //這個函數(shù)執(zhí)行分為以下幾個步驟
    //1. 創(chuàng)建了 `__rw_objc_super` 的結(jié)構(gòu)體對象,參數(shù)1: viewController實(shí)例self,ViewController class
    //2. 并向 ViewController中注冊了一個 `viewDidLoad` 方法
    //3. 返回了2個參數(shù): self對應(yīng)的 `__rw_objc_super` 結(jié)構(gòu)體 和 `viewDidLoad` 的 `SEL`
    //4. __rw_objc_super查找viewDidLoad具體實(shí)現(xiàn)并執(zhí)行
    ((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最基本的實(shí)現(xiàn),暴露了runtime最常用的接口,類和對象的安全檢查以及消息轉(zhuǎn)發(fā),它包含了一個isa指針.所以對于任何繼承于NSObject的子類,它們都可以轉(zhuǎn)化為一個objc_class類型,它們的實(shí)例對象就是objc_class的結(jié)構(gòu)體指針,相應(yīng)的方法,屬性,協(xié)議都能在objc_class和它相關(guān)的屬性中找到.
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結(jié)構(gòu)體指針類型,在新版本的runtime中,它繼承于objc_object,內(nèi)部提供一個class_rw_t用來記錄類初始化中會更新的數(shù)據(jù)(用class_data_bits_t來保存),很多關(guān)鍵屬性通過RO_RW_開頭的宏定義變量來定義,對于比較常的方法通過申明放式在外部單實(shí)現(xiàn),它本省只負(fù)責(zé)管理一些class基本的操作,內(nèi)存分配,類型檢查,而關(guān)于它的方法,屬性,協(xié)議相關(guān)的屬性定義class_rw_t中間,由class_data_bits_t專門負(fù)責(zé)讀寫。
`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`來進(jìn)行讀寫更新`class_rw_t`的相關(guān)信息
    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) { ... 
    //引用計(jì)數(shù)相關(guān)的方法檢查設(shè)置 retain/release/autorelease/retainCount/
    bool hasCustomRR() { 
    void setHasDefaultRR() { //設(shè)置默認(rèn)的引用計(jì)數(shù)方法
    void setHasCustomRR(bool inherited = false);
    void printCustomRR(bool inherited);
    //檢查和設(shè)置 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環(huán)境檢測
    bool hasAutomaticIvars() { ... 
    bool isARC() { ...
    //對象關(guān)聯(lián)
    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();
  
    //抽象的實(shí)現(xiàn)
    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();
    }
    //當(dāng)`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字節(jié)以上
    size_t instanceSize(size_t extraBytes) { ...
    void setInstanceSize(uint32_t newSize) { ...
    void chooseClassArrayIndex();
    // 設(shè)置類索引
    void setClassArrayIndex(unsigned Idx) { ...
    unsigned classArrayIndex() { ...
};

  • class_rw_t: 維護(hù)一個class經(jīng)常需要變動的內(nèi)容
//添加propterties/protocols/methods時機(jī)
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; 
    //定義動態(tài)更新的方法,屬性和協(xié)議
    method_array_t c;
    property_array_t properties;
    protocol_array_t protocols; 
    //subclass記錄,放便數(shù)據(jù)的快速讀取
    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結(jié)構(gòu)體,提供了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: 記錄當(dāng)前類常用的方法列表
struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask; //用于控制緩存的擴(kuò)容
    mask_t _occupied; //記錄方法的數(shù)量(_buckets緩存數(shù)量)

public:
    //cache更新
    ...
};
struct bucket_t { //主要負(fù)責(zé)記錄常用的方法并緩存
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的對象關(guān)聯(lián),它是通過另外一種方式添加的,通過一個全局的AssociationsHashMap來實(shí)現(xiàn)的。同樣采用了DISGUISE來包裝object指針,防止誤檢測內(nèi)存泄漏
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

  • 元類其實(shí)就是一個剛剛初始化的class_rw_t結(jié)構(gòu)體指針,它的flagsRO_META.
static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta){ ...
    //在初始化一個類的時候,會同時創(chuàng)建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);
    //注意此處,元類因?yàn)椴荒鼙恍薷?所以它直接采用了`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; ....

    ....
    //當(dāng)前創(chuàng)建的類通過傳入了`meta`class來定義
    //此時的`meta`元類只是剛剛初始化的結(jié)構(gòu)體指針,除了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);
}

//結(jié)合`object_class`中的原來
    //元類檢測,此出flag由類對象分配的時候設(shè)置
    bool isMetaClass() { ... return data()->ro->flags & RO_META; ...

//上面執(zhí)行` cls->initClassIsa(meta);`最終會進(jìn)到這個方法來
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是蘋果為了提高簡單數(shù)據(jù)類型分配效率和節(jié)省內(nèi)存開銷而誕生的,舉個栗子64位的空間中如果保存一個大小只占有32位或者空間更小的數(shù)據(jù),這個數(shù)據(jù)是用指針表示,指針表示一個引用,指針比數(shù)據(jù)的內(nèi)存大的多,這樣通過指針訪問就有點(diǎn)得不嘗失了,所以就干脆直接把數(shù)據(jù)保存到指針中.通過對指針的內(nèi)存空間重新layout得到tagged point

  • 用一個標(biāo)志位tag來表示數(shù)據(jù)類型,

  • payload來指定它所容納的數(shù)據(jù)

  • extended為擴(kuò)展字段

適用于NSNumber、NSDate,小的NSString類型,從它的內(nèi)存分布來看.

__attribute__ 關(guān)鍵字

Attribute官方文檔介紹

  • 在閱讀源碼中有發(fā)現(xiàn)使用了大量的的 __attribute__ 關(guān)鍵字,它是是 GUN C 提供的,能編譯器可以根據(jù) __attribute__ 的定義為函數(shù),類型,對象根據(jù)約定的標(biāo)志進(jìn)行額外的操作,這樣就可以在我們編寫代碼時對編譯規(guī)則進(jìn)行適當(dāng)?shù)男薷?
 __attribute__ ((deprecated)) 
 __attribute__((unavailable))
 __attribute__((unused))
__attribute__((noreturn)) 

int x __attribute__ ((aligned (16))) = 0; //字節(jié)對齊,用更小的只來標(biāo)示該類型,減少空間,最大不能超過鏈接器的最大對齊字節(jié)
struct student
{
    char name[7];
    uint32_t id;
    char subject[5];
} __attribute__ ((aligned(4)));  //總字節(jié)應(yīng)為 12

short array[3] __attribute__ ((aligned (__BIGGEST_ALIGNMENT__))); //最大的機(jī)器字節(jié)碼對齊

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

__attribute__((format (printf, 1, 2))); //對可變參數(shù)的函數(shù)格式進(jìn)行校驗(yàn),(1表示為格式化字符串位置,2表示可變參位置)
attribute__((used))用于告訴編譯器在目標(biāo)文件中保留一個靜態(tài)函數(shù)或者靜態(tài)變量,即使它沒有被引用。
__attribute__((always_inline)) 實(shí)現(xiàn)函數(shù)內(nèi)聯(lián),對于簡短頻繁調(diào)用的函數(shù),直接放入符號表,減少堆棧開銷
__attribute__((constructor)) 修飾的函數(shù)在main函數(shù)之前執(zhí)行,配合`__attribute__((destructor))`使用,常用統(tǒng)計(jì)庫的時間 
__attribute__((objc_designated_initializer)) 指定某個函數(shù)為初始化構(gòu)造器,限制對象的創(chuàng)建接口,這點(diǎn)對于初始化封裝內(nèi)部必須實(shí)現(xiàn)的內(nèi)部邏輯非常有

TODO:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容