什么是運(yùn)行時(shí)
運(yùn)行時(shí)是OC動(dòng)態(tài)性得以實(shí)現(xiàn)的一個(gè)機(jī)制映之,OC以一個(gè)動(dòng)態(tài)語(yǔ)言,把靜態(tài)語(yǔ)言編譯和鏈接的事情放到了運(yùn)行時(shí)來(lái)處理怜奖,但是怎么處理呢浑测,運(yùn)行時(shí)機(jī)制就是處理這個(gè)事情的,它是一套用C和匯編編寫(xiě)的API。
并且蘋(píng)果開(kāi)源了API迁央, 其中主要在文件runtime.h 和 message.h中
核心概念
類(lèi)的本質(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)體指針掷匠, 指向某個(gè)類(lèi)實(shí)例, 表示這個(gè)類(lèi)
typedef struct objc_class *Class;
類(lèi)的本質(zhì)岖圈,或者說(shuō)數(shù)據(jù)格式就是結(jié)構(gòu)體讹语, 一個(gè)結(jié)構(gòu)體變量就是一個(gè)類(lèi)對(duì)象,或者說(shuō)實(shí)例蜂科。
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ù)實(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)體指針, 一個(gè)方法包含 方法名SEL导匣, 方法類(lèi)型 和 方法的實(shí)現(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;
/// 實(shí)例變量
struct objc_ivar {
char * _Nullable ivar_name // 變量名 OBJC2_UNAVAILABLE;
char * _Nullable ivar_type // 變量的類(lèi)型編碼 OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
typedef struct objc_ivar *Ivar;
/// 分類(lèi) 包括分類(lèi)名擎值, 類(lèi)名,實(shí)例方法列表逐抑,類(lèi)方法列表和協(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;
類(lèi),父類(lèi)屹蚊,元類(lèi)
/*
1, 實(shí)例的類(lèi)其實(shí)也是對(duì)象厕氨, 叫做類(lèi)對(duì)象,區(qū)別就是類(lèi)對(duì)象在內(nèi)存中只有一份 類(lèi)對(duì)象的類(lèi)叫做元類(lèi)
2汹粤,當(dāng)調(diào)用實(shí)例方法時(shí)命斧, 會(huì)去類(lèi)對(duì)象的方法列表中查找匹配
3,當(dāng)調(diào)用類(lèi)方法時(shí)嘱兼, 會(huì)去類(lèi)的元類(lèi)中查找
4国葬,觀察類(lèi)存儲(chǔ)結(jié)構(gòu)的定義, 發(fā)現(xiàn)有isa和 super兩個(gè)class類(lèi)型的數(shù)據(jù)芹壕, 其中isa指向所屬的類(lèi)汇四,super指向父類(lèi)
5.每個(gè)實(shí)例對(duì)象的類(lèi)都是類(lèi)對(duì)象,每個(gè)類(lèi)對(duì)象的類(lèi)都是元類(lèi)對(duì)象踢涌,每個(gè)元類(lèi)對(duì)象的類(lèi)都是根元類(lèi)(root meta class的isa指向自身)
6.類(lèi)對(duì)象的父類(lèi)最終繼承自根類(lèi)對(duì)象NSObject通孽,NSObject的父類(lèi)為nil
7.元類(lèi)對(duì)象(包括根元類(lèi))的父類(lèi)最終繼承自根類(lèi)對(duì)象NSObject
*/
方法調(diào)用的本質(zhì)?
方法調(diào)用的本質(zhì)就是向方法調(diào)用者發(fā)送了一條消息睁壁,核心方法是
objc_msgSend(void /* id self, SEL op, ... */ ) , OC中的每一個(gè)方法調(diào)用會(huì)轉(zhuǎn)化成這個(gè)c函數(shù)調(diào)用背苦, 其中第一個(gè)參數(shù)是方法的調(diào)用者钦无, 第二個(gè)參數(shù)表示方法名痰哨, 之后是可變參數(shù)列表, 可以傳入調(diào)用方法需要的參數(shù)牍蜂。那么這個(gè)方法底層做了什么呢钳降, 它會(huì)去該實(shí)例的類(lèi)對(duì)象的方法列表中尋找同名的方法厚宰, 如果在本類(lèi)中找不到就去到父類(lèi)中尋找, 如果找到同名方法SEL或者Selector后遂填, 拿到對(duì)應(yīng)的IMP固阁,然后根據(jù)參數(shù)調(diào)用對(duì)應(yīng)的函數(shù)壤躲。如果找不到就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)
有什么作用
利用runtime提供的API我們可以獲取所有已經(jīng)注冊(cè)的類(lèi),獲取指定類(lèi)的信息(包括名字备燃,所有實(shí)例變量碉克,屬性, 方法信息) 動(dòng)態(tài)的創(chuàng)建類(lèi)并齐, 給類(lèi)添加方法漏麦,屬性(assiociateObject)和 交換方法的實(shí)現(xiàn)(hook)
1, 獲取類(lèi)的所有實(shí)例變量(類(lèi)型 名字)其中實(shí)例變量包括類(lèi)中定義的實(shí)例變量和屬性生成的實(shí)例變量
2,獲取屬性
3况褪,獲取方法撕贞。 獲取對(duì)象方法是在本類(lèi)中查找,测垛, 獲取類(lèi)方法需要到元類(lèi)中查找
4捏膨,添加屬性。
4.1食侮,對(duì)于還沒(méi)有注冊(cè)的類(lèi) 添加屬性有相應(yīng)的函數(shù)号涯,但是屬性類(lèi)型編碼需要看一下
4.2, 對(duì)于已經(jīng)注冊(cè)過(guò)的類(lèi)锯七,如果想添加屬性的話链快,只能使用關(guān)聯(lián)對(duì)象了。對(duì)于還沒(méi)有注冊(cè)的類(lèi) 有相應(yīng)的函數(shù)可以添加屬性
5眉尸,添加方法 改變方法的實(shí)現(xiàn)
6域蜗,動(dòng)態(tài)的添加一個(gè)類(lèi) 創(chuàng)建實(shí)例
7,獲取實(shí)例變量 和 屬性區(qū)別
8噪猾, 用運(yùn)行時(shí)配合KVC 改變系統(tǒng)的私有屬性
詳見(jiàn)demo https://github.com/JTWang4778/RuntimeDemo
在Swift中使用和在OC中使用有什么區(qū)別
Swift代碼中已經(jīng)沒(méi)有了Objective-C的運(yùn)行時(shí)消息機(jī)制, 在代碼編譯時(shí)即確定了其實(shí)際調(diào)用的方法. 所以純粹的Swift類(lèi)和對(duì)象沒(méi)有辦法使用runtime, 更不存在method swizzling.
為了兼容Objective-C, 凡是繼承NSObject的類(lèi)都會(huì)保留其動(dòng)態(tài)性, 依然遵循Objective-C的?運(yùn)行時(shí)消息機(jī)制, 因此可以通過(guò)runtime獲取其屬性和方法, 實(shí)現(xiàn)method swizzling.
面向切面編程
APO霉祸, Aspect Oriented Programming面向切面編程, 可以通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)在不修改源代碼的情況下給程序動(dòng)態(tài)統(tǒng)一添加功能的一種技術(shù)袱蜡。利用運(yùn)行時(shí)我們可以在IOS開(kāi)發(fā)中運(yùn)用面向切面編程的思路給工程統(tǒng)一添加某一項(xiàng)功能脉执。 典型的是給現(xiàn)有的類(lèi)添加方法和屬性,然后hook到原有實(shí)現(xiàn)添加處理戒劫。 有名的三方有FDFullscreenPopGesture 和 MLeaksFinder
關(guān)于runtime的幾個(gè)問(wèn)題
1, + class, -class 和 objc_getClass 作用一樣嗎半夷?
object_getClass(obj)返回的是obj中的isa指針;而[obj class]則分兩種情況:一是當(dāng)obj為實(shí)例對(duì)象時(shí)迅细,[obj class]中class是實(shí)例方法:- (Class)class巫橄,返回的obj對(duì)象中的isa指針;二是當(dāng)obj為類(lèi)對(duì)象(包括元類(lèi)和根類(lèi)以及根元類(lèi))時(shí)茵典,調(diào)用的是類(lèi)方法:+ (Class)class湘换,返回的結(jié)果為其本身。
http://www.reibang.com/p/ae5c32708bc6
2,isKindOfClass 和 isMemmberOfClass 區(qū)別
// JTView *subInstance = [JTView new];
// // 調(diào)用者是否為給定類(lèi)的實(shí)例或任何繼承自給定類(lèi)的實(shí)例彩倚。
// BOOL asdf = [subInstance isKindOfClass:[JTView class]];
// if (asdf) {
// NSLog(@"是子類(lèi)");
// }else {
// NSLog(@"不是子類(lèi)");
// }
// // 是否是給定類(lèi)的實(shí)例
// if ([subInstance isMemberOfClass:[JTView class]]) {
// NSLog(@"是該類(lèi)的子類(lèi)");
// }else {
// NSLog(@"不是該類(lèi)的子類(lèi)");
// }
3, 幾個(gè)面試題
http://blog.sunnyxx.com/2014/11/06/runtime-nuts/
4,+load 和 +initialize 方法
- 調(diào)用方式不同筹我,load方法是在main方法執(zhí)行之前,直接調(diào)用函數(shù)帆离,而initialize走的是消息機(jī)制
- 調(diào)用時(shí)機(jī)不同蔬蕊, load方法是在main方法執(zhí)行之前, 類(lèi)加載進(jìn)內(nèi)存的時(shí)候調(diào)用的哥谷,并且只要類(lèi)實(shí)現(xiàn)了load方法必定調(diào)用岸夯。而initialize是惰性調(diào)用, 只有第一次使用類(lèi)的時(shí)候才會(huì)調(diào)用
- load方法的調(diào)用順序是(如果都實(shí)現(xiàn)的話)超類(lèi)们妥,子類(lèi)猜扮,分類(lèi),對(duì)于多個(gè)分類(lèi)的調(diào)用順序就不一定了监婶。如果沒(méi)有實(shí)現(xiàn)就不調(diào)用旅赢,不會(huì)調(diào)用父類(lèi)的load方法。也就是說(shuō)一個(gè)類(lèi)的load方法只調(diào)用一次惑惶,但是initialize可能調(diào)用多次煮盼。
- initialize 是線程安全的, 如果子類(lèi)沒(méi)有實(shí)現(xiàn)就會(huì)調(diào)用父類(lèi)的方法