runtime 和 runloop 作為一個(gè)程序員進(jìn)階是必須的,也是非常重要的, 在面試過程中是經(jīng)常會(huì)被問到的, 所以大家有必要進(jìn)行研究,有能力的童鞋可以和下面作者一樣, 親歷實(shí)踐一下。
在簡(jiǎn)書里發(fā)現(xiàn)了兩篇非常好的文章介紹 runtime和runloop的,在這里合二為一了, 把原版作者的東西拿了過來, 為了尊重作者,在這里注明一下 @sam_lau 是runtime的作者, @tripleCC是runloop的作者
RunTime
Objective-C是基于C語言加入了面向?qū)ο筇匦?/b>和消息轉(zhuǎn)發(fā)機(jī)制的動(dòng)態(tài)語言,這意味著它不僅需要一個(gè)編譯器,還需要Runtime系統(tǒng)來動(dòng)態(tài)創(chuàng)建類和對(duì)象,進(jìn)行消息發(fā)送和轉(zhuǎn)發(fā)。下面通過分析Apple開源的Runtime代碼(我使用的版本是objc4-646.tar)來深入理解Objective-C的Runtime機(jī)制。
Runtime數(shù)據(jù)結(jié)構(gòu)
在Objective-C中,使用[receiver message]語法并不會(huì)馬上執(zhí)行receiver對(duì)象的message方法的代碼,而是向receiver發(fā)送一條message消息,這條消息可能由receiver來處理,也可能由轉(zhuǎn)發(fā)給其他對(duì)象來處理,也有可能假裝沒有接收到這條消息而沒有處理。其實(shí)[receiver message]被編譯器轉(zhuǎn)化為:
idobjc_msgSend (idself, SEL op, ... );
下面從兩個(gè)數(shù)據(jù)結(jié)構(gòu)id和SEL來逐步分析和理解Runtime有哪些重要的數(shù)據(jù)結(jié)構(gòu)。
SEL
SEL是函數(shù)objc_msgSend第二個(gè)參數(shù)的數(shù)據(jù)類型,表示方法選擇器,按下面路徑打開objc.h文件

SEL Data Structure
查看到SEL數(shù)據(jù)結(jié)構(gòu)如下:
typedef struct objc_selector *SEL;
其實(shí)它就是映射到方法的C字符串,你可以通過Objc編譯器命令@selector()或者Runtime系統(tǒng)的sel_registerName函數(shù)來獲取一個(gè)SEL類型的方法選擇器。
如果你知道selector對(duì)應(yīng)的方法名是什么,可以通過NSString* NSStringFromSelector(SEL aSelector)方法將SEL轉(zhuǎn)化為字符串,再用NSLog打印。
id
接下來看objc_msgSend第一個(gè)參數(shù)的數(shù)據(jù)類型id,id是通用類型指針,能夠表示任何對(duì)象。按下面路徑打開objc.h文件

id Data Structure.png
查看到id數(shù)據(jù)結(jié)構(gòu)如下:
/// Represents an instance of a class.structobjc_object { ?
Class isa ?OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.typedefstructobjc_object *id;
id其實(shí)就是一個(gè)指向objc_object結(jié)構(gòu)體指針,它包含一個(gè)Class isa成員,根據(jù)isa指針就可以順藤摸瓜找到對(duì)象所屬的類。
注意:根據(jù)Apple的官方文檔Key-Value Observing Implementation Details提及,key-value observing是使用isa-swizzling的技術(shù)實(shí)現(xiàn)的,isa指針在運(yùn)行時(shí)被修改,指向一個(gè)中間類而不是真正的類。所以,你不應(yīng)該使用isa指針來確定類的關(guān)系,而是使用class方法來確定實(shí)例對(duì)象的類。
Class
isa指針的數(shù)據(jù)類型是Class,Class表示對(duì)象所屬的類,按下面路徑打開objc.h文件

Class Data Structure
/// An opaque type that represents an Objective-C class.typedefstructobjc_class *Class;
可以查看到Class其實(shí)就是一個(gè)objc_class結(jié)構(gòu)體指針,但這個(gè)頭文件找不到它的定義,需要在runtime.h才能找到objc_class結(jié)構(gòu)體的定義。
按下面路徑打開runtime.h文件

objc_class Data Structure
查看到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 *` */
注意:OBJC2_UNAVAILABLE是一個(gè)Apple對(duì)Objc系統(tǒng)運(yùn)行版本進(jìn)行約束的宏定義,主要為了兼容非Objective-C 2.0的遺留版本,但我們?nèi)阅軓闹蝎@取一些有用信息。
讓我們分析一些重要的成員變量表示什么意思和對(duì)應(yīng)使用哪些數(shù)據(jù)結(jié)構(gòu)。
isa表示一個(gè)Class對(duì)象的Class,也就是Meta Class。在面向?qū)ο笤O(shè)計(jì)中,一切都是對(duì)象,Class在設(shè)計(jì)中本身也是一個(gè)對(duì)象。我們會(huì)在objc-runtime-new.h文件找到證據(jù),發(fā)現(xiàn)objc_class有以下定義:
structobjc_class : objc_object {
// Class ISA;
Class superclass;
cache_tcache;
// formerly cache pointer and vtableclass_data_bits_tbits;
// class_rw_t * plus custom rr/alloc flags......
}
由此可見,結(jié)構(gòu)體objc_class也是繼承objc_object,說明Class在設(shè)計(jì)中本身也是一個(gè)對(duì)象。
其實(shí)Meta Class也是一個(gè)Class,那么它也跟其他Class一樣有自己的isa和super_class指針,關(guān)系如下:

Class isa and superclass relationship from Google
上圖實(shí)線是super_class指針,虛線是isa指針。有幾個(gè)關(guān)鍵點(diǎn)需要解釋以下:
Root class (class)其實(shí)就是NSObject,NSObject是沒有超類的,所以Root class(class)的superclass指向nil。
每個(gè)Class都有一個(gè)isa指針指向唯一的Meta class
Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一個(gè)回路。
每個(gè)Meta class的isa指針都指向Root class (meta)。
super_class表示實(shí)例對(duì)象對(duì)應(yīng)的父類
name表示類名
ivars表示多個(gè)成員變量,它指向objc_ivar_list結(jié)構(gòu)體。在runtime.h可以看到它的定義:
struct objc_ivar_list {
int ivar_count ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
OBJC2_UNAVAILABLE;
#ifdef __LP64__intspace ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
structobjc_ivar ivar_list[1] ? ? ? ? ? ? ? ? ? ? ? ? ? ?
OBJC2_UNAVAILABLE;
}
objc_ivar_list其實(shí)就是一個(gè)鏈表,存儲(chǔ)多個(gè)objc_ivar,而objc_ivar結(jié)構(gòu)體存儲(chǔ)類的單個(gè)成員變量信息。
methodLists表示方法列表,它指向objc_method_list結(jié)構(gòu)體的二級(jí)指針,可以動(dòng)態(tài)修改*methodLists的值來添加成員方法,也是Category實(shí)現(xiàn)原理,同樣也解釋Category不能添加實(shí)例變量的原因。在runtime.h可以看到它的定義:
struct objc_method_list {
struct objc_method_list *obsolete ? ? ? ? ? ? ? ? ? ? ? ?
OBJC2_UNAVAILABLE;
int method_count ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
OBJC2_UNAVAILABLE;
#ifdef __LP64__intspace ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
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;
structobjc_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__intspace ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
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
typede fvoid(*IMP)(void/* id, SEL, ... */);
#else typedefid(*IMP)(id, SEL, ...);
#endif
當(dāng)你向某個(gè)對(duì)象發(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)對(duì)象receiver調(diào)用方法message時(shí),首先根據(jù)對(duì)象receiver的isa指針查找到它對(duì)應(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作為入口,逐步深入分析Runtime的數(shù)據(jù)結(jié)構(gòu),了解每個(gè)數(shù)據(jù)結(jié)構(gòu)的作用和它們之間關(guān)系后,我們正式轉(zhuǎn)入消息發(fā)送這個(gè)正題。
objc_msgSend函數(shù)
在前面已經(jīng)提過,當(dāng)某個(gè)對(duì)象使用語法[receiver message]來調(diào)用某個(gè)方法時(shí),其實(shí)[receiver message]被編譯器轉(zhuǎn)化為:
id objc_msgSend (id self, SEL op, ... );
現(xiàn)在讓我們看一下objc_msgSend它具體是如何發(fā)送消息:
首先根據(jù)receiver對(duì)象的isa指針獲取它對(duì)應(yīng)的class
優(yōu)先在class的cache查找message方法,如果找不到,再到methodLists查找
如果沒有在class找到,再到super_class查找
一旦找到message這個(gè)方法,就執(zhí)行它實(shí)現(xiàn)的IMP。

Objc Message.gif
self與super
為了讓大家更好地理解self和super,借用sunnyxx博客的ios程序員6級(jí)考試一道題目:下面的代碼分別輸出什么?
@implementationSon:Father
- (id)init{self= [superinit];
if(self) ? ?{
NSLog(@"%@",NSStringFromClass([selfclass]));
NSLog(@"%@",NSStringFromClass([superclass])); ? ?
}
return self;
}
@end
self表示當(dāng)前這個(gè)類的對(duì)象,而super是一個(gè)編譯器標(biāo)示符,和self指向同一個(gè)消息接受者。在本例中,無論是[self class]還是[super class],接受消息者都是Son對(duì)象,但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(structobjc_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.structobjc_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 Classclass;
#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。
隱藏參數(shù)self和_cmd
當(dāng)[receiver message]調(diào)用方法時(shí),系統(tǒng)會(huì)在運(yùn)行時(shí)偷偷地動(dòng)態(tài)傳入兩個(gè)隱藏參數(shù)self和_cmd,之所以稱它們?yōu)殡[藏參數(shù),是因?yàn)樵谠创a中沒有聲明和定義這兩個(gè)參數(shù)。至于對(duì)于self的描述,上面已經(jīng)解釋非常清楚了,下面我們重點(diǎn)講解_cmd。
_cmd表示當(dāng)前調(diào)用方法,其實(shí)它就是一個(gè)方法選擇器SEL。一般用于判斷方法名或在Associated Objects中唯一標(biāo)識(shí)鍵名,后面在Associated Objects會(huì)講到。
方法解析與消息轉(zhuǎn)發(fā)
[receiver message]調(diào)用方法時(shí),如果在message方法在receiver對(duì)象的類繼承體系中沒有找到方法,那怎么辦?一般情況下,程序在運(yùn)行時(shí)就會(huì)Crash掉,拋出unrecognized selector sent to …類似這樣的異常信息。但在拋出異常之前,還有三次機(jī)會(huì)按以下順序讓你拯救程序。
Method Resolution
Fast Forwarding
Normal Forwarding

Message Forward from Google
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è)簡(jiǎn)單例子,定義一個(gè)類Message,它主要定義一個(gè)方法sendMessage,下面就是它的設(shè)計(jì)與實(shí)現(xiàn):
@interfaceMessage:NSObject- (void)sendMessage:(NSString*)word;@end
@implementationMessage
- (void)sendMessage:(NSString*)word{
NSLog(@"normal way : send message = %@", word);
}
@end
如果我在viewDidLoad方法中創(chuàng)建Message對(duì)象并調(diào)用sendMessage方法:
- (void)viewDidLoad { ? ?
[super viewDidLoad]; ? ?
Message *message = [Messagenew]; ? ?
[message sendMessage:@"Sam Lau"];
}
控制臺(tái)會(huì)打印以下信息:
normal way :sendmessage = 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([selfclass], sel, imp_implementationWithBlock(^(idself,NSString*word{
NSLog(@"method resolution way : send message = %@", word); ? ? ? ?
}),"v@*"); ?
}
returnYES;
}
控制臺(tái)就會(huì)打印以下信息:
method resolution way :sendmessage = Sam Lau
注意到上面代碼有這樣一個(gè)字符串"v@*,它表示方法的參數(shù)和返回值,詳情請(qǐng)參考Type Encodings
如果resolveInstanceMethod方法返回NO,運(yùn)行時(shí)就跳轉(zhuǎn)到下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)
Fast Forwarding
如果目標(biāo)對(duì)象實(shí)現(xiàn)- forwardingTargetForSelector:方法,系統(tǒng)就會(huì)在運(yùn)行時(shí)調(diào)用這個(gè)方法,只要這個(gè)方法返回的不是nil或self,也會(huì)重啟消息發(fā)送的過程,把這消息轉(zhuǎn)發(fā)給其他對(duì)象來處理。否則,就會(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]; ? ?
}returnnil;
}
此時(shí)還缺一個(gè)轉(zhuǎn)發(fā)消息的類MessageForwarding,這個(gè)類的設(shè)計(jì)與實(shí)現(xiàn)如下:
@interfaceMessageForwarding:NSObject
- (void)sendMessage:(NSString*)word;
@end
@implementationMessageForwarding
- (void)sendMessage:(NSString*)word{
NSLog(@"fast forwarding way : send message = %@", word);
}
@end
此時(shí),控制臺(tái)會(huì)打印以下信息:
fast forwarding way :sendmessage = Sam Lau
這里叫Fast,是因?yàn)檫@一步不會(huì)創(chuàng)建NSInvocation對(duì)象,但Normal Forwarding會(huì)創(chuàng)建它,所以相對(duì)于更快點(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對(duì)象并調(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];
}
}
關(guān)于這個(gè)例子的示例代碼請(qǐng)到github下載。
三種方法的選擇
Runtime提供三種方式來將原來的方法實(shí)現(xiàn)代替掉,那該怎樣選擇它們呢?
Method Resolution:由于Method Resolution不能像消息轉(zhuǎn)發(fā)那樣可以交給其他對(duì)象來處理,所以只適用于在原來的類中代替掉。
Fast Forwarding:它可以將消息處理轉(zhuǎn)發(fā)給其他對(duì)象,使用范圍更廣,不只是限于原來的對(duì)象。
Normal Forwarding:它跟Fast Forwarding一樣可以消息轉(zhuǎn)發(fā),但它能通過NSInvocation對(duì)象獲取更多消息發(fā)送的信息,例如:target、selector、arguments和返回值等信息。
Associated Objects
Categories can be used to declare either instance methods or class methods but are not usually suitable for declaring additional properties. It’s valid syntax to include a property declaration in a category interface, but it’s not possible to declare an additional instance variable in a category. This means the compiler won’t synthesize any instance variable, nor will it synthesize any property accessor methods. You can write your own accessor methods in the category implementation, but you won’t be able to keep track of a value for that property unless it’s already stored by the original class. (Programming with Objective-C)
當(dāng)想使用Category對(duì)已存在的類進(jìn)行擴(kuò)展時(shí),一般只能添加實(shí)例方法或類方法,而不適合添加額外的屬性。雖然可以在Category頭文件中聲明property屬性,但在實(shí)現(xiàn)文件中編譯器是無法synthesize任何實(shí)例變量和屬性訪問方法。這時(shí)需要自定義屬性訪問方法并且使用Associated Objects來給已存在的類Category添加自定義的屬性。Associated Objects提供三個(gè)API來向?qū)ο?b>添加、獲取和刪除關(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+AssociatedObjectCategory添加屬性associatedObject的示例代碼:
NSObject+AssociatedObject.h
@interface NSObject(AssociatedObject)
@property(strong,nonatomic)id associatedObject;
@end
NSObject+AssociatedObject.m
@implementationNSObject(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。
Method Swizzling
Method Swizzling就是在運(yùn)行時(shí)將一個(gè)方法的實(shí)現(xiàn)代替為另一個(gè)方法的實(shí)現(xiàn)。如果能夠利用好這個(gè)技巧,可以寫出簡(jiǎn)潔、有效且維護(hù)性更好的代碼。可以參考兩篇關(guān)于Method Swizzling技巧的文章:
Method Swizzling 和 AOP 實(shí)踐
Aspect-Oriented Programming(AOP)
類似記錄日志、身份驗(yàn)證、緩存等事務(wù)非?,嵥椋c業(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)證、緩存)而無需修改代碼。
危險(xiǎn)性
Method Swizzling就像一把瑞士小刀,如果使用得當(dāng),它會(huì)有效地解決問題。但使用不當(dāng),將帶來很多麻煩。在stackoverflow上有人已經(jīng)提出這樣一個(gè)問題:What are the Dangers of Method Swizzling in Objective C?,它的危險(xiǎn)性主要體現(xiàn)以下幾個(gè)方面:
Method swizzling is not atomic
Changes behavior of un-owned code
Possible naming conflicts
Swizzling changes the method's arguments
The order of swizzles matters
Difficult to understand (looks recursive)
Difficult to debug
總結(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)目。
擴(kuò)展閱讀
Method Swizzling 和 AOP 實(shí)踐
What are the Dangers of Method Swizzling in Objective C?
RunLoop
深入理解RunLoop這篇文章寫的很好!
簡(jiǎn)介
RunLoop顧名思義,就是運(yùn)行循環(huán)的意思。
基本作用:
保持程序的持續(xù)運(yùn)行
處理App中的各類事件(觸摸事件、定時(shí)器事件、Selector事件)
節(jié)省CPU資源,提高程序性能:沒有事件時(shí)就進(jìn)行睡眠狀態(tài)
內(nèi)部實(shí)現(xiàn):
do-while循環(huán),在這個(gè)循環(huán)內(nèi)部不斷地處理各種任務(wù)(Source\Timeer\Observer)
注意點(diǎn):
一個(gè)線程對(duì)應(yīng)一個(gè)RunLoop(采用字典存儲(chǔ),線程號(hào)為key,RunLoop為value)
主線程的RunLoop默認(rèn)已經(jīng)啟動(dòng),子線程的RunLoop需要手動(dòng)啟動(dòng)
RunLoop只能選擇一個(gè)Mode啟動(dòng),如果當(dāng)前Mode沒有任何Source、Timer、Observer,那么就不會(huì)進(jìn)入RunLoop
RunLoop的主要函數(shù)調(diào)用順序?yàn)椋篊FRunLoopRun->CFRunLoopRunSpecific->__CFRunLoopRun

注意特殊情況,事實(shí)上,在只有Observer的情況,也不一定會(huì)進(jìn)入循環(huán),因?yàn)樵创a里面只會(huì)顯式地檢測(cè)兩個(gè)東西:Source和Timer(這兩個(gè)是主動(dòng)向RunLoop發(fā)送消息的);Observer是被動(dòng)接收消息的

RunLoop在第一次獲取時(shí)創(chuàng)建,在線程結(jié)束時(shí)銷毀
RunLoop循環(huán)示意圖:(針對(duì)上面的__CFRunLoopRun函數(shù),Mode已經(jīng)判斷非空前提)
圖1

RunLoop循環(huán)示意圖
圖2

接觸過微處理器編程的基本上都知道,在編寫微處理器程序時(shí),我通常會(huì)在main函數(shù)中寫一個(gè)無限循環(huán),然后在這個(gè)循環(huán)里面對(duì)外部事件進(jìn)行監(jiān)聽,比如外部中斷,一些傳感器的數(shù)據(jù)等,在沒有外部中斷時(shí),就讓CPU進(jìn)入低功耗模式。如果接收到了外部中斷,就恢復(fù)到正常模式,對(duì)中斷進(jìn)行處理。
while(1) {// 根據(jù)中斷決定是否切換模式執(zhí)行任務(wù)}// 或者for(;;) {}
RunLoop和這個(gè)相似,也是在線程的main中增加了一個(gè)循環(huán):
int main(int argc,char* argv[]) {
BOOLrunning =YES;
do{// 執(zhí)行各種任務(wù),處理各種事件// ......
}
while(running);
return0;
}
所以線程在這種情況下,便不會(huì)退出。
關(guān)于MainRunLoop:
int main(int argc,char* argv[]) {
@autoreleasepool{
return UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegate class])); ? ?
}
}
在viewDidLoad中設(shè)置斷電,然后得到以下主線程棧信息:

可以看到,UIApplicationMain內(nèi)部啟動(dòng)了一個(gè)和主線程相關(guān)聯(lián)的RunLoop(_CFRunLoopRun)。在這里也可以推斷,程序進(jìn)入U(xiǎn)IApplicationMain就不會(huì)退出了。我稍微對(duì)主函數(shù)進(jìn)行了如下修改,并在return語句上打印了斷點(diǎn):

運(yùn)行程序后,并不會(huì)在斷點(diǎn)處停下,證實(shí)了上面的推斷。
上面涉及了一個(gè)_CFRunLoopRun函數(shù),接下來說明下iOS中訪問和使用RunLoop的API:
Foundation--NSRunLoop
Core Foundation--CFRunLoopRef(開源)
因?yàn)楹笳呤情_源的,且前者是在后者上針對(duì)OC的封裝,所以一般是對(duì)CFRunLoopRef進(jìn)行研究。
兩套API對(duì)應(yīng)獲取RunLoop對(duì)象的方式:
Foundation
[NSRunLoop currentRunLoop]; // 當(dāng)前runloop
[NSRunLoop mainRunLoop];// 主線程runloop
Core Foundation
CFRunLoopGetCurrent();// 當(dāng)前runloop
CFRunLoopGetMain();// 主線程runloop
值得注意的是,獲取當(dāng)前RunLoop都是進(jìn)行懶加載的,也就是調(diào)用時(shí)自動(dòng)創(chuàng)建線程對(duì)應(yīng)的RunLoop。
RunLoop相關(guān)類:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

類之間的關(guān)系
以上圖片說明了各個(gè)類之間的關(guān)系。
CFRunLoopModeRef說明:
代表RunLoop的運(yùn)行模式,一個(gè)RunLoop可以包含多個(gè)Mode,每個(gè)Mode可以包含多個(gè)Source、Timer、Observer
每次RunLoop啟動(dòng)時(shí),只能指定其中一個(gè)Mode,這個(gè)Mode就變成了CurrentMode
當(dāng)啟動(dòng)RunLoop時(shí),如果所在Mode中沒有Source、Timer、Observer,那么將不會(huì)進(jìn)入RunLoop,會(huì)直接結(jié)束
如果要切換Mode,只能退出Loop,再重新制定一個(gè)Mode進(jìn)入
系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:
NSDefaultRunLoopMode:App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響
UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用
GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
NSRunLoopCommonModes: 這是一個(gè)占位用的Mode,不是一種真正的Mode
關(guān)于NSRunLoopCommonModes:
一個(gè)Mode可以將自己標(biāo)記為“Common”屬性,每當(dāng) RunLoop 的內(nèi)容發(fā)生變化時(shí),RunLoop會(huì)對(duì)標(biāo)記有“Common”屬性的Mode進(jìn)行相適應(yīng)的切換,并同步Source/Observer/Timer
在主線程中,kCFRunLoopDefaultMode 和 UITrackingRunLoopMode這兩個(gè)Mode都是被默認(rèn)標(biāo)記為“Common”屬性的,從輸出的主線程RunLoop可以查看。

- 結(jié)合上面兩點(diǎn),當(dāng)使用NSRunLoopCommonModes占位時(shí),會(huì)表明使用標(biāo)記為“Common”屬性的Mode,在一定層度上,可以說是“擁有了兩個(gè)Mode”,可以在這兩個(gè)Mode中的其中任意一個(gè)進(jìn)行工作
CFRunLoopTimerRef說明:
CFRunLoopTimerRef是基于時(shí)間的觸發(fā)器,它包含了一個(gè)時(shí)間長(zhǎng)度和一個(gè)回調(diào)函數(shù)指針。當(dāng)它加入到RunLoop時(shí),RunLoop會(huì)注冊(cè)對(duì)應(yīng)的時(shí)間點(diǎn),當(dāng)時(shí)間點(diǎn)到時(shí),RunLoop會(huì)被喚醒以執(zhí)行那個(gè)回調(diào)
CFRunLoopTimerRef大部分指的是NSTimer,它受RunLoop的Mode影響
由于NSTimer在RunLoop中處理,所以受其影響較大,有時(shí)可能會(huì)不準(zhǔn)確。還有一種定時(shí)器是GCD定時(shí)器,它并不在RunLoop中,所以不受其影響,也就比較精確
接下來說明各種Mode下,NSTimer的工作情況:
情況1
在對(duì)創(chuàng)建的定時(shí)器進(jìn)行模式修改前,scheduledTimerWithTimeInterval創(chuàng)建的定時(shí)器只在NSDefaultRunLoopMode模式下可以正常運(yùn)行,當(dāng)滾動(dòng)UIScroolView時(shí),模式轉(zhuǎn)換成UITrackingRunLoopMode,定時(shí)器就失效了。
修改成NSRunLoopCommonModes后,定時(shí)器在兩個(gè)模式下都可以正常運(yùn)行
// 創(chuàng)建的定時(shí)器默認(rèn)添加到當(dāng)前的RunLoop中(沒有就創(chuàng)建),而且是NSDefaultRunLoopMode模式NSTimer*timer = [NSTimerscheduledTimerWithTimeInterval:1.0target:selfselector:@selector(run) userInfo:nilrepeats:YES];
// 可以通過以下方法對(duì)模型進(jìn)行修改
[[NSRunLoopmainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
情況2
timerWithTimeInterval創(chuàng)建的定時(shí)器并沒有手動(dòng)添加進(jìn)RunLoop,所以需要手動(dòng)進(jìn)行添加。當(dāng)添加為以下模式時(shí),定時(shí)器只在UITrackingRunLoopMode模式下進(jìn)行工作,也就是滑動(dòng)UIScrollView時(shí)就會(huì)工作,停止滑動(dòng)時(shí)就不工作
如果把UITrackingRunLoopMode換成NSDefaultRunLoopMode,那么效果就和情況1沒修改Mode前的效果一樣
NSTimer*timer = [NSTimertimerWithTimeInterval:1.0target:selfselector:@selector(run) userInfo:nilrepeats:YES];
// 在UITrackingRunLoopMode模式下定時(shí)器才會(huì)運(yùn)行
[[NSRunLoopmainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
CFRunLoopSourceRef說明:
Source分類
按官方文檔
Port-Based Sources
Custom Input Sources
Cocoa Perform Selector Sources
按照函數(shù)調(diào)用棧
Source0:非基于Port的
Source0本身不能主動(dòng)觸發(fā)事件,只包含了一個(gè)回調(diào)函數(shù)指針
Source1:基于Port的,通過內(nèi)核和其他線程通信,接收、分發(fā)系統(tǒng)事件
包含了mach_port和一個(gè)回調(diào)函數(shù)指針,接收到相關(guān)消息后,會(huì)分發(fā)給Source0進(jìn)行處理
CFRunLoopObserverRef說明:
CFRunLoopObserverRef是觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變
能夠監(jiān)聽的狀態(tài)
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity){ ? ? ? ?
kCFRunLoopEntry = (1UL <<0),
// 進(jìn)入RunLoopkCFRunLoopBeforeTimers = (1UL <<1),
//即將處理timerkCFRunLoopBeforeSources = (1UL <<2),
//即將處理SourceskCFRunLoopBeforeWaiting = (1UL <<5),
//即將進(jìn)入休眠kCFRunLoopAfterWaiting = (1UL <<6),
//即將喚醒kCFRunLoopExit = (1UL <<7),
//即將退出RunLoopkCFRunLoopAllActivities =0x0FFFFFFFU//所有活動(dòng)
};
添加監(jiān)聽者步驟
// 創(chuàng)建監(jiān)聽著
CFRunLoopObserverRefobserver =CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopBeforeTimers,YES,0, ^(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity) {
NSLog(@"%ld", activity); ? ?
});
// ? ?[[NSRunLoop currentRunLoop] getCFRunLoop]
// 向當(dāng)前runloop添加監(jiān)聽者
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 釋放內(nèi)存CFRelease(observer);
CF的內(nèi)存管理(Core Foundation):
1.凡是帶有Create、Copy、Retain等字眼的函數(shù),創(chuàng)建出來的對(duì)象,都需要在最后做一次release
比如CFRunLoopObserverCreate
2.release函數(shù):CFRelease(對(duì)象);
自動(dòng)釋放池釋放的時(shí)間和RunLoop的關(guān)系:
注意,這里的自動(dòng)釋放池指的是主線程的自動(dòng)釋放池,我們看不見它的創(chuàng)建和銷毀。自己手動(dòng)創(chuàng)建@autoreleasepool {}是根據(jù)代碼塊來的,出了這個(gè)代碼塊就釋放了。
App啟動(dòng)后,蘋果在主線程 RunLoop 里注冊(cè)了兩個(gè) Observer,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()。
第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop),其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush()創(chuàng)建自動(dòng)釋放池。其 order 是-2147483647,優(yōu)先級(jí)最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。

第二個(gè) Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush()釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop) 時(shí)調(diào)用 _objc_autoreleasePoolPop() 來釋放自動(dòng)釋放池。這個(gè) Observer 的 order 是 2147483647,優(yōu)先級(jí)最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。

在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)、Timer回調(diào)內(nèi)的。這些回調(diào)會(huì)被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著,所以不會(huì)出現(xiàn)內(nèi)存泄漏,開發(fā)者也不必顯示創(chuàng)建 Pool 了。
在自己創(chuàng)建線程時(shí),需要手動(dòng)創(chuàng)建自動(dòng)釋放池AutoreleasePool
綜合上面,可以得到以下結(jié)論:

@autoreleasepool {}內(nèi)部實(shí)現(xiàn)
有以下代碼:
int main(int argc,const char* argv[]){
@autoreleasepool{ ? ?
}
return0;
}
查看編譯轉(zhuǎn)換后的代碼:
int main(int argc,const char* argv[]){
/* @autoreleasepool */{
__AtAutoreleasePool __autoreleasepool; ? ?
}
return0;
}
__AtAutoreleasePool是什么呢?找到其定義:
struct__AtAutoreleasePool {
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
} ?~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void* atautoreleasepoolobj;
};
可以看到__AtAutoreleasePool是一個(gè)類:
其構(gòu)造函數(shù)使用objc_autoreleasePoolPush創(chuàng)建了一個(gè)線程池,并保存給成員變量atautoreleasepool obj。
其析構(gòu)函數(shù)使用objc_autoreleasePoolPop銷毀了線程池
結(jié)合以上信息,main函數(shù)里面的__autoreleasepool是一個(gè)局部變量。當(dāng)其創(chuàng)建時(shí),會(huì)調(diào)用構(gòu)造函數(shù)創(chuàng)建線程池,出了{(lán)}代碼塊時(shí),局部變量被銷毀,調(diào)用其析構(gòu)函數(shù)銷毀線程池。
RunLoop實(shí)際應(yīng)用
常駐線程
當(dāng)創(chuàng)建一個(gè)線程,并且希望它一直存在時(shí),就需要使用到RunLoop,否則線程一執(zhí)行完任務(wù)就會(huì)停止。
要向線程存在,需要有強(qiáng)指針引用他,其他的代碼如下:
// 屬性
@property(strong,nonatomic)NSThread *thread;
// 創(chuàng)建線程
_thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(test) object:nil];
[_thread start];
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event{
// 點(diǎn)擊時(shí)使線程_thread執(zhí)行test方法
[self performSelector:@selector(test) onThread:_thread withObject:nilwaitUntilDone:NO];
}
- (void)test{
NSLog(@"__test__");
}
就單單以上代碼,是不起效果的,因?yàn)榫€程沒有RunLoop,執(zhí)行完test后就停止了,無法再讓其執(zhí)行任務(wù)(強(qiáng)制start會(huì)崩潰)。
通過在子線程中給RunLoop添加監(jiān)聽者,可以了解下performSelector:onThread:內(nèi)部做的事情:
調(diào)用performSelector:onThread: 時(shí),實(shí)際上它會(huì)創(chuàng)建一個(gè)Source0加到對(duì)應(yīng)線程的RunLoop里去,所以,如果對(duì)應(yīng)的線程沒有RunLoop,這個(gè)方法就會(huì)失效

// 這句在主線程中調(diào)用// _thread就是下面的線程
[selfperformSelector:@selector(run) onThread:_thread withObject:nilwaitUntilDone:NO];
performSelecter:afterDelay:也是一樣的內(nèi)部操作方法,只是創(chuàng)建的Timer添加到當(dāng)前線程的RunLoop中了

// 創(chuàng)建RunLoop即將喚醒監(jiān)聽者
CFRunLoopObserverRefobserver =CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopBeforeTimers,YES,0, ^(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity) {
/ /打印喚醒前的
RunLoopNSLog(@"%ld--%@", activity, [NSRunLoopcurrentRunLoop]); ?
});
// 向當(dāng)前runloop添加監(jiān)聽者
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 釋放內(nèi)存CFRelease(observer); ?
[self performSelector:@selector(setView:) withObject:nilafterDelay:2.0];
// 使model不為空
[[NSRunLoop currentRunLoop] addPort:[NSPortport] forMode:NSDefault RunLoopMode]; ?
[[NSRunLoop currentRunLoop] run];
綜合上面的解釋,可以知道performSelector:onThread:沒有起作用,是因?yàn)開thread線程內(nèi)部沒有RunLoop,所以需要在線程內(nèi)部創(chuàng)建RunLoop。
創(chuàng)建RunLoop并使對(duì)應(yīng)線程成為常駐線程的常見方式有2:
方式1
向創(chuàng)建的RunLoop添加NSPort(Sources),讓Mode不為空,RunLoop能進(jìn)入循環(huán)不會(huì)退出
[[NSRunLoop currentRunLoop]addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop]run];
方式2
讓RunLoop一直嘗試運(yùn)行,判斷Mode是否為空,不是為空就進(jìn)入RunLoop循環(huán)
while(1) { ?
[[NSRunLoopcurrentRunLoop] run];
}
AFNetWorking就使用到了常駐線程:
創(chuàng)建常駐線程
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool{ ? ? ? ?
[[NSThread currentThread] setName:@"AFNetworking"];
// 創(chuàng)建RunLoop并向Mode添加NSMachPort,使RunLoop不會(huì)退出
NSRunLoop*runLoop = [NSRunLoopcurrentRunLoop]; ? ? ?
[runLoop addPort:[NSMachPortport] forMode:NSDefaultRunLoopMode]; ? ? ? ?
[runLoop run]; ? ?
}
}
+ (NSThread*)networkRequestThread {
static NSThread*_networkRequestThread =nil;
static dispatch_once_toncePredicate;dispatch_once(&oncePredicate, ^{ ? ? ? ?
_networkRequestThread = [[NSThreadalloc] initWithTarget:selfselector:@selector(networkRequestThreadEntryPoint:) object:nil]; ? ? ? ?[_networkRequestThread start]; ? ?
});
return_networkRequestThread;
}
使用常駐線程
- (void)start { ? ?
[self.locklock];
if([selfisCancelled]) { ? ? ? ?
[selfperformSelector:@selector(cancelConnection) onThread:[[selfclass] networkRequestThread] withObject:nilwaitUntilDone:NOmodes:[self.runLoopModesallObjects]]; ? ?
}else if([selfisReady]) {
self.state= AFOperationExecutingState; ? ? ? ?
[self performSelector:@selector(operationDidStart) onThread:[[selfclass] networkRequestThread] withObject:nilwaitUntilDone:NOmodes:[self.runLoopModesallObjects]]; ?
} ?
[self.lockunlock];
}
給子線程開啟定時(shí)器
_thread = [[NSThread alloc] initWithTarget:selfselector:@selector(test) object:nil];
[_thread start];
// 子線程添加定時(shí)器
- (void)subTimer{
// 默認(rèn)創(chuàng)建RunLoop并向其model添加timer,所以后續(xù)只需要讓RunLoop run起來即可
[NSTimer scheduledTimerWithTimeInterval:1.0target:selfselector:@selector(run) userInfo:nilrepeats:YES];
// 貌似source1不為空,source0就不為空// ?
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];[[NSRunLoopcurrentRunLoop] run];
}
讓某些事件(行為、任務(wù))在特定模式下執(zhí)行
比如圖片的設(shè)置,在UIScrollView滾動(dòng)的情況下,我不希望設(shè)置圖片,等停止?jié)L動(dòng)了再設(shè)置圖片,可以用以下代碼:
// 圖片只在NSDefaultRunLoopMode模式下會(huì)進(jìn)行設(shè)置顯示
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20150712_39"] afterDelay:2.0inModes:@[NSDefault RunLoopMode]];
先設(shè)置任務(wù)在NSDefaultRunLoopMode模式在執(zhí)行,這樣,在滾動(dòng)使RunLoop進(jìn)入U(xiǎn)ITrackingRunLoopMode時(shí),就不會(huì)進(jìn)行圖片的設(shè)置了。
控制定時(shí)器在特定模式下執(zhí)行
上文的《CFRunLoopTimerRef說明:》中已經(jīng)指出
添加Observer監(jiān)聽RunLoop的狀態(tài)
監(jiān)聽點(diǎn)擊事件的處理(在所有點(diǎn)擊事件之前做一些事情)
具體步驟在《CFRunLoopObserverRef說明:》中已寫明
GCD定時(shí)器
注意:
dispatch_source_t是個(gè)類,這點(diǎn)比較特殊
// ? ?dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_ttimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0, dispatch_get_global_queue(0,0)); ?
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW,1.0* NSEC_PER_SEC,0* NSEC_PER_SEC); ? ?dispatch_source_set_event_handler(timer, ^{ ? ? ? ?
NSLog(@"__"); ? ? ? ?
NSLog(@"%@", [NSThread currentThread]);
staticNSInteger count =0;
if(count++ ==3) {
// 為什么dispatch_cancel不能用_timer?/
// Controlling expression type '__strong dispatch_source_t' (aka 'NSObject *__strong') not compatible with any generic association type
// 類型錯(cuò)誤,可能dispatch_cancel是宏定義,需要的就是方法調(diào)用,而不是變量// ? ? ? ? ? ?dispatch_cancel(self.timer);dispatch_source_cancel(_timer); ? ? ? ?
} ? ?
});
// 定時(shí)器默認(rèn)是停止的,需要手動(dòng)恢復(fù)
dispatch_resume(timer);
// 需要一個(gè)強(qiáng)引用保證timer不被釋放
_timer = timer;
最后一點(diǎn)需要說明的是,SDWebImage框架的下載圖片業(yè)務(wù)中也使用到了RunLoop,老確保圖片下載成功后才關(guān)閉任務(wù)子線程。
原文鏈接:http://www.itdecent.cn/p/ebc6e20b84cf