runtime
運(yùn)行時(shí)語言,實(shí)現(xiàn)Object-C的C語言庫,將OC轉(zhuǎn)換成C進(jìn)行編譯的過渡者。
作為一門動(dòng)態(tài)編程語言,Objective-C 會(huì)盡可能的將編譯和鏈接時(shí)要做的事情推遲到運(yùn)行時(shí)。只要有可能,Objective-C 總是使用動(dòng)態(tài) 的方式來解決問題。這意味著 Objective-C 語言不僅需要一個(gè)編譯環(huán)境,同時(shí)也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來執(zhí)行編譯好的代碼,這就是runtime。
運(yùn)行時(shí)系統(tǒng)(runtime)扮演的角色類似于 Objective-C 語言的操作系統(tǒng),Objective-C 基于該系統(tǒng)來工作。因此,runtime好比Objective-C的靈魂,很多東西都是在這個(gè)基礎(chǔ)上出現(xiàn)的。所以它是值的你花功夫去理解的。
實(shí)現(xiàn)
主要是用C語言實(shí)現(xiàn),部分由匯編語言。實(shí)際上正是runtime將OC中面向?qū)ο蟮念愞D(zhuǎn)換成C語言中面向過程的結(jié)構(gòu)體。無論是實(shí)例對象還是類對象,實(shí)際上對應(yīng)的都是C中的結(jié)構(gòu)體。
使用
iskindofclass和isMemberOfClass就是兩個(gè)典型的運(yùn)行時(shí)方法。
同時(shí)注意使用class_addMethod等方法時(shí)要引入#import <objc/runtime.h>
頭文件。
關(guān)于32位與64位
runtime分為早期版本和現(xiàn)行版本,32位使用早期,64位使用現(xiàn)行。32位已被淘汰。
64位與32位的區(qū)別在于,操作系統(tǒng)上64位用于3D動(dòng)畫等先進(jìn)功能32位用于日常簡單使用;處理器上64位比32位更寬處理能力更強(qiáng);軟件上基于32位和64位開發(fā)的不同;內(nèi)存容量不同。現(xiàn)在必須支持arm64,代碼中主要注意類型長度上的區(qū)別。ios推薦使用的NSInteger區(qū)別于int的地方也就是在于前者可以根據(jù)系統(tǒng)位數(shù)使用較大的長度。
與傳統(tǒng)靜態(tài)語言C語言的區(qū)別
很常見的一個(gè)消息發(fā)送語句:
[receiver message]
會(huì)被編譯器轉(zhuǎn)化成
objc_msgSend(receiver, selector)
如果有參數(shù)則為
objc_msgSend(receiver, selector, arg1, arg2, …)
receiver:消息接受者(receiver),self,某個(gè)類
selector:方法名(message)
arg:參數(shù)
傳統(tǒng)的靜態(tài)語言,編譯器會(huì)自上而下,在存在調(diào)用函數(shù)時(shí)會(huì)進(jìn)入調(diào)用函數(shù),逐句實(shí)現(xiàn)編譯。
而OC在編譯過程中并不會(huì)這樣做,只是編輯成明確方法名調(diào)用者參數(shù)的形式,該函數(shù)被如何實(shí)現(xiàn)以及是否實(shí)現(xiàn)并不關(guān)心。只有在程序運(yùn)行時(shí)才會(huì)根據(jù)方法名進(jìn)入具體函數(shù)中,這也就是會(huì)在運(yùn)行中找不到對應(yīng)函數(shù)時(shí)會(huì)造成崩潰的原因。
OC實(shí)際上是使用消息傳遞機(jī)制代替?zhèn)鹘y(tǒng)C語言的函數(shù)調(diào)用。
_CMD,SEL類型,可以獲取當(dāng)前方法名的關(guān)鍵字。
- (void)message
{
self.name = @"James";//通過self關(guān)鍵字給當(dāng)前對象的屬性賦值
SEL currentSel = _cmd;//通過_cmd關(guān)鍵字取到當(dāng)前函數(shù)對應(yīng)的SEL
NSLog(@"currentSel is :%s",(char *)currentSel);
}
打印結(jié)果
ObjcRunTime[693:403] currentSel is :message
初步理解,其實(shí)objc_msgSend 所做的事情,就是通過我們傳入的self 指針,找到class 的method_list 然后根據(jù)SEL 做比較,沒有的話,就在super class 找,如此往復(fù)。直到找到匹配的SEL,然后,call imp。當(dāng)然,runtime在其中又做了很多優(yōu)化。
OC中的消息傳遞
OC中的消息傳遞與C中函數(shù)調(diào)用的最大區(qū)別在于OC中的消息傳遞在時(shí)運(yùn)行時(shí)中進(jìn)行的,而函數(shù)調(diào)用是在編譯時(shí)就可以進(jìn)行的。
id num = @123;
//輸出123
NSLog(@"%@", num);
//程序崩潰,報(bào)錯(cuò)[__NSCFNumber appendString:]: unrecognized selector sent to instance 0x7b27
[num appendString:@"Hello World"];
上述代碼沒有任何問題,id可以使任何類型包括NSString,但是在運(yùn)行過程中在NSNumber中找不到appendString這個(gè)方法,所以會(huì)報(bào)錯(cuò)。所以消息傳遞的強(qiáng)大之處在于可以在運(yùn)行時(shí)添加新方法,缺點(diǎn)在于無法再編譯時(shí)發(fā)現(xiàn)錯(cuò)誤。
OC中的消息傳遞轉(zhuǎn)化為C的函數(shù)調(diào)用
簡單的創(chuàng)建Person對象
//為了方便查看轉(zhuǎn)寫后的C語言代碼,將alloc和init分兩步完成
Person *p = [Person alloc];
p = [p init];
p.name = @"Jiaming Chen";
[p showMyself];
使用clang -rewrite-objc main.m
可以轉(zhuǎn)化成C
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc"));
p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_f5b408_mi_1);
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("showMyself"));
第一行代碼簡要表示為
Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
第一步,獲取Person類,第二步注冊alloc方法,然后通過objc_msgSend將alloc消息發(fā)送給消息接受者Person。
第二行代碼簡要表示為
p = objc_msgSend(p, sel_registerName("init"));
由此可見,對于OC而言都是以消息傳遞實(shí)現(xiàn),而正是runtime通過objc_msgSend將一個(gè)面向?qū)ο蟮南鬟f轉(zhuǎn)換成了一個(gè)面向過程的的函數(shù)調(diào)用。
消息轉(zhuǎn)發(fā)
我們知道消息傳遞實(shí)際上做的事就是利用objc_msgSend,將消息傳遞給接受者,在接受者的結(jié)構(gòu)體中的objc_method_list中查找方法。如果找到就可以直接調(diào)用,如果找不到就會(huì)通過super_class指針去它的父類中查找,直至基類NSObject。如果還是沒有找到就會(huì)調(diào)用NSObject的doesNotRecognizeSelector報(bào)出unrecognized的錯(cuò)誤。也就是我們常出現(xiàn)的unrecognized selector sent to instance
錯(cuò)誤(對象提前被釋放或者為空導(dǎo)致無法識(shí)別其方法)。
但是在此之前,還有三次機(jī)會(huì)來處理這個(gè)消息來避免出現(xiàn)崩潰,這就是所謂的消息轉(zhuǎn)發(fā)。詳細(xì)見后文。
runtime源碼學(xué)習(xí)
- selector
selector可以叫做選擇器,其實(shí)指的就是對象的方法,也可以理解為C語言里面的函數(shù)指針,面向?qū)ο罄锩娴膶?yīng)概念。
@selector(xxxx)的作用是找到名字為xxxx的方法。一般用于[a performSelector:@selector(b)];就是說去調(diào)用a對象的b方法,和[a b];的意思一樣,但是這樣更加動(dòng)態(tài)一些。@selector(xxxx)返回的類型是SEL,看方法說明的時(shí)候如果參數(shù)類型是SEL,那么就是要接受@selector(xxxx)返回的值。
在Objc中 SEL的定義是:
typedef struct objc_selector *SEL;
我們注意到對于SEL的定義,實(shí)際上就是使用typedef將結(jié)構(gòu)體objc_selector重新命名為*SEL。而SEL則是指向這個(gè)結(jié)構(gòu)體的指針,所以SEL的對象不需要再加*。
在Mac OS X中SEL其實(shí)被映射為一個(gè)C字符串,可以看作是方法的名字(編號),它并不一個(gè)指向具體方法實(shí)現(xiàn)(IMP類型才是)。對于所有的類,只要方法名是相同的,產(chǎn)生的selector都是一樣的。
簡而言之,你可以理解 @selector()就是取類方法的編號,他的行為基本可以等同C語言的中函數(shù)指針,只不過C語言中,可以把函數(shù)名直接賦給一個(gè)函數(shù)指針,而Object-C的類不能直接應(yīng)用函數(shù)指針,這樣只能做一個(gè)@selector語法來取。
它的結(jié)果是一個(gè)SEL類型。這個(gè)類型本質(zhì)是類方法的名字(編號)。
注意1. @selector是查找當(dāng)前類的方法,而[object @selector(方法名:方法參數(shù)..) ] ;是取object對應(yīng)類的相應(yīng)方法.
注意2.查找類方法時(shí),除了方法名,方法參數(shù)也查詢條件之一.
注意3. 可以用字符串來找方法 SEL 變量名 = NSSelectorFromString(方法名字的字符串);
注意4. 可以運(yùn)行中用SEL變量反向查出方法名字字符串
NSString *變量名 = NSStringFromSelector(SEL參數(shù));
runtime 在實(shí)現(xiàn)selector時(shí),實(shí)現(xiàn)了一個(gè)很大的Set,簡單的說就是一個(gè)經(jīng)過了優(yōu)化過的hash表。而Set的特點(diǎn)就是唯一,也就是SEL是唯一的。那么對于字符串的比較僅僅需要比較他們的地址就可以了。 所以O(shè)C中不允許使用方法名一樣參數(shù)不一樣的函數(shù),編譯器會(huì)根據(jù)每個(gè)方法的方法名生成唯一的SEL。所以,在速度上是無與倫比的。
selector主要用于兩個(gè)對象之間進(jìn)行松耦合的通訊.這種方法很多開發(fā)環(huán)境用到。比如GTK,Delphi.基本上整個(gè)Cocoa庫之間對象,控制之間通訊都是在這個(gè)基礎(chǔ)構(gòu)建的。
用戶行為統(tǒng)計(jì)中的經(jīng)典運(yùn)用:
+ (void)setUpAnalytics
{
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//讀取配置文件,獲取需要統(tǒng)計(jì)的事件列表
for (NSString *classNameString in analyticsData.allKeys) {
//使用運(yùn)行時(shí)創(chuàng)建類對象
const char *className = [classNameString UTF8String];
//從一個(gè)字串返回一個(gè)類
Class newClass = objc_getClass(className);
NSArray *pageEventList = [[analyticsData objectForKey:classNameString] objectForKey:Event];
for (NSDictionary *eventDict in pageEventList) {
//事件方法名稱
NSString *eventMethodName = eventDict[MethodName];
SEL seletor = NSSelectorFromString(eventMethodName);
NSString *eventId = eventDict[EventId];
[weakSelf trackEventWithClass:newClass selector:seletor event:eventId];
[weakSelf uAnalyticstrackEventWithEventdata:eventDict];
}
}
});
}
- id
id是通用類型指針,能夠表示任何對象,換句話說,id 類型的變量可以存放任何數(shù)據(jù)類型的對象。在內(nèi)部處理上,這種類型被定義為指向?qū)ο蟮闹羔?/strong>,實(shí)際上是一個(gè)指向這種對象的實(shí)例變量的指針。
查看到id數(shù)據(jù)結(jié)構(gòu)如下:
// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
// A pointer to an instance of a class.
typedef struct objc_object *id;
實(shí)際上*id就是結(jié)構(gòu)體objc_object的別名。而id則是一個(gè)指向objc_object結(jié)構(gòu)體指針,它包含一個(gè)Class isa成員,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類。需要注意的是id 是一個(gè)指針,所以在使用id的時(shí)候不需要加星號。
NSString *str = [[NSString alloc] init];
這里我們定義的對象str,實(shí)際上同樣是一個(gè)objc_object結(jié)構(gòu)體指針。
- Class
isa指針的數(shù)據(jù)類型是Class,Class表示對象所屬的類。
// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
可以查看到Class其實(shí)就是一個(gè)objc_class結(jié)構(gòu)體指針。class類型和id類型一樣,本身繼承于指針類型,用于儲(chǔ)存類對象的指針,故在聲明class類型變量時(shí)和id一樣不需要*號。
NSString *str = [[NSString alloc] init];
Class c = [str Class];
這里我們定義的c,實(shí)際上同樣是一個(gè)objc_class結(jié)構(gòu)體指針。
查看到objc_class結(jié)構(gòu)體定義如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
OC的類其實(shí)也是一個(gè)對象,即類對象,意思就是你可以向一個(gè)類發(fā)送消息。為了可以調(diào)用類方法,這個(gè)類的isa指針必須指向一個(gè)包含這些類方法的類結(jié)構(gòu)體,也就是元類(meta-class)的概念。meta-class之所以重要,是因?yàn)樗4嬷鴦?chuàng)建類對象以及類方法所需的所有信息。每個(gè)類都會(huì)有一個(gè)單獨(dú)的meta-class,因?yàn)槊總€(gè)類的類方法基本不可能完全相同。
任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己所屬的類。所有的meta-class使用基類的meta-class作為它們的父類,而基類的meta-class也是屬于它自己,也就是說基類的meta-class的isa指針指向它自己。
上圖實(shí)線是super_class指針,虛線是isa指針。有幾個(gè)關(guān)鍵點(diǎn)需要解釋以下:
1.Root class (class)其實(shí)就是NSObject,NSObject是沒有超類的,所以Root class(class)的superclass指向nil。
2.每個(gè)Class都有一個(gè)isa指針指向唯一的Meta class
3.Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一個(gè)回路。
4.每個(gè)Meta class的isa指針都指向Root class (meta)。
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
Class c1 = [p class];
Class c2 = [Person class];
//輸出 1
NSLog(@"%d", c1 == c2);
}
return 0;
}
由此可見,類對象是單例。對于Person這個(gè)類對象來說,只有一個(gè),所有類對象均是如此。
介紹兩個(gè)函數(shù):
OBJC_EXPORT BOOL class_isMetaClass(Class cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
OBJC_EXPORT Class object_getClass(id obj)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
class_isMetaClass用于判斷Class對象是否為元類,object_getClass用于獲取對象的isa指針指向的對象。
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
//輸出1
NSLog(@"%d", [p class] == object_getClass(p));
//輸出0
NSLog(@"%d", class_isMetaClass(object_getClass(p)));
//輸出1
NSLog(@"%d", class_isMetaClass(object_getClass([Person class])));
//輸出0
NSLog(@"%d", object_getClass(p) == object_getClass([Person class]));
}
return 0;
}
由上看出,實(shí)例的isa指針指向類,類的isa指針指向元類。
super_class表示實(shí)例對象對應(yīng)的父類;
name表示類名,我們可以在運(yùn)行期,通過這個(gè)名稱查找到該類(通過:id objc_getClass(const char *aClassName))或該類的 metaclass(id objc_getMetaClass(const char *aClassName));
ivars表示多個(gè)成員變量,它指向objc_ivar_list結(jié)構(gòu)體。在runtime.h可以看到它的定義:
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
}
objc_ivar_list其實(shí)就是一個(gè)鏈表,存儲(chǔ)多個(gè)objc_ivar,而objc_ivar結(jié)構(gòu)體存儲(chǔ)類的單個(gè)成員變量信息。
(* struct objc_ivar ivar_list[1] 這個(gè)稱為結(jié)構(gòu)體數(shù)組,顧名思義,結(jié)構(gòu)數(shù)組是指能夠存放多個(gè)結(jié)構(gòu)體類型(objc_ivar)的一種數(shù)組(ivar_list)形式。)
methodLists表示方法列表,它指向objc_method_list結(jié)構(gòu)體的二級指針,可以動(dòng)態(tài)修改methodLists的值來添加成員方法,也是Category實(shí)現(xiàn)原理,同樣也解釋Category不能添加屬性的原因。
Category只能在運(yùn)行時(shí)中添加屬性。代碼如下:
///例如可能是這樣的使用
static const void *propertyKey = &propertyKey;
/// 將value通過運(yùn)行時(shí)綁定到self
objc_setAssociatedObject(self, propertyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
/// 將value在運(yùn)行時(shí)中通過propertyKey取出綁定的值
id value = objc_getAssociatedObject(self, propertyKey);
在runtime.h可以看到它的定義
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
同理,objc_method_list也是一個(gè)鏈表,存儲(chǔ)多個(gè)objc_method,而objc_method結(jié)構(gòu)體存儲(chǔ)類的某個(gè)方法的信息。
cache用來緩存經(jīng)常訪問的方法,它指向objc_cache結(jié)構(gòu)體,后面會(huì)重點(diǎn)講到。
protocols表示類遵循哪些協(xié)議。
- Method
Method表示類中的某個(gè)方法,在runtime.h文件中找到它的定義:
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
其實(shí)Method就是一個(gè)指向objc_method結(jié)構(gòu)體指針,它存儲(chǔ)了方法名(method_name)、方法類型(method_types)和方法實(shí)現(xiàn)(method_imp)等信息。而method_imp的數(shù)據(jù)類型是IMP,它是一個(gè)函數(shù)指針,后面會(huì)重點(diǎn)提及。
- Ivar
Ivar表示類中的實(shí)例變量,在runtime.h文件中找到它的定義:
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
Ivar其實(shí)就是一個(gè)指向objc_ivar結(jié)構(gòu)體指針,它包含了變量名(ivar_name)、變量類型(ivar_type)等信息。
- IMP
在上面講Method時(shí)就說過,IMP本質(zhì)上就是一個(gè)函數(shù)指針,指向方法的實(shí)現(xiàn),在objc.h找到它的定義:
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
當(dāng)你向某個(gè)對象發(fā)送一條信息,可以由這個(gè)函數(shù)指針來指定方法的實(shí)現(xiàn),它最終就會(huì)執(zhí)行那段代碼,這樣可以繞開消息傳遞階段而去執(zhí)行另一個(gè)方法實(shí)現(xiàn)。
- cache
顧名思義,Cache主要用來緩存,那它緩存什么呢?我們先在runtime.h文件看看它的定義
typedef struct objc_cache *Cache OBJC2_UNAVAILABLE;
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
Cache其實(shí)就是一個(gè)存儲(chǔ)Method的鏈表,主要是為了優(yōu)化方法調(diào)用的性能。當(dāng)對象receiver調(diào)用方法message時(shí),首先根據(jù)對象receiver的isa指針查找到它對應(yīng)的類,然后在類的methodLists中搜索方法,如果沒有找到,就使用super_class指針到父類中的methodLists查找,一旦找到就調(diào)用方法。如果沒有找到,有可能消息轉(zhuǎn)發(fā),也可能忽略它。但這樣查找方式效率太低,因?yàn)橥粋€(gè)類大概只有20%的方法經(jīng)常被調(diào)用,占總調(diào)用次數(shù)的80%。所以使用Cache來緩存經(jīng)常調(diào)用的方法,當(dāng)調(diào)用方法時(shí),優(yōu)先在Cache查找,如果沒有找到,再到methodLists查找。
消息發(fā)送
- objc_msgSend函數(shù)
在前面已經(jīng)提過,當(dāng)某個(gè)對象使用語法[receiver message]來調(diào)用某個(gè)方法時(shí),其實(shí)[receiver message]被編譯器轉(zhuǎn)化為:
id objc_msgSend ( id self, SEL op, ... );
現(xiàn)在讓我們看一下objc_msgSend它具體是如何發(fā)送消息:
1.首先根據(jù)receiver對象的isa指針獲取它對應(yīng)的class;
2.優(yōu)先在class的cache查找message方法,如果找不到,再到methodLists查找;
3.如果沒有在class找到,再到super_class查找;
4.一旦找到message這個(gè)方法,就執(zhí)行它實(shí)現(xiàn)的IMP。
- self與super
為了讓大家更好地理解self和super,借用sunnyxx博客的iOS程序員6級考試一道題目:下面的代碼分別輸出什么?
@implementation Son : Father
-(id)init
{
self = [super init];
if (self)
{
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
self表示當(dāng)前這個(gè)類的對象,而super是一個(gè)編譯器標(biāo)示符,和self指向同一個(gè)消息接受者(son)。在本例中,無論是[self class]還是[super class],接受消息者都是Son對象,但super與self不同的是,self調(diào)用class方法時(shí),是在子類Son中查找方法,而super調(diào)用class方法時(shí),是在父類Father中查找方法。
當(dāng)調(diào)用[self class]方法時(shí),會(huì)轉(zhuǎn)化為objc_msgSend函數(shù),這個(gè)函數(shù)定義如下:
id objc_msgSend(id self, SEL op, ...)
這時(shí)會(huì)從當(dāng)前Son類的方法列表中查找,如果沒有,就到Father類查找,還是沒有,最后在NSObject類查找到。我們可以從NSObject.mm文件中看到- (Class)class的實(shí)現(xiàn):
-(Class)class {
return object_getClass(self);
}
所以NSLog(@"%@", NSStringFromClass([self class]));會(huì)輸出Son。
當(dāng)調(diào)用[super class]方法時(shí),會(huì)轉(zhuǎn)化為objc_msgSendSuper,這個(gè)函數(shù)定義如下:
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
objc_msgSendSuper函數(shù)第一個(gè)參數(shù)super的數(shù)據(jù)類型是一個(gè)指向objc_super的結(jié)構(gòu)體,從message.h文件中查看它的定義:
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
#endif
結(jié)構(gòu)體包含兩個(gè)成員,第一個(gè)是receiver,表示某個(gè)類的實(shí)例。第二個(gè)是super_class表示當(dāng)前類的父類。
這時(shí)首先會(huì)構(gòu)造出objc_super結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體第一個(gè)成員是self,第二個(gè)成員是(id)class_getSuperclass(objc_getClass("Son")),實(shí)際上該函數(shù)會(huì)輸出Father。然后在Father類查找class方法,查找不到,最后在NSObject查到。此時(shí),內(nèi)部使用objc_msgSend(objc_super->receiver, @selector(class))去調(diào)用,與[self class]調(diào)用相同,所以結(jié)果還是Son。
Method Resolution
Fast Forwarding
Normal Forwarding
我的理解:objc_super結(jié)構(gòu)體中receiver是son,superclass是father,而objc_msgSend(objc_super->receiver, @selector(class))內(nèi)部是使用objc_super中的receiver作為消息發(fā)送者,也就是son調(diào)用NSObject中的class方法,所以結(jié)果是son。
方法解析與消息轉(zhuǎn)發(fā)
[receiver message]調(diào)用方法時(shí),如果在message方法在receiver對象的類繼承體系中沒有找到方法,那怎么辦?一般情況下,程序在運(yùn)行時(shí)就會(huì)Crash掉,拋出unrecognized selector sent to…類似這樣的異常信息。但在拋出異常之前,還有三次機(jī)會(huì)按以下順序讓你拯救程序。
1.Method Resolution 即所屬類動(dòng)態(tài)方法解析
2.Fast Forwarding 即備援接受者
3.Normal Forwarding
- Method Resolution首先Objective-C在運(yùn)行時(shí)調(diào)用+ resolveInstanceMethod:或+ resolveClassMethod:方法,讓你添加方法的實(shí)現(xiàn)。如果你添加方法并返回YES,那系統(tǒng)在運(yùn)行時(shí)就會(huì)重新啟動(dòng)一次消息發(fā)送的過程。
舉一個(gè)簡單例子,定義一個(gè)類Message,它主要定義一個(gè)方法sendMessage,下面就是它的設(shè)計(jì)與實(shí)現(xiàn):
@interface Message : NSObject
-(void)sendMessage:(NSString *)word;
@end
@implementation Message
-(void)sendMessage:(NSString *)word
{
NSLog(@"normal way : send message = %@", word);
}
@end
如果我在viewDidLoad方法中創(chuàng)建Message對象并調(diào)用sendMessage方法:
-(void)viewDidLoad {
[super viewDidLoad];
Message *message = [Message new];
[message sendMessage:@"Sam Lau"];
}
控制臺(tái)會(huì)打印以下信息:
normal way : send message = Sam Lau
但現(xiàn)在我將原來sendMessage方法實(shí)現(xiàn)給注釋掉,覆蓋resolveInstanceMethod方法:
#pragma mark - Method Resolution
/// override resolveInstanceMethod or resolveClassMethod for changing sendMessage method implementation
+(BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(sendMessage:)) {
class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *word) {
NSLog(@"method resolution way : send message = %@", word);
}), "v@*");
}
return YES;
}
控制臺(tái)就會(huì)打印以下信息:
method resolution way : send message = Sam Lau
該方法主要原型為
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
方法名為給class添加方法,返回一個(gè)Bool類型返回值。第一個(gè)參數(shù)為需要添加方法的類,第二個(gè)參數(shù)為實(shí)例方法的名字也就是+(BOOL)resolveInstanceMethod:(SEL)sel
方法中的參數(shù)sel,第三個(gè)參數(shù)為IMP類型的變量也就是函數(shù)的實(shí)現(xiàn)。需要傳一個(gè)C函數(shù),該函數(shù)至少要有兩個(gè)變量,一個(gè)是id self,一個(gè)是SEL _cmd,也可以傳字符串。
另一種寫法。
void dynamicAdditionMethodIMP(id self, SEL _cmd) {
//實(shí)現(xiàn)
NSLog(@"dynamicAdditionMethodIMP");
}
class_addMethod([self class], name, (IMP)dynamicAdditionMethodIMP, "v@:");
第四個(gè)參數(shù)傳入一個(gè)字符串"v@*,它表示方法的參數(shù)和返回值,詳情請參考Type Encodings。
如果resolveInstanceMethod方法返回NO,運(yùn)行時(shí)就跳轉(zhuǎn)到下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)。
- Fast Forwarding
當(dāng)對象所屬類不能動(dòng)態(tài)添加方法后,runtime就會(huì)詢問當(dāng)前的接受者是否有其他對象可以處理這個(gè)未知的selector。方法就是:
- (id)forwardingTargetForSelector:(SEL)aSelector;
如果目標(biāo)對象實(shí)現(xiàn)- forwardingTargetForSelector:方法,系統(tǒng)就會(huì)在運(yùn)行時(shí)調(diào)用這個(gè)方法,只要這個(gè)方法返回的不是nil或self,也會(huì)重啟消息發(fā)送的過程,把這消息轉(zhuǎn)發(fā)給其他對象來處理。否則,就會(huì)繼續(xù)Normal Fowarding。
繼續(xù)上面Message類的例子,將sendMessage和resolveInstanceMethod方法注釋掉,然后添加forwardingTargetForSelector方法的實(shí)現(xiàn):
#pragma mark - Fast Forwarding
-(id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(sendMessage:)) {
return [MessageForwarding new];
}
return nil;
}
此時(shí)還缺一個(gè)轉(zhuǎn)發(fā)消息的類MessageForwarding,這個(gè)類的設(shè)計(jì)與實(shí)現(xiàn)如下:
@interface MessageForwarding : NSObject
-(void)sendMessage:(NSString *)word;
@end
@implementation MessageForwarding
-(void)sendMessage:(NSString *)word
{
NSLog(@"fast forwarding way : send message = %@", word);
}
@end
此時(shí),控制臺(tái)會(huì)打印以下信息:
fast forwarding way : send message = Sam Lau
這里叫Fast,是因?yàn)檫@一步不會(huì)創(chuàng)建NSInvocation對象,但Normal Forwarding會(huì)創(chuàng)建它,所以相對于更快點(diǎn)。
- Normal Forwarding
如果沒有使用Fast Forwarding來消息轉(zhuǎn)發(fā),最后只有使用Normal Forwarding來進(jìn)行消息轉(zhuǎn)發(fā)。它首先調(diào)用methodSignatureForSelector:方法來獲取函數(shù)的參數(shù)和返回值,如果返回為nil,程序會(huì)Crash掉,并拋出unrecognized selector sent to instance異常信息。如果返回一個(gè)函數(shù)簽名,系統(tǒng)就會(huì)創(chuàng)建一個(gè)NSInvocation對象并調(diào)用-forwardInvocation:方法。
繼續(xù)前面的例子,將forwardingTargetForSelector方法注釋掉,添加methodSignatureForSelector和forwardInvocation方法的實(shí)現(xiàn):
#pragma mark - Normal Forwarding
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if (!methodSignature) {
methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
}
return methodSignature;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
MessageForwarding *messageForwarding = [MessageForwarding new];
if ([messageForwarding respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:messageForwarding];
}
}
三種方法的選擇
Runtime提供三種方式來將原來的方法實(shí)現(xiàn)代替掉,那該怎樣選擇它們呢?
- Method Resolution
由于Method Resolution不能像消息轉(zhuǎn)發(fā)那樣可以交給其他對象來處理,所以只適用于在原來的類中代替掉。 - Fast Forwarding
它可以將消息處理轉(zhuǎn)發(fā)給其他對象,使用范圍更廣,不只是限于原來的對象。 - Normal Forwarding
它跟Fast Forwarding一樣可以消息轉(zhuǎn)發(fā),但它能通過NSInvocation對象獲取更多消息發(fā)送的信息,例如:target、selector、arguments和返回值等信息。
Associated Objects
如果我們想對系統(tǒng)的類添加方法時(shí),可以使用擴(kuò)展,但是添加屬性時(shí),只能使用繼承。如果不想使用繼承,則可以用runtime來關(guān)聯(lián)對象,這與我們自定義類的添加屬性不同。本質(zhì)上是使用類別進(jìn)行擴(kuò)展,通過添加get方法和set方法從而在使用時(shí)可以使用點(diǎn)方法使用。與普通使用方法一致。
同時(shí)當(dāng)使用Category對某個(gè)類進(jìn)行擴(kuò)展時(shí),有時(shí)需要存儲(chǔ)屬性,Category是不支持的,這時(shí)需要使用Associated Objects來給已存在的類Category添加自定義的屬性。Associated Objects提供三個(gè)C語言API來向?qū)ο筇砑印@取和刪除關(guān)聯(lián)值:
- void objc_setAssociatedObject (id object, const void *key, id value, objc_AssociationPolicy policy )
- id objc_getAssociatedObject (id object, const void *key )
- void objc_removeAssociatedObjects (id object )
其中objc_AssociationPolicy是個(gè)枚舉類型,它可以指定Objc內(nèi)存管理的引用計(jì)數(shù)機(jī)制。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
下面有個(gè)關(guān)于NSObject+AssociatedObject Category添加屬性associatedObject的示例代碼:
NSObject+AssociatedObject.h
@interface NSObject (AssociatedObject)
@property (strong, nonatomic) id associatedObject;
@end
NSObject+AssociatedObject.m
@implementation NSObject (AssociatedObject)
- (void)setAssociatedObject:(id)associatedObject
{
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject
{
return objc_getAssociatedObject(self, _cmd);
}
@end
Associated Objects的key要求是唯一并且是常量,而SEL是滿足這個(gè)要求的,所以上面的采用隱藏參數(shù)_cmd作為key。
一個(gè)給scrollview添加refreshView的實(shí)際用例:
@interface UIScrollView (Refresh)
@property (nonatomic) RefreshView * refreshView;
@end
#import <objc/runtime.h>
static char kRefreshView;
@implementation UIScrollView (Refresh)
@dynamic refreshView;
- (void)setRefreshView:(RefreshView *)aView {
objc_setAssociatedObject(self, &kRefreshView, aView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (RefreshView *)refreshView {
return objc_getAssociatedObject(self, &kRefreshView);
}
@end
GetClass
得到一個(gè)實(shí)例的類。代碼如下:
#import "ViewController.h"
#import <objc/runtime.h>
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person * p1 = [[Person alloc] init];
Class c1 = object_getClass(p1);
NSLog(@"%@", c1);
Person * p2 = [[[c1 class] alloc] init];
NSLog(@"%@", p2.name);
}
isKindOfClass和isMemberOfClass
先看看isKindOfClass和isMemberOfClass在Object.mm中的實(shí)現(xiàn):
- (BOOL)isKindOf:aClass
{
Class cls;
for (cls = isa; cls; cls = cls->superclass)
if (cls == (Class)aClass)
return YES;
return NO;
}
- (BOOL)isMemberOf:aClass
{
return isa == (Class)aClass;
}
可以看到isKindOfClass是取自身指針isa與參數(shù)指針做對比,如果相等返回YES,如果不等則取其superClass指針,直到superClass為空為止,循環(huán)結(jié)束返回空。
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}
//輸出
2014-11-05 14:45:08.474 Test[9412:721945] 1 0 0 0
res1中NSObject的isa指針指向NSOject的meta-class,繼續(xù)循環(huán),meta-class的superClass是NSObject所以為ture;
res3中Sark的isa指針指向sark的meta-class,接下來指向NSObject的meta-class,再接下來指向NSObject;
res2中NSObject的isa指針指向NSOject的meta-class,與NSObject不一致;
res4中Sark的isa指針指向sark的meta-class,與Sark不一致。
注意上面是類與類相比。下面是類的實(shí)例與類相比:
Person *person = [[Person alloc] init];
Teacher *teacher = [[Teacher alloc] init];
//YES
if ([teacher isKindOfClass:[Teacher class]]) {
NSLog(@"teacher 是 Teacher類或Teacher的子類");
}
//YES
if ([teacher isKindOfClass:[Person class]]) {
NSLog(@"teacher 是 Person類或Person的子類");
}
//YES
if ([teacher isKindOfClass:[NSObject class]]) {
NSLog(@"teacher 是 NSObject類或NSObject的子類");
}
// YES
if ( [teacher respondsToSelector: @selector( setName: )] == YES ) {
NSLog(@"teacher responds to setSize: method" );
}
// NO
if ( [teacher respondsToSelector: @selector( abcde )] == YES ) {
NSLog(@"teacher responds to nonExistant method" );
}
// YES
if ( [Teacher respondsToSelector: @selector( alloc )] == YES ) {
NSLog(@"teacher class responds to alloc method\n" );
}
Method Swizzling
Method Swizzling就是在運(yùn)行時(shí)將一個(gè)方法的實(shí)現(xiàn)代替為另一個(gè)方法的實(shí)現(xiàn)。由于Foundation等框架都是閉源的,我們沒有辦法直接修改代碼,通常情況下只能通過繼承,類別,關(guān)聯(lián)屬性等手段添加屬性和實(shí)例方法,較為繁瑣。而Method Swizzling可以通過將一個(gè)方法的實(shí)現(xiàn)代替另一個(gè)方法的實(shí)現(xiàn),從而達(dá)到修改閉源代碼的目的。
例如一個(gè)想要統(tǒng)計(jì)每個(gè)頁面出現(xiàn)的次數(shù),如果在所有頁面的viewWillAppear中都添加統(tǒng)計(jì)代碼,則太過繁瑣。那我們使用一個(gè)類別,并自定義一個(gè)方法替代系統(tǒng)的viewWillAppear,在其中做統(tǒng)計(jì),并在統(tǒng)計(jì)結(jié)束后調(diào)用系統(tǒng)的viewWillAppear即可。
@interface UIViewController (MyUIViewController)
@end
@implementation UIViewController(MyUIViewController)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(viewWillAppear:);
Method originalMethod = class_getInstanceMethod([self class], originalSelector);
SEL exchangeSelector = @selector(myViewWillAppear:);
Method exchangeMethod = class_getInstanceMethod([self class], exchangeSelector);
method_exchangeImplementations(originalMethod, exchangeMethod);
});
}
- (void)myViewWillAppear:(BOOL)animated {
//這里實(shí)際上是調(diào)用系統(tǒng)的viewWillAppear,不會(huì)遞歸調(diào)用。
[self myViewWillAppear:animated];
NSLog(@"MyViewWillAppear %@", [self class]);
}
使用load實(shí)現(xiàn)預(yù)加載。使用GCD的dispatch_once_t保證只會(huì)交換一次不會(huì)重復(fù)交換。使用method_exchangeImplementations來實(shí)現(xiàn)交換。
將上述代碼放入工程中就可以打印所有viewcontroller的加載情況。
Aspect-Oriented Programming(AOP)
類似記錄日志、身份驗(yàn)證、緩存等事務(wù)非常瑣碎,與業(yè)務(wù)邏輯無關(guān),很多地方都有,又很難抽象出一個(gè)模塊,這種程序設(shè)計(jì)問題,業(yè)界給它們起了一個(gè)名字叫橫向關(guān)注點(diǎn)(Cross-cutting concern),AOP作用就是分離橫向關(guān)注點(diǎn)(Cross-cutting concern)來提高模塊復(fù)用性,它可以在既有的代碼添加一些額外的行為(記錄日志、身份驗(yàn)證、緩存)而無需修改代碼。
runtime如何實(shí)現(xiàn)weak置為nil
runtime對注冊的類會(huì)進(jìn)行布局,對于weak修飾的對象會(huì)放入一個(gè)hash表中,用weak指向的對象內(nèi)存地址作為key。當(dāng)此對象的引用計(jì)數(shù)為0的時(shí)候會(huì)dealloc,假如weak指向的對象內(nèi)存地址是a,那么就會(huì)以a為鍵在這個(gè)weak表中搜索,找到所有以a為鍵的weak對象,從而設(shè)置為nil。
NSString *name = [[NSString alloc] initWithString: @"Jiaming Chen"];
__weak NSString *weakStr = name;
當(dāng)為weakStr這一weak類型的對象賦值時(shí),編譯器會(huì)根據(jù)name的地址為key去查找weak哈希表,該表項(xiàng)的值為一個(gè)數(shù)組,將weakStr對象的地址加入到數(shù)組中,當(dāng)name變量超出變量作用域或引用計(jì)數(shù)為0時(shí),會(huì)執(zhí)行dealloc函數(shù),在執(zhí)行該函數(shù)時(shí),編譯器會(huì)以name變量的地址去查找weak哈希表的值,并將數(shù)組里所有 weak對象全部賦值為nil。
總結(jié)
雖然在平時(shí)項(xiàng)目不是經(jīng)常用到Objective-C的Runtime特性,但當(dāng)你閱讀一些iOS開源項(xiàng)目時(shí),你就會(huì)發(fā)現(xiàn)很多時(shí)候都會(huì)用到。所以深入理解Objective-C的Runtime數(shù)據(jù)結(jié)構(gòu)、消息轉(zhuǎn)發(fā)機(jī)制有助于你更容易地閱讀和學(xué)習(xí)開源項(xiàng)目。