iOS runtime和runloop

runtime 和 runloop 作為一個程序員進階是必須的惹悄,也是非常重要的憔儿, 在面試過程中是經(jīng)常會被問到的, 所以大家有必要進行研究量蕊,有能力的童鞋可以和下面作者一樣, 親歷實踐一下艇挨。


在簡書里發(fā)現(xiàn)了兩篇非常好的文章介紹 runtime和runloop的残炮,在這里合二為一了, 把原版作者的東西拿了過來缩滨, 為了尊重作者势就,在這里注明一下 @sam_lau 是runtime的作者, @tripleCC是runloop的作者?




RunTime

Objective-C是基于C語言加入了面向?qū)ο筇匦?/b>和消息轉(zhuǎn)發(fā)機制的動態(tài)語言脉漏,這意味著它不僅需要一個編譯器苞冯,還需要Runtime系統(tǒng)來動態(tài)創(chuàng)建類和對象,進行消息發(fā)送和轉(zhuǎn)發(fā)侧巨。下面通過分析Apple開源的Runtime代碼(我使用的版本是objc4-646.tar)來深入理解Objective-C的Runtime機制舅锄。

Runtime數(shù)據(jù)結(jié)構(gòu)

在Objective-C中,使用[receiver message]語法并不會馬上執(zhí)行receiver對象的message方法的代碼司忱,而是向receiver發(fā)送一條message消息皇忿,這條消息可能由receiver來處理,也可能由轉(zhuǎn)發(fā)給其他對象來處理坦仍,也有可能假裝沒有接收到這條消息而沒有處理鳍烁。其實[receiver message]被編譯器轉(zhuǎn)化為:

idobjc_msgSend (idself, SEL op, ... );

下面從兩個數(shù)據(jù)結(jié)構(gòu)id和SEL來逐步分析和理解Runtime有哪些重要的數(shù)據(jù)結(jié)構(gòu)。

SEL

SEL是函數(shù)objc_msgSend第二個參數(shù)的數(shù)據(jù)類型繁扎,表示方法選擇器幔荒,按下面路徑打開objc.h文件

SEL Data Structure

查看到SEL數(shù)據(jù)結(jié)構(gòu)如下:

typedefstructobjc_selector *SEL;

其實它就是映射到方法的C字符串,你可以通過Objc編譯器命令@selector()或者Runtime系統(tǒng)的sel_registerName函數(shù)來獲取一個SEL類型的方法選擇器锻离。

如果你知道selector對應的方法名是什么铺峭,可以通過NSString* NSStringFromSelector(SEL aSelector)方法將SEL轉(zhuǎn)化為字符串,再用NSLog打印汽纠。

id

接下來看objc_msgSend第一個參數(shù)的數(shù)據(jù)類型id卫键,id是通用類型指針,能夠表示任何對象虱朵。按下面路徑打開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其實就是一個指向objc_object結(jié)構(gòu)體指針莉炉,它包含一個Class isa成員钓账,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類

注意:根據(jù)Apple的官方文檔Key-Value Observing Implementation Details提及絮宁,key-value observing是使用isa-swizzling的技術(shù)實現(xiàn)的梆暮,isa指針在運行時被修改,指向一個中間類而不是真正的類绍昂。所以啦粹,你不應該使用isa指針來確定類的關系,而是使用class方法來確定實例對象的類窘游。

Class

isa指針的數(shù)據(jù)類型是Class唠椭,Class表示對象所屬的類,按下面路徑打開objc.h文件

Class Data Structure

/// An opaque type that represents an Objective-C class.typedefstructobjc_class *Class;

可以查看到Class其實就是一個objc_class結(jié)構(gòu)體指針忍饰,但這個頭文件找不到它的定義贪嫂,需要在runtime.h才能找到objc_class結(jié)構(gòu)體的定義。

按下面路徑打開runtime.h文件

objc_class Data Structure

查看到objc_class結(jié)構(gòu)體定義如下:

structobjc_class {? ? Class isa? OBJC_ISA_AVAILABILITY;#if !__OBJC2__Class super_class? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;constchar*name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;longversion? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;longinfo? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;longinstance_size? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;structobjc_ivar_list *ivars? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;structobjc_method_list **methodLists? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;structobjc_cache *cache? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;structobjc_protocol_list *protocols? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;#endif} OBJC2_UNAVAILABLE;/* Use `Class` instead of `struct objc_class *` */

注意:OBJC2_UNAVAILABLE是一個Apple對Objc系統(tǒng)運行版本進行約束的宏定義艾蓝,主要為了兼容非Objective-C 2.0的遺留版本力崇,但我們?nèi)阅軓闹蝎@取一些有用信息。

讓我們分析一些重要的成員變量表示什么意思和對應使用哪些數(shù)據(jù)結(jié)構(gòu)赢织。

isa表示一個Class對象的Class亮靴,也就是Meta Class。在面向?qū)ο笤O計中敌厘,一切都是對象台猴,Class在設計中本身也是一個對象。我們會在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在設計中本身也是一個對象宪彩。

其實Meta Class也是一個Class休讳,那么它也跟其他Class一樣有自己的isa和super_class指針,關系如下:

Class isa and superclass relationship from Google

上圖實線是super_class指針尿孔,虛線是isa指針俊柔。有幾個關鍵點需要解釋以下:

Root class (class)其實就是NSObject,NSObject是沒有超類的活合,所以Root class(class)的superclass指向nil雏婶。

每個Class都有一個isa指針指向唯一的Meta class

Root class(meta)的superclass指向Root class(class),也就是NSObject白指,形成一個回路留晚。

每個Meta class的isa指針都指向Root class (meta)。

super_class表示實例對象對應的父類

name表示類名

ivars表示多個成員變量告嘲,它指向objc_ivar_list結(jié)構(gòu)體错维。在runtime.h可以看到它的定義:

structobjc_ivar_list {intivar_count? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;#ifdef __LP64__intspace? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;#endif/* variable length structure */structobjc_ivar ivar_list[1]? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;}

objc_ivar_list其實就是一個鏈表奖地,存儲多個objc_ivar,而objc_ivar結(jié)構(gòu)體存儲類的單個成員變量信息赋焕。

methodLists表示方法列表参歹,它指向objc_method_list結(jié)構(gòu)體的二級指針,可以動態(tài)修改*methodLists的值來添加成員方法隆判,也是Category實現(xiàn)原理犬庇,同樣也解釋Category不能添加實例變量的原因。在runtime.h可以看到它的定義:

structobjc_method_list {structobjc_method_list *obsolete? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;intmethod_count? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;#ifdef __LP64__intspace? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;#endif/* variable length structure */structobjc_method method_list[1]? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;}

同理侨嘀,objc_method_list也是一個鏈表械筛,存儲多個objc_method,而objc_method結(jié)構(gòu)體存儲類的某個方法的信息飒炎。

cache用來緩存經(jīng)常訪問的方法,它指向objc_cache結(jié)構(gòu)體笆豁,后面會重點講到郎汪。

protocols表示類遵循哪些協(xié)議

Method

Method表示類中的某個方法,在runtime.h文件中找到它的定義:

/// An opaque type that represents a method in a class definition.typedefstructobjc_method *Method;structobjc_method {? ? SEL method_name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;char*method_types? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;? ? IMP method_imp? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;}

其實Method就是一個指向objc_method結(jié)構(gòu)體指針闯狱,它存儲了方法名(method_name)煞赢、方法類型(method_types)和方法實現(xiàn)(method_imp)等信息。而method_imp的數(shù)據(jù)類型是IMP哄孤,它是一個函數(shù)指針照筑,后面會重點提及。

Ivar

Ivar表示類中的實例變量瘦陈,在runtime.h文件中找到它的定義:

/// An opaque type that represents an instance variable.typedefstructobjc_ivar *Ivar;structobjc_ivar {char*ivar_name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;char*ivar_type? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;intivar_offset? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;#ifdef __LP64__intspace? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;#endif}

Ivar其實就是一個指向objc_ivar結(jié)構(gòu)體指針凝危,它包含了變量名(ivar_name)、變量類型(ivar_type)等信息晨逝。

IMP

在上面講Method時就說過蛾默,IMP本質(zhì)上就是一個函數(shù)指針,指向方法的實現(xiàn)捉貌,在objc.h找到它的定義:

/// A pointer to the function of a method implementation.#if!OBJC_OLD_DISPATCH_PROTOTYPEStypedefvoid(*IMP)(void/* id, SEL, ... */);#elsetypedefid(*IMP)(id, SEL, ...);#endif

當你向某個對象發(fā)送一條信息支鸡,可以由這個函數(shù)指針來指定方法的實現(xiàn),它最終就會執(zhí)行那段代碼趁窃,這樣可以繞開消息傳遞階段而去執(zhí)行另一個方法實現(xiàn)牧挣。

Cache

顧名思義,Cache主要用來緩存醒陆,那它緩存什么呢瀑构?我們先在runtime.h文件看看它的定義:

typedefstructobjc_cache *Cache? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;structobjc_cache {unsignedintmask/* total = mask + 1 */OBJC2_UNAVAILABLE;unsignedintoccupied? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;? ? Method buckets[1]? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;};

Cache其實就是一個存儲Method的鏈表,主要是為了優(yōu)化方法調(diào)用的性能统求。當對象receiver調(diào)用方法message時检碗,首先根據(jù)對象receiver的isa指針查找到它對應的類据块,然后在類的methodLists中搜索方法,如果沒有找到折剃,就使用super_class指針到父類中的methodLists查找另假,一旦找到就調(diào)用方法。如果沒有找到怕犁,有可能消息轉(zhuǎn)發(fā)边篮,也可能忽略它。但這樣查找方式效率太低奏甫,因為往往一個類大概只有20%的方法經(jīng)常被調(diào)用戈轿,占總調(diào)用次數(shù)的80%。所以使用Cache來緩存經(jīng)常調(diào)用的方法阵子,當調(diào)用方法時思杯,優(yōu)先在Cache查找,如果沒有找到挠进,再到methodLists查找色乾。

消息發(fā)送

前面從objc_msgSend作為入口,逐步深入分析Runtime的數(shù)據(jù)結(jié)構(gòu)领突,了解每個數(shù)據(jù)結(jié)構(gòu)的作用和它們之間關系后暖璧,我們正式轉(zhuǎn)入消息發(fā)送這個正題。

objc_msgSend函數(shù)

在前面已經(jīng)提過君旦,當某個對象使用語法[receiver message]來調(diào)用某個方法時澎办,其實[receiver message]被編譯器轉(zhuǎn)化為:

idobjc_msgSend (idself, SEL op, ... );

現(xiàn)在讓我們看一下objc_msgSend它具體是如何發(fā)送消息:

首先根據(jù)receiver對象的isa指針獲取它對應的class

優(yōu)先在class的cache查找message方法,如果找不到金砍,再到methodLists查找

如果沒有在class找到局蚀,再到super_class查找

一旦找到message這個方法,就執(zhí)行它實現(xiàn)的IMP恕稠。

Objc Message.gif

self與super

為了讓大家更好地理解self和super至会,借用sunnyxx博客的ios程序員6級考試一道題目:下面的代碼分別輸出什么?

@implementationSon:Father- (id)init{self= [superinit];if(self)? ? {NSLog(@"%@",NSStringFromClass([selfclass]));NSLog(@"%@",NSStringFromClass([superclass]));? ? }returnself;}@end

self表示當前這個類的對象谱俭,而super是一個編譯器標示符奉件,和self指向同一個消息接受者。在本例中昆著,無論是[self class]還是[super class]县貌,接受消息者都是Son對象,但super與self不同的是凑懂,self調(diào)用class方法時煤痕,是在子類Son中查找方法,而super調(diào)用class方法時,是在父類Father中查找方法摆碉。

當調(diào)用[self class]方法時塘匣,會轉(zhuǎn)化為objc_msgSend函數(shù),這個函數(shù)定義如下:

idobjc_msgSend(idself, SEL op, ...)

這時會從當前Son類的方法列表中查找巷帝,如果沒有忌卤,就到Father類查找,還是沒有楞泼,最后在NSObject類查找到驰徊。我們可以從NSObject.mm文件中看到- (Class)class的實現(xiàn):

- (Class)class{returnobject_getClass(self);}

所以NSLog(@"%@", NSStringFromClass([self class]));會輸出Son

當調(diào)用[super class]方法時堕阔,會轉(zhuǎn)化為objc_msgSendSuper棍厂,這個函數(shù)定義如下:

idobjc_msgSendSuper(structobjc_super *super, SEL op, ...)

objc_msgSendSuper函數(shù)第一個參數(shù)super的數(shù)據(jù)類型是一個指向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)體包含兩個成員超陆,第一個是receiver牺弹,表示某個類的實例。第二個是super_class表示當前類的父類时呀。

這時首先會構(gòu)造出objc_super結(jié)構(gòu)體例驹,這個結(jié)構(gòu)體第一個成員是self,第二個成員是(id)class_getSuperclass(objc_getClass("Son"))退唠,實際上該函數(shù)會輸出Father。然后在Father類查找class方法荤胁,查找不到瞧预,最后在NSObject查到。此時仅政,內(nèi)部使用objc_msgSend(objc_super->receiver, @selector(class))去調(diào)用垢油,與[self class]調(diào)用相同,所以結(jié)果還是Son圆丹。

隱藏參數(shù)self和_cmd

當[receiver message]調(diào)用方法時滩愁,系統(tǒng)會在運行時偷偷地動態(tài)傳入兩個隱藏參數(shù)self和_cmd,之所以稱它們?yōu)殡[藏參數(shù)辫封,是因為在源代碼中沒有聲明和定義這兩個參數(shù)硝枉。至于對于self的描述,上面已經(jīng)解釋非常清楚了倦微,下面我們重點講解_cmd妻味。

_cmd表示當前調(diào)用方法,其實它就是一個方法選擇器SEL欣福。一般用于判斷方法名或在Associated Objects中唯一標識鍵名责球,后面在Associated Objects會講到。

方法解析與消息轉(zhuǎn)發(fā)

[receiver message]調(diào)用方法時,如果在message方法在receiver對象的類繼承體系中沒有找到方法雏逾,那怎么辦嘉裤?一般情況下,程序在運行時就會Crash掉栖博,拋出unrecognized selector sent to …類似這樣的異常信息屑宠。但在拋出異常之前,還有三次機會按以下順序讓你拯救程序笛匙。

Method Resolution

Fast Forwarding

Normal Forwarding

Message Forward from Google

Method Resolution

首先Objective-C在運行時調(diào)用+ resolveInstanceMethod:或+ resolveClassMethod:方法侨把,讓你添加方法的實現(xiàn)。如果你添加方法并返回YES妹孙,那系統(tǒng)在運行時就會重新啟動一次消息發(fā)送的過程秋柄。

舉一個簡單例子,定義一個類Message蠢正,它主要定義一個方法sendMessage骇笔,下面就是它的設計與實現(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對象并調(diào)用sendMessage方法:

- (void)viewDidLoad {? ? [superviewDidLoad];? ? Message *message = [Messagenew];? ? [message sendMessage:@"Sam Lau"];}

控制臺會打印以下信息:

normal way :sendmessage = Sam Lau

但現(xiàn)在我將原來sendMessage方法實現(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;}

控制臺就會打印以下信息:

method resolution way :sendmessage = Sam Lau

注意到上面代碼有這樣一個字符串"v@*嚣崭,它表示方法的參數(shù)和返回值浦夷,詳情請參考Type Encodings

如果resolveInstanceMethod方法返回NO廷臼,運行時就跳轉(zhuǎn)到下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)

Fast Forwarding

如果目標對象實現(xiàn)- forwardingTargetForSelector:方法,系統(tǒng)就會在運行時調(diào)用這個方法,只要這個方法返回的不是nil或self改艇,也會重啟消息發(fā)送的過程,把這消息轉(zhuǎn)發(fā)給其他對象來處理喉誊。否則牛郑,就會繼續(xù)Normal Fowarding

繼續(xù)上面Message類的例子签财,將sendMessage和resolveInstanceMethod方法注釋掉串慰,然后添加forwardingTargetForSelector方法的實現(xiàn):

#pragma mark - Fast Forwarding- (id)forwardingTargetForSelector:(SEL)aSelector{if(aSelector ==@selector(sendMessage:)) {return[MessageForwarding new];? ? }returnnil;}

此時還缺一個轉(zhuǎn)發(fā)消息的類MessageForwarding,這個類的設計與實現(xiàn)如下:

@interfaceMessageForwarding:NSObject- (void)sendMessage:(NSString*)word;@end

@implementationMessageForwarding- (void)sendMessage:(NSString*)word{NSLog(@"fast forwarding way : send message = %@", word);}@end

此時唱蒸,控制臺會打印以下信息:

fast forwarding way :sendmessage = Sam Lau

這里叫Fast邦鲫,是因為這一步不會創(chuàng)建NSInvocation對象,但Normal Forwarding會創(chuàng)建它神汹,所以相對于更快點庆捺。

Normal Forwarding

如果沒有使用Fast Forwarding來消息轉(zhuǎn)發(fā),最后只有使用Normal Forwarding來進行消息轉(zhuǎn)發(fā)屁魏。它首先調(diào)用methodSignatureForSelector:方法來獲取函數(shù)的參數(shù)和返回值疼燥,如果返回為nil,程序會Crash掉蚁堤,并拋出unrecognized selector sent to instance異常信息醉者。如果返回一個函數(shù)簽名但狭,系統(tǒng)就會創(chuàng)建一個NSInvocation對象并調(diào)用-forwardInvocation:方法。

繼續(xù)前面的例子撬即,將forwardingTargetForSelector方法注釋掉立磁,添加methodSignatureForSelector和forwardInvocation方法的實現(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];}}

關于這個例子的示例代碼請到github下載。

三種方法的選擇

Runtime提供三種方式來將原來的方法實現(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

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)

當想使用Category對已存在的類進行擴展時瞧柔,一般只能添加實例方法或類方法漆弄,而不適合添加額外的屬性。雖然可以在Category頭文件中聲明property屬性造锅,但在實現(xiàn)文件中編譯器是無法synthesize任何實例變量和屬性訪問方法撼唾。這時需要自定義屬性訪問方法并且使用Associated Objects來給已存在的類Category添加自定義的屬性。Associated Objects提供三個API來向?qū)ο?b>添加哥蔚、獲取和刪除關聯(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是個枚舉類型倒谷,它可以指定Objc內(nèi)存管理的引用計數(shù)機制肺素。

typedefOBJC_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. */};

下面有個關于NSObject+AssociatedObjectCategory添加屬性associatedObject的示例代碼:

NSObject+AssociatedObject.h

@interfaceNSObject(AssociatedObject)@property(strong,nonatomic)idassociatedObject;@end

NSObject+AssociatedObject.m

@implementationNSObject(AssociatedObject)- (void)setAssociatedObject:(id)associatedObject{? ? objc_setAssociatedObject(self,@selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (id)associatedObject{returnobjc_getAssociatedObject(self, _cmd);}@end

Associated Objects的key要求是唯一并且是常量倍靡,而SEL是滿足這個要求的,所以上面的采用隱藏參數(shù)_cmd作為key塌西。

Method Swizzling

Method Swizzling就是在運行時將一個方法的實現(xiàn)代替為另一個方法的實現(xiàn)筝尾。如果能夠利用好這個技巧捡需,可以寫出簡潔筹淫、有效且維護性更好的代碼∈庀迹可以參考兩篇關于Method Swizzling技巧的文章:

nshipster Method Swizzling

Method Swizzling 和 AOP 實踐

Aspect-Oriented Programming(AOP)

類似記錄日志汰蓉、身份驗證顾孽、緩存等事務非橙艉瘢瑣碎盹沈,與業(yè)務邏輯無關乞封,很多地方都有肃晚,又很難抽象出一個模塊,這種程序設計問題吧碾,業(yè)界給它們起了一個名字叫橫向關注點(Cross-cutting concern)墓卦,AOP作用就是分離橫向關注點(Cross-cutting concern)來提高模塊復用性落剪,它可以在既有的代碼添加一些額外的行為(記錄日志忠怖、身份驗證凡泣、緩存)而無需修改代碼。

危險性

Method Swizzling就像一把瑞士小刀钞翔,如果使用得當布轿,它會有效地解決問題汰扭。但使用不當萝毛,將帶來很多麻煩笆包。在stackoverflow上有人已經(jīng)提出這樣一個問題:What are the Dangers of Method Swizzling in Objective C?庵佣,它的危險性主要體現(xiàn)以下幾個方面:

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é)

雖然在平時項目不是經(jīng)常用到Objective-C的Runtime特性,但當你閱讀一些iOS開源項目時肛根,你就會發(fā)現(xiàn)很多時候都會用到派哲。所以深入理解Objective-C的Runtime數(shù)據(jù)結(jié)構(gòu)芭届、消息轉(zhuǎn)發(fā)機制有助于你更容易地閱讀和學習開源項目喉脖。

擴展閱讀

玉令天下博客的Objective-C Runtime

顧鵬博客的Objective-C Runtime

Associated Objects

Method Swizzling

Method Swizzling 和 AOP 實踐

Objective-C Runtime Reference

What are the Dangers of Method Swizzling in Objective C?

ios程序員6級考試(答案和解釋)




RunLoop

深入理解RunLoop這篇文章寫的很好!

簡介

RunLoop顧名思義谦絮,就是運行循環(huán)的意思层皱。

基本作用:

保持程序的持續(xù)運行

處理App中的各類事件(觸摸事件草冈、定時器事件怎棱、Selector事件)

節(jié)省CPU資源拳恋,提高程序性能:沒有事件時就進行睡眠狀態(tài)

內(nèi)部實現(xiàn):

do-while循環(huán)谬运,在這個循環(huán)內(nèi)部不斷地處理各種任務(Source\Timeer\Observer)

注意點:

一個線程對應一個RunLoop(采用字典存儲梆暖,線程號為key式廷,RunLoop為value)

主線程的RunLoop默認已經(jīng)啟動滑废,子線程的RunLoop需要手動啟動

RunLoop只能選擇一個Mode啟動,如果當前Mode沒有任何Source俺陋、Timer腊状、Observer缴挖,那么就不會進入RunLoop

RunLoop的主要函數(shù)調(diào)用順序為:CFRunLoopRun->CFRunLoopRunSpecific->__CFRunLoopRun

注意特殊情況映屋,事實上棚点,在只有Observer的情況砌梆,也不一定會進入循環(huán)么库,因為源代碼里面只會顯式地檢測兩個東西:Source和Timer(這兩個是主動向RunLoop發(fā)送消息的)诉儒;Observer是被動接收消息的

RunLoop在第一次獲取時創(chuàng)建忱反,在線程結(jié)束時銷毀

RunLoop循環(huán)示意圖:(針對上面的__CFRunLoopRun函數(shù),Mode已經(jīng)判斷非空前提)

圖1

RunLoop循環(huán)示意圖

圖2

接觸過微處理器編程的基本上都知道注竿,在編寫微處理器程序時,我通常會在main函數(shù)中寫一個無限循環(huán)宣谈,然后在這個循環(huán)里面對外部事件進行監(jiān)聽闻丑,比如外部中斷嗦嗡,一些傳感器的數(shù)據(jù)等侥祭,在沒有外部中斷時卑硫,就讓CPU進入低功耗模式。如果接收到了外部中斷硝拧,就恢復到正常模式障陶,對中斷進行處理抱究。

while(1) {// 根據(jù)中斷決定是否切換模式執(zhí)行任務}// 或者for(;;) {}

RunLoop和這個相似鼓寺,也是在線程的main中增加了一個循環(huán):

intmain(intargc,char* argv[]) {BOOLrunning =YES;do{// 執(zhí)行各種任務,處理各種事件// ......}while(running);return0;}

所以線程在這種情況下苦银,便不會退出幔虏。

關于MainRunLoop:

intmain(intargc,char* argv[]) {@autoreleasepool{returnUIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegate class]));? ? }}

在viewDidLoad中設置斷電所计,然后得到以下主線程棧信息:

可以看到,UIApplicationMain內(nèi)部啟動了一個和主線程相關聯(lián)的RunLoop(_CFRunLoopRun)踪栋。在這里也可以推斷夷都,程序進入UIApplicationMain就不會退出了冬阳。我稍微對主函數(shù)進行了如下修改肝陪,并在return語句上打印了斷點:

運行程序后氯窍,并不會在斷點處停下,證實了上面的推斷政供。

上面涉及了一個_CFRunLoopRun函數(shù)鲫骗,接下來說明下iOS中訪問和使用RunLoop的API:

Foundation--NSRunLoop

Core Foundation--CFRunLoopRef(開源)

因為后者是開源的,且前者是在后者上針對OC的封裝术吝,所以一般是對CFRunLoopRef進行研究排苍。

兩套API對應獲取RunLoop對象的方式:

Foundation

[NSRunLoop currentRunLoop]; // 當前runloop

[NSRunLoop mainRunLoop];// 主線程runloop

Core Foundation

CFRunLoopGetCurrent();// 當前runloop

CFRunLoopGetMain();// 主線程runloop

值得注意的是,獲取當前RunLoop都是進行懶加載的彤守,也就是調(diào)用時自動創(chuàng)建線程對應的RunLoop具垫。

RunLoop相關類:

CFRunLoopRef

CFRunLoopModeRef

CFRunLoopSourceRef

CFRunLoopTimerRef

CFRunLoopObserverRef

類之間的關系

以上圖片說明了各個類之間的關系。

CFRunLoopModeRef說明:

代表RunLoop的運行模式起宽,一個RunLoop可以包含多個Mode坯沪,每個Mode可以包含多個Source屏箍、Timer、Observer

每次RunLoop啟動時颖御,只能指定其中一個Mode潘拱,這個Mode就變成了CurrentMode

當啟動RunLoop時,如果所在Mode中沒有Source禽最、Timer川无、Observer,那么將不會進入RunLoop仅叫,會直接結(jié)束

如果要切換Mode惑芭,只能退出Loop遂跟,再重新制定一個Mode進入

系統(tǒng)默認注冊了5個Mode:

NSDefaultRunLoopMode:App的默認Mode凯亮,通常主線程是在這個Mode下運行

UITrackingRunLoopMode:界面跟蹤 Mode假消,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響

UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode啃沪,啟動完成后就不再使用

GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode创千,通常用不到

NSRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode

關于NSRunLoopCommonModes:

一個Mode可以將自己標記為“Common”屬性殿雪,每當 RunLoop 的內(nèi)容發(fā)生變化時,RunLoop會對標記有“Common”屬性的Mode進行相適應的切換河泳,并同步Source/Observer/Timer

在主線程中拆挥,kCFRunLoopDefaultMode 和 UITrackingRunLoopMode這兩個Mode都是被默認標記為“Common”屬性的,從輸出的主線程RunLoop可以查看汉矿。

- 結(jié)合上面兩點奈揍,當使用NSRunLoopCommonModes占位時男翰,會表明使用標記為“Common”屬性的Mode,在一定層度上租冠,可以說是“擁有了兩個Mode”,可以在這兩個Mode中的其中任意一個進行工作

CFRunLoopTimerRef說明:

CFRunLoopTimerRef是基于時間的觸發(fā)器话原,它包含了一個時間長度和一個回調(diào)函數(shù)指針涉馅。當它加入到RunLoop時稚矿,RunLoop會注冊對應的時間點桥爽,當時間點到時钠四,RunLoop會被喚醒以執(zhí)行那個回調(diào)

CFRunLoopTimerRef大部分指的是NSTimer缀去,它受RunLoop的Mode影響

由于NSTimer在RunLoop中處理,所以受其影響較大咏雌,有時可能會不準確栅贴。還有一種定時器是GCD定時器,它并不在RunLoop中坛缕,所以不受其影響捆昏,也就比較精確

接下來說明各種Mode下赚楚,NSTimer的工作情況:

情況1

在對創(chuàng)建的定時器進行模式修改前,scheduledTimerWithTimeInterval創(chuàng)建的定時器只在NSDefaultRunLoopMode模式下可以正常運行骗卜,當滾動UIScroolView時宠页,模式轉(zhuǎn)換成UITrackingRunLoopMode,定時器就失效了寇仓。

修改成NSRunLoopCommonModes后,定時器在兩個模式下都可以正常運行

// 創(chuàng)建的定時器默認添加到當前的RunLoop中(沒有就創(chuàng)建)遍烦,而且是NSDefaultRunLoopMode模式NSTimer*timer = [NSTimerscheduledTimerWithTimeInterval:1.0target:selfselector:@selector(run) userInfo:nilrepeats:YES];// 可以通過以下方法對模型進行修改[[NSRunLoopmainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

情況2

timerWithTimeInterval創(chuàng)建的定時器并沒有手動添加進RunLoop俭嘁,所以需要手動進行添加。當添加為以下模式時服猪,定時器只在UITrackingRunLoopMode模式下進行工作供填,也就是滑動UIScrollView時就會工作,停止滑動時就不工作

如果把UITrackingRunLoopMode換成NSDefaultRunLoopMode罢猪,那么效果就和情況1沒修改Mode前的效果一樣

NSTimer*timer = [NSTimertimerWithTimeInterval:1.0target:selfselector:@selector(run) userInfo:nilrepeats:YES];// 在UITrackingRunLoopMode模式下定時器才會運行[[NSRunLoopmainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

CFRunLoopSourceRef說明:

Source分類

按官方文檔

Port-Based Sources

Custom Input Sources

Cocoa Perform Selector Sources

按照函數(shù)調(diào)用棧

Source0:非基于Port的

Source0本身不能主動觸發(fā)事件近她,只包含了一個回調(diào)函數(shù)指針

Source1:基于Port的,通過內(nèi)核和其他線程通信膳帕,接收粘捎、分發(fā)系統(tǒng)事件

包含了mach_port和一個回調(diào)函數(shù)指針,接收到相關消息后备闲,會分發(fā)給Source0進行處理

CFRunLoopObserverRef說明:

CFRunLoopObserverRef是觀察者晌端,能夠監(jiān)聽RunLoop的狀態(tài)改變

能夠監(jiān)聽的狀態(tài)

typedefCF_OPTIONS(CFOptionFlags, CFRunLoopActivity){? ? ? ? kCFRunLoopEntry = (1UL <<0),// 進入RunLoopkCFRunLoopBeforeTimers = (1UL <<1),//即將處理timerkCFRunLoopBeforeSources = (1UL <<2),//即將處理SourceskCFRunLoopBeforeWaiting = (1UL <<5),//即將進入休眠kCFRunLoopAfterWaiting = (1UL <<6),//即將喚醒kCFRunLoopExit = (1UL <<7),//即將退出RunLoopkCFRunLoopAllActivities =0x0FFFFFFFU//所有活動};

添加監(jiān)聽者步驟

// 創(chuàng)建監(jiān)聽著CFRunLoopObserverRefobserver =CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopBeforeTimers,YES,0, ^(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity) {NSLog(@"%ld", activity);? ? });//? ? [[NSRunLoop currentRunLoop] getCFRunLoop]// 向當前runloop添加監(jiān)聽者CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);// 釋放內(nèi)存CFRelease(observer);

CF的內(nèi)存管理(Core Foundation):

1.凡是帶有Create、Copy恬砂、Retain等字眼的函數(shù)咧纠,創(chuàng)建出來的對象,都需要在最后做一次release

比如CFRunLoopObserverCreate

2.release函數(shù):CFRelease(對象);

自動釋放池釋放的時間和RunLoop的關系:

注意泻骤,這里的自動釋放池指的是主線程的自動釋放池漆羔,我們看不見它的創(chuàng)建和銷毀梧奢。自己手動創(chuàng)建@autoreleasepool {}是根據(jù)代碼塊來的,出了這個代碼塊就釋放了演痒。

App啟動后亲轨,蘋果在主線程 RunLoop 里注冊了兩個 Observer,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()鸟顺。

第一個 Observer 監(jiān)視的事件是 Entry(即將進入Loop)惦蚊,其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush()創(chuàng)建自動釋放池。其 order 是-2147483647讯嫂,優(yōu)先級最高蹦锋,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。

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

在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)曲楚、Timer回調(diào)內(nèi)的厘唾。這些回調(diào)會被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著,所以不會出現(xiàn)內(nèi)存泄漏洞渤,開發(fā)者也不必顯示創(chuàng)建 Pool 了阅嘶。

在自己創(chuàng)建線程時属瓣,需要手動創(chuàng)建自動釋放池AutoreleasePool

綜合上面载迄,可以得到以下結(jié)論:

@autoreleasepool {}內(nèi)部實現(xiàn)

有以下代碼:

intmain(intargc,constchar* argv[]){@autoreleasepool{? ? }return0;}

查看編譯轉(zhuǎn)換后的代碼:

intmain(intargc,constchar* argv[]){/* @autoreleasepool */{ __AtAutoreleasePool __autoreleasepool;? ? }return0;}

__AtAutoreleasePool是什么呢?找到其定義:

struct__AtAutoreleasePool {? __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}? ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}void* atautoreleasepoolobj;};

可以看到__AtAutoreleasePool是一個類:

其構(gòu)造函數(shù)使用objc_autoreleasePoolPush創(chuàng)建了一個線程池抡蛙,并保存給成員變量atautoreleasepoolobj护昧。

其析構(gòu)函數(shù)使用objc_autoreleasePoolPop銷毀了線程池

結(jié)合以上信息,main函數(shù)里面的__autoreleasepool是一個局部變量粗截。當其創(chuàng)建時惋耙,會調(diào)用構(gòu)造函數(shù)創(chuàng)建線程池,出了{}代碼塊時熊昌,局部變量被銷毀绽榛,調(diào)用其析構(gòu)函數(shù)銷毀線程池。

RunLoop實際應用

常駐線程

當創(chuàng)建一個線程婿屹,并且希望它一直存在時灭美,就需要使用到RunLoop,否則線程一執(zhí)行完任務就會停止昂利。

要向線程存在届腐,需要有強指針引用他铁坎,其他的代碼如下:

// 屬性@property(strong,nonatomic)NSThread*thread;// 創(chuàng)建線程_thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(test) object:nil];[_thread start];- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event{// 點擊時使線程_thread執(zhí)行test方法[selfperformSelector:@selector(test) onThread:_thread withObject:nilwaitUntilDone:NO];}//- (void)test{NSLog(@"__test__");}

就單單以上代碼,是不起效果的犁苏,因為線程沒有RunLoop硬萍,執(zhí)行完test后就停止了,無法再讓其執(zhí)行任務(強制start會崩潰)围详。

通過在子線程中給RunLoop添加監(jiān)聽者朴乖,可以了解下performSelector:onThread:內(nèi)部做的事情:

調(diào)用performSelector:onThread: 時,實際上它會創(chuàng)建一個Source0加到對應線程的RunLoop里去助赞,所以寒砖,如果對應的線程沒有RunLoop,這個方法就會失效

// 這句在主線程中調(diào)用// _thread就是下面的線程[selfperformSelector:@selector(run) onThread:_thread withObject:nilwaitUntilDone:NO];

performSelecter:afterDelay:也是一樣的內(nèi)部操作方法嫉拐,只是創(chuàng)建的Timer添加到當前線程的RunLoop中了

// 創(chuàng)建RunLoop即將喚醒監(jiān)聽者CFRunLoopObserverRefobserver =CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopBeforeTimers,YES,0, ^(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity) {// 打印喚醒前的RunLoopNSLog(@"%ld--%@", activity, [NSRunLoopcurrentRunLoop]);? ? });// 向當前runloop添加監(jiān)聽者CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);// 釋放內(nèi)存CFRelease(observer);? ? [selfperformSelector:@selector(setView:) withObject:nilafterDelay:2.0];// 使model不為空[[NSRunLoopcurrentRunLoop] addPort:[NSPortport] forMode:NSDefaultRunLoopMode];? ? [[NSRunLoopcurrentRunLoop] run];

綜合上面的解釋哩都,可以知道performSelector:onThread:沒有起作用,是因為_thread線程內(nèi)部沒有RunLoop婉徘,所以需要在線程內(nèi)部創(chuàng)建RunLoop漠嵌。

創(chuàng)建RunLoop并使對應線程成為常駐線程的常見方式有2:

方式1

向創(chuàng)建的RunLoop添加NSPort(Sources),讓Mode不為空盖呼,RunLoop能進入循環(huán)不會退出

[[NSRunLoop currentRunLoop]addPort:[NSPort port] forMode:NSDefaultRunLoopMode];[[NSRunLoop currentRunLoop]run];

方式2

讓RunLoop一直嘗試運行儒鹿,判斷Mode是否為空,不是為空就進入RunLoop循環(huán)

while(1) {? ? [[NSRunLoopcurrentRunLoop] run];}

AFNetWorking就使用到了常駐線程:

創(chuàng)建常駐線程

+ (void)networkRequestThreadEntryPoint:(id)__unused object {@autoreleasepool{? ? ? ? [[NSThreadcurrentThread] setName:@"AFNetworking"];// 創(chuàng)建RunLoop并向Mode添加NSMachPort几晤,使RunLoop不會退出NSRunLoop*runLoop = [NSRunLoopcurrentRunLoop];? ? ? ? [runLoop addPort:[NSMachPortport] forMode:NSDefaultRunLoopMode];? ? ? ? [runLoop run];? ? }}+ (NSThread*)networkRequestThread {staticNSThread*_networkRequestThread =nil;staticdispatch_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]];? ? }elseif([selfisReady]) {self.state= AFOperationExecutingState;? ? ? ? [selfperformSelector:@selector(operationDidStart) onThread:[[selfclass] networkRequestThread] withObject:nilwaitUntilDone:NOmodes:[self.runLoopModesallObjects]];? ? }? ? [self.lockunlock];}

給子線程開啟定時器

_thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(test) object:nil];[_thread start];// 子線程添加定時器- (void)subTimer{// 默認創(chuàng)建RunLoop并向其model添加timer约炎,所以后續(xù)只需要讓RunLoop run起來即可[NSTimerscheduledTimerWithTimeInterval:1.0target:selfselector:@selector(run) userInfo:nilrepeats:YES];// 貌似source1不為空,source0就不為空//? ? [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];[[NSRunLoopcurrentRunLoop] run];}

讓某些事件(行為蟹瘾、任務)在特定模式下執(zhí)行

比如圖片的設置圾浅,在UIScrollView滾動的情況下,我不希望設置圖片憾朴,等停止?jié)L動了再設置圖片狸捕,可以用以下代碼:

// 圖片只在NSDefaultRunLoopMode模式下會進行設置顯示[self.imageViewperformSelector:@selector(setImage:) withObject:[UIImageimageNamed:@"Snip20150712_39"] afterDelay:2.0inModes:@[NSDefaultRunLoopMode]];

先設置任務在NSDefaultRunLoopMode模式在執(zhí)行,這樣众雷,在滾動使RunLoop進入UITrackingRunLoopMode時灸拍,就不會進行圖片的設置了。

控制定時器在特定模式下執(zhí)行

上文的《CFRunLoopTimerRef說明:》中已經(jīng)指出

添加Observer監(jiān)聽RunLoop的狀態(tài)

監(jiān)聽點擊事件的處理(在所有點擊事件之前做一些事情)

具體步驟在《CFRunLoopObserverRef說明:》中已寫明

GCD定時器

注意:

dispatch_source_t是個類砾省,這點比較特殊

//? ? 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// 類型錯誤鸡岗,可能dispatch_cancel是宏定義,需要的就是方法調(diào)用编兄,而不是變量//? ? ? ? ? ? dispatch_cancel(self.timer);dispatch_source_cancel(_timer);? ? ? ? }? ? });// 定時器默認是停止的轩性,需要手動恢復dispatch_resume(timer);// 需要一個強引用保證timer不被釋放_timer = timer;

最后一點需要說明的是,SDWebImage框架的下載圖片業(yè)務中也使用到了RunLoop翻诉,老確保圖片下載成功后才關閉任務子線程炮姨。

參考文檔

深入理解RunLoop

如果你喜歡這里的專題捌刮, 請直接添加關注哦, 如果你喜歡這里的總結(jié)舒岸, 可以打賞作者哦

一塊錢是你小小的心意绅作,也是作者無悔的付出, 這些總結(jié)的價值講一直持續(xù)給你蛾派,每天

三篇文章俄认,歡迎你來關注哦

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市洪乍,隨后出現(xiàn)的幾起案子眯杏,更是在濱河造成了極大的恐慌,老刑警劉巖壳澳,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岂贩,死亡現(xiàn)場離奇詭異,居然都是意外死亡巷波,警方通過查閱死者的電腦和手機萎津,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抹镊,“玉大人锉屈,你說我怎么就攤上這事】宥” “怎么了颈渊?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長终佛。 經(jīng)常有香客問我俊嗽,道長,這世上最難降的妖魔是什么查蓉? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任乌询,我火速辦了婚禮榜贴,結(jié)果婚禮上豌研,老公的妹妹穿的比我還像新娘。我一直安慰自己唬党,他們只是感情好鹃共,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著驶拱,像睡著了一般霜浴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蓝纲,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天阴孟,我揣著相機與錄音晌纫,去河邊找鬼。 笑死永丝,一個胖子當著我的面吹牛锹漱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播慕嚷,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼哥牍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了喝检?” 一聲冷哼從身側(cè)響起嗅辣,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挠说,沒想到半個月后澡谭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡损俭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年译暂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撩炊。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡外永,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拧咳,到底是詐尸還是另有隱情伯顶,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布骆膝,位于F島的核電站祭衩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏阅签。R本人自食惡果不足惜掐暮,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望政钟。 院中可真熱鬧路克,春花似錦、人聲如沸养交。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碎连。三九已至灰羽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背廉嚼。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工玫镐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怠噪。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓摘悴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親舰绘。 傳聞我的和親對象是個殘疾皇子蹂喻,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容

  • runtime 和 runloop 作為一個程序員進階是必須的,也是非常重要的捂寿, 在面試過程中是經(jīng)常會被問到的恤左, ...
    made_China閱讀 1,210評論 0 7
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉浇借,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,719評論 0 9
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 763評論 0 1
  • 對于從事 iOS 開發(fā)人員來說床佳,所有的人都會答出【runtime 是運行時】什么情況下用runtime?大部分人能...
    夢夜繁星閱讀 3,721評論 7 64
  • 妹妹中考完了天天在家待著导俘,一次視頻聊天的過程中,我突然想到讓她鍛煉一下演講和寫作驳概,再做做卷子復習之前學過的東西赤嚼。一...
    新生千尋閱讀 529評論 8 3