什么是運行時
運行時是OC動態(tài)性得以實現(xiàn)的一個機(jī)制,OC以一個動態(tài)語言,把靜態(tài)語言編譯和鏈接的事情放到了運行時來處理,但是怎么處理呢,運行時機(jī)制就是處理這個事情的,它是一套用C和匯編編寫的API。
并且蘋果開源了API, 其中主要在文件runtime.h 和 message.h中
核心概念
類的本質(zhì)
objc_class 和 Class
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
}
// Class 結(jié)構(gòu)體指針, 指向某個類實例, 表示這個類
typedef struct objc_class *Class;
類的本質(zhì),或者說數(shù)據(jù)格式就是結(jié)構(gòu)體, 一個結(jié)構(gòu)體變量就是一個類對象,或者說實例。
objc_object 和 id
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
objc_selector 和 SEL
typedef struct objc_selector *SEL;
IMP 可以理解為函數(shù)指針, 指向函數(shù)實現(xiàn)首地址
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
/// 方法 Method 結(jié)構(gòu)體指針, 一個方法包含 方法名SEL, 方法類型 和 方法的實現(xiàn)IMP
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
typedef struct objc_method *Method;
/// 實例變量
struct objc_ivar {
char * _Nullable ivar_name // 變量名 OBJC2_UNAVAILABLE;
char * _Nullable ivar_type // 變量的類型編碼 OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
typedef struct objc_ivar *Ivar;
/// 分類 包括分類名, 類名,實例方法列表,類方法列表和協(xié)議列表
struct objc_category {
char * _Nonnull category_name OBJC2_UNAVAILABLE;
char * _Nonnull class_name OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
}
typedef struct objc_category *Category;
/// 屬性
typedef struct objc_property *objc_property_t;
類,父類,元類
/*
1, 實例的類其實也是對象, 叫做類對象,區(qū)別就是類對象在內(nèi)存中只有一份 類對象的類叫做元類
2,當(dāng)調(diào)用實例方法時, 會去類對象的方法列表中查找匹配
3,當(dāng)調(diào)用類方法時, 會去類的元類中查找
4,觀察類存儲結(jié)構(gòu)的定義, 發(fā)現(xiàn)有isa和 super兩個class類型的數(shù)據(jù), 其中isa指向所屬的類,super指向父類
5.每個實例對象的類都是類對象,每個類對象的類都是元類對象,每個元類對象的類都是根元類(root meta class的isa指向自身)
6.類對象的父類最終繼承自根類對象NSObject,NSObject的父類為nil
7.元類對象(包括根元類)的父類最終繼承自根類對象NSObject
*/

方法調(diào)用的本質(zhì)?
方法調(diào)用的本質(zhì)就是向方法調(diào)用者發(fā)送了一條消息,核心方法是
objc_msgSend(void /* id self, SEL op, ... */ ) , OC中的每一個方法調(diào)用會轉(zhuǎn)化成這個c函數(shù)調(diào)用, 其中第一個參數(shù)是方法的調(diào)用者, 第二個參數(shù)表示方法名, 之后是可變參數(shù)列表, 可以傳入調(diào)用方法需要的參數(shù)。那么這個方法底層做了什么呢, 它會去該實例的類對象的方法列表中尋找同名的方法, 如果在本類中找不到就去到父類中尋找, 如果找到同名方法SEL或者Selector后, 拿到對應(yīng)的IMP,然后根據(jù)參數(shù)調(diào)用對應(yīng)的函數(shù)。如果找不到就會進(jìn)入消息轉(zhuǎn)發(fā)
有什么作用
利用runtime提供的API我們可以獲取所有已經(jīng)注冊的類,獲取指定類的信息(包括名字,所有實例變量,屬性, 方法信息) 動態(tài)的創(chuàng)建類, 給類添加方法,屬性(assiociateObject)和 交換方法的實現(xiàn)(hook)
1, 獲取類的所有實例變量(類型 名字)其中實例變量包括類中定義的實例變量和屬性生成的實例變量
2,獲取屬性
3,獲取方法。 獲取對象方法是在本類中查找,, 獲取類方法需要到元類中查找
4,添加屬性。
4.1,對于還沒有注冊的類 添加屬性有相應(yīng)的函數(shù),但是屬性類型編碼需要看一下
4.2, 對于已經(jīng)注冊過的類,如果想添加屬性的話,只能使用關(guān)聯(lián)對象了。對于還沒有注冊的類 有相應(yīng)的函數(shù)可以添加屬性
5,添加方法 改變方法的實現(xiàn)
6,動態(tài)的添加一個類 創(chuàng)建實例
7,獲取實例變量 和 屬性區(qū)別
8, 用運行時配合KVC 改變系統(tǒng)的私有屬性
詳見demo https://github.com/JTWang4778/RuntimeDemo
在Swift中使用和在OC中使用有什么區(qū)別
Swift代碼中已經(jīng)沒有了Objective-C的運行時消息機(jī)制, 在代碼編譯時即確定了其實際調(diào)用的方法. 所以純粹的Swift類和對象沒有辦法使用runtime, 更不存在method swizzling.
為了兼容Objective-C, 凡是繼承NSObject的類都會保留其動態(tài)性, 依然遵循Objective-C的?運行時消息機(jī)制, 因此可以通過runtime獲取其屬性和方法, 實現(xiàn)method swizzling.
面向切面編程
APO, Aspect Oriented Programming面向切面編程, 可以通過預(yù)編譯方式和運行期動態(tài)代理實現(xiàn)在不修改源代碼的情況下給程序動態(tài)統(tǒng)一添加功能的一種技術(shù)。利用運行時我們可以在IOS開發(fā)中運用面向切面編程的思路給工程統(tǒng)一添加某一項功能。 典型的是給現(xiàn)有的類添加方法和屬性,然后hook到原有實現(xiàn)添加處理。 有名的三方有FDFullscreenPopGesture 和 MLeaksFinder
關(guān)于runtime的幾個問題
1, + class, -class 和 objc_getClass 作用一樣嗎?
object_getClass(obj)返回的是obj中的isa指針;而[obj class]則分兩種情況:一是當(dāng)obj為實例對象時,[obj class]中class是實例方法:- (Class)class,返回的obj對象中的isa指針;二是當(dāng)obj為類對象(包括元類和根類以及根元類)時,調(diào)用的是類方法:+ (Class)class,返回的結(jié)果為其本身。
http://www.itdecent.cn/p/ae5c32708bc6
2,isKindOfClass 和 isMemmberOfClass 區(qū)別
// JTView *subInstance = [JTView new];
// // 調(diào)用者是否為給定類的實例或任何繼承自給定類的實例。
// BOOL asdf = [subInstance isKindOfClass:[JTView class]];
// if (asdf) {
// NSLog(@"是子類");
// }else {
// NSLog(@"不是子類");
// }
// // 是否是給定類的實例
// if ([subInstance isMemberOfClass:[JTView class]]) {
// NSLog(@"是該類的子類");
// }else {
// NSLog(@"不是該類的子類");
// }
3, 幾個面試題
http://blog.sunnyxx.com/2014/11/06/runtime-nuts/
4,+load 和 +initialize 方法
- 調(diào)用方式不同,load方法是在main方法執(zhí)行之前,直接調(diào)用函數(shù),而initialize走的是消息機(jī)制
- 調(diào)用時機(jī)不同, load方法是在main方法執(zhí)行之前, 類加載進(jìn)內(nèi)存的時候調(diào)用的,并且只要類實現(xiàn)了load方法必定調(diào)用。而initialize是惰性調(diào)用, 只有第一次使用類的時候才會調(diào)用
- load方法的調(diào)用順序是(如果都實現(xiàn)的話)超類,子類,分類,對于多個分類的調(diào)用順序就不一定了。如果沒有實現(xiàn)就不調(diào)用,不會調(diào)用父類的load方法。也就是說一個類的load方法只調(diào)用一次,但是initialize可能調(diào)用多次。
- initialize 是線程安全的, 如果子類沒有實現(xiàn)就會調(diào)用父類的方法