深入淺出 Runtime(一):初識

師大小海騰發表於2020-02-24

Runtime 系列文章

深入淺出 Runtime(一):初識
深入淺出 Runtime(二):資料結構
深入淺出 Runtime(三):訊息機制
深入淺出 Runtime(四):super 的本質
深入淺出 Runtime(五):具體應用
深入淺出 Runtime(六):相關面試題

深入淺出 Runtime(一):初識

Runtime 簡介

  • Runtime 是一個用C、彙編編寫的執行時庫,包含了很多 C 語言的 API,封裝了很多動態性相關的函式;
  • Objective-C 是一門動態執行時語言,允許很多操作推遲到程式執行時再進行。OC的動態性就是由 Runtime 來支撐和實現的,Rumtime 就是它的核心;
  • 我們平時編寫的OC程式碼,底層都是轉換成了 Runtime API 進行呼叫。

Objective-C 是一門動態執行時語言

什麼是編譯時與執行時?

  • 編譯時:編譯器將程式程式碼編譯成計算機能夠識別的語言,只進行一些簡單的語法檢查;
  • 執行時:程式碼跑起來,被裝載到記憶體中去,此時如果出錯會導致程式崩潰。如經典的 crash:unrecognized selector send to instance/class

編譯時語言與動態執行時語言的區別?

  • 編譯時語言:在編譯期進行函式決議;
  • 動態執行時語言:將函式決議推遲到執行時。
舉例

對於 NSString *string = [[NSMutableArray alloc]init];

  • 編譯時:編譯器進行型別檢查的時候,由於給一個NSString型別的指標賦值的是一個NSMutableArray物件,所以編譯器會給出型別不匹配的警告。但是編譯器會將 string當作NSString的例項,所以string物件呼叫NSString的方法,編譯沒有任何問題,而呼叫NSMutableArray的方法,編譯會直接報錯。
  • 執行時:由於string實際上是指向一個NSMutableArray物件,NSMutableArray物件沒有stringByAppendingString:方法,所以導致crash:unrecognized selector send to instance
    NSString *string = [[NSMutableArray alloc]init];  //⚠️Incompatible pointer types initializing 'NSString *' with an expression of type 'NSMutableArray *'
    [string stringByAppendingString:@"abc"];
    [string addObject:@"abc"];  //❌No visible @interface For 'NSString' declares the selector 'addObject:'
複製程式碼

Runtime 有兩個版本

  • Legacy (早期版本) ,對應的程式設計介面:Objective-C 1.0,應用於32-bit programs on OS X desktop
  • Modern (現代版本),對應的程式設計介面:Objective-C 2.0,應用於iPhone applications and 64-bit programs on OS X v10.5 and later

Objective-C 程式在三個不同的級別上與 Runtime 系統進行互動

  • 通過 Objective-C 原始碼;
  • 通過 Foundation 框架中 NSObject 類定義的方法,如:
// 根據 instance 物件或者類名獲得一個 class 物件
- (Class)class
+ (Class)class
// 判斷當前 instance/class 物件的 isa 指向是不是 class/meta-class 物件或者它的子類型別
- (BOOL)isKindOfClass:(Class)cls
+ (BOOL)isKindOfClass:(Class)cls
// 判斷當前 instance/class 物件的 isa 指向是不是 class/meta-class 物件型別
- (BOOL)isMemberOfClass:(Class)cls
+ (BOOL)isMemberOfClass:(Class)cls
// 判斷物件是否可以接收特定訊息
- (BOOL)respondsToSelector:(SEL)sel
+ (BOOL)respondsToSelector:(SEL)sel
// 判斷物件是否實現了特定協議中定義的方法
- (BOOL)conformsToProtocol:(Protocol *)protocol
+ (BOOL)conformsToProtocol:(Protocol *)protocol
// 可以根據一個 SEL,得到該方法的 IMP
- (IMP)methodForSelector:(SEL)sel
+ (IMP)methodForSelector:(SEL)sel
複製程式碼

類相關

// 動態建立一對類和元類(引數:父類,類名,額外的記憶體空間)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
// 註冊一對類和元類(要在類註冊之前新增成員變數)
void objc_registerClassPair(Class cls) 
// 銷燬一對類和元類
void objc_disposeClassPair(Class cls)
// 獲取 isa 指向的 Class
Class object_getClass(id obj)
// 設定 isa 指向的 Class
Class object_setClass(id obj, Class cls)
// 判斷一個 OC 物件是否為 Class
BOOL object_isClass(id obj)
// 判斷一個 Class 是否為元類
BOOL class_isMetaClass(Class cls)
// 獲取父類
Class class_getSuperclass(Class cls)
複製程式碼

成員變數相關

// 獲取一個例項變數資訊
Ivar class_getInstanceVariable(Class cls, const char *name)
// 拷貝例項變數列表(最後需要呼叫 free 釋放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
// 設定和獲取成員變數的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
// 動態新增成員變數(已經註冊的類是不能動態新增成員變數的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
// 獲取成員變數的相關資訊
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
複製程式碼

屬性相關

// 獲取一個屬性
objc_property_t class_getProperty(Class cls, const char *name)
// 拷貝屬性列表(最後需要呼叫 free 釋放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
// 動態新增屬性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
// 動態替換屬性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
// 獲取屬性的一些資訊
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
複製程式碼

方法相關

// 獲得一個例項方法、類方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
// 方法實現相關操作
IMP class_getMethodImplementation(Class cls, SEL name) 
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) 
// 拷貝方法列表(最後需要呼叫 free 釋放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
// 動態新增方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
// 動態替換方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
// 獲取方法的相關資訊(帶有 copy 的需要呼叫 free 去釋放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
// 選擇器相關
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
// 用 block 作為方法實現
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
複製程式碼

關聯物件相關

傳送門:OC - Association 關聯物件

// 新增關聯物件
void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
// 獲得關聯物件
id objc_getAssociatedObject(id object, const void * key)
// 移除指定 object 的所有關聯物件
void objc_removeAssociatedObjects(id object)
複製程式碼

Runtime 都有哪些應用?

  • 利用關聯物件(AssociatedObject)給分類新增屬性
  • 遍歷類的所有成員變數(修改 textfield 的佔位文字顏色、字典轉模型、自動歸檔解檔)
  • 交換方法實現(攔截交換系統的方法)
  • 利用訊息轉發機制解決方法找不到的異常問題
  • ......

相關連結

蘋果維護的 Runtime 開原始碼
GNU 維護的 Runtime 開原始碼
蘋果官方文件 Runtime

相關文章