Runtime 系列文章
深入淺出 Runtime(一):初識
深入淺出 Runtime(二):資料結構
深入淺出 Runtime(三):訊息機制
深入淺出 Runtime(四):super 的本質
深入淺出 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
複製程式碼
- 通過直接呼叫 Runtime 函式,如:
類相關
// 動態建立一對類和元類(引數:父類,類名,額外的記憶體空間)
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)
複製程式碼
關聯物件相關
// 新增關聯物件
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 的佔位文字顏色、字典轉模型、自動歸檔解檔)
- 交換方法實現(攔截交換系統的方法)
- 利用訊息轉發機制解決方法找不到的異常問題
- ......