前段日子,我又看了一遍sunnyxx的一段有關(guān)runtime的分享會(huì)視頻(不要吐槽AV畫(huà)質(zhì)),結(jié)合這幾年在印象筆記里的各種碎片以及看的書(shū),為自己進(jìn)行一個(gè)知識(shí)的整合和梳理。
簡(jiǎn)述
Runtime 又叫運(yùn)行時(shí),是一套底層的,由C語(yǔ)言和匯編實(shí)現(xiàn)的API,是 iOS 系統(tǒng)的核心之一??梢哉f(shuō),Objective-C = C + runtime。C語(yǔ)言使用的是靜態(tài)綁定(static binding),也就是說(shuō),在編譯期的時(shí)候就能覺(jué)醒運(yùn)行時(shí)的應(yīng)該調(diào)用的函數(shù)。而因?yàn)閞untime的關(guān)系,Objective-C會(huì)在運(yùn)行的死后才會(huì)決定調(diào)用那個(gè)函數(shù)。
我這里準(zhǔn)備把它分為四部分:
- Runtime的類和對(duì)象
- Runtime的消息機(jī)制
- Runtime的關(guān)聯(lián)對(duì)象
- Runtime的方法替換
Runtime的類和對(duì)象
Class 和 id
Objective-C(為了方便,下面用OC代替)的類是由Class來(lái)表示的,實(shí)際上是一個(gè)objc_class的指針,而對(duì)象,則是objc_object:
struct objc_class {
struct objc_class *isa;
};
struct objc_object {
struct objc_class *isa;
};
typedef struct objc_class *Class; //類 (class object)
typedef struct objc_object *id; //對(duì)象 (instance of class)
沒(méi)個(gè)結(jié)構(gòu)體的收個(gè)成員是Class類變量,定義了所屬的類。
接下來(lái)是objc_class的定義:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
這里有幾個(gè)字段我們要了解的:
- isa:,它指向metaClass(元類),我們會(huì)在后面介紹它。
- super_class:指向該類的父類,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy),則super_class為NULL,我們也把它稱之為
元類。 - cache:用于緩存最近使用的方法。一個(gè)接收者對(duì)象接收到一個(gè)消息時(shí),它會(huì)根據(jù)isa指針去查找能夠響應(yīng)這個(gè)消息的對(duì)象。在實(shí)際使用中,這個(gè)對(duì)象只有一部分方法是常用的,很多方法其實(shí)很少用或者根本用不上。這種情況下,如果每次消息來(lái)時(shí),我們都是methodLists中遍歷一遍,性能勢(shì)必很差。這時(shí),cache就派上用場(chǎng)了。在我們每次調(diào)用過(guò)一個(gè)方法后,這個(gè)方法就會(huì)被緩存到cache列表中,下次調(diào)用的時(shí)候runtime就會(huì)優(yōu)先去cache中查找,如果cache沒(méi)有,才去methodLists中查找方法。這樣,對(duì)于那些經(jīng)常用到的方法的調(diào)用,但提高了調(diào)用的效率。
-
version:我們可以使用這個(gè)字段來(lái)提供類的版本信息。這對(duì)于對(duì)象的序列化非常有用,它可是讓我們識(shí)別出不同類定義版本中實(shí)例變量布局的改變。
圖中實(shí)線是 super_class指針,虛線是isa指針
1.Root class (class)其實(shí)就是NSObject,NSObject是沒(méi)有超類的,所以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)。
- Root class(class)中保存實(shí)例方法(-方法)并在方法列表中查找,Root class(meta)中保存類(+方法)并在方法列表中查找。
關(guān)于元類,更多具體可以研究這篇文章What is a meta-class in Objective-C?
至于第三條形成閉環(huán)的原因,也就是Root class(meta)的super_class是Root class(class)。我猜測(cè)是因?yàn)閞untime機(jī)制需要一個(gè)最終的類去存儲(chǔ)、查找方法。蘋果講大多數(shù)的類的最終指向了NSObject已解決這個(gè)問(wèn)題,元類并不處理實(shí)例方法。
這里有個(gè)題:
下面代碼會(huì)怎么樣?
@interface NSObject (Sark)
+(void)foo;
@implementation NSObject (Sark)
- (void)foo {
NSLog(@"IMP:-[NSObject (Sark) foo]");
}
@end
測(cè)試代碼
[NSObject foo];
[[NSObject new]foo];
答案是會(huì)輸出兩個(gè)相同的結(jié)果。在調(diào)用[NSObject foo]的時(shí)候,會(huì)先在NSObject的meta-class中去查找foo方法的IMP,未找到,繼續(xù)在superClass中去查找,NSObject的meta-class的superClass就是本身NSObject,于是又回到NSObject的類方法中查找foo方法,于是乎找到了,執(zhí)行foo方法。
在調(diào)用[[NSObject new] foo]的時(shí)候,會(huì)先生成一個(gè)NSObject的對(duì)象,用這個(gè)NSObject實(shí)例對(duì)象再去調(diào)用foo方法的時(shí)候,會(huì)去NSObject的實(shí)例方法里面去查找,找到,于是也會(huì)執(zhí)行foo方法。
查詢類型信息
在NSObject中,查詢類型信息有兩個(gè)方法:
//判斷對(duì)象是否為某個(gè)特定的實(shí)例。
- (BOOL)isKindOfClass:(Class)aClass;
//判斷對(duì)象是否為某類或其派生類的實(shí)例。
- (BOOL)isMemberOfClass:(Class)aClass;
這里的查詢方法使用isa指針獲取對(duì)象所屬的類,然后通過(guò)super_class指針在繼承體系中上溯。
這里有個(gè)題:
@interface Sark : NSObject
@end
@implementation Sark
@end
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);
答案是:YES,NO,NO,NO
我簡(jiǎn)單的說(shuō)一下這個(gè)幾個(gè)方法的查找流程。
- res1
[NSObject class]執(zhí)行完之后調(diào)用isKindOfClass,第一次判斷先判斷NSObject 和 NSObject的meta class是否相等,之前講到meta class的時(shí)候放了一張很詳細(xì)的圖,從圖上我們也可以看出,NSObject的meta class與本身不等。接著第二次循環(huán)判斷NSObject與meta class的superclass是否相等。還是從那張圖上面我們可以看到:Root class(meta) 的superclass 就是 Root class(class),也就是NSObject本身。所以第二次循環(huán)相等。 - res2
isa 指向 NSObject 的 Meta Class,所以和 NSObject Class不相等。 - res3
Sark class]執(zhí)行完之后調(diào)用isKindOfClass,第一次for循環(huán),Sark的Meta Class與[Sark class]不等,第二次for循環(huán),Sark Meta Class的super class 指向的是 NSObject Meta Class, 和 Sark Class不相等。第三次for循環(huán),NSObject Meta Class的super class指向的是NSObject Class,和 Sark Class 不相等。第四次循環(huán),NSObject Class 的super class 指向 nil, 和 Sark Class不相等。第四次循環(huán)之后,退出循環(huán)。 - res4
isa指向Sark的Meta Class,和Sark Class也不等。
Runtime的消息機(jī)制
消息發(fā)送
在Objective-C上,調(diào)用任何方法實(shí)際上都是在傳遞消息。有關(guān)消息機(jī)制的原理,大家可以看Objective-C 消息發(fā)送與轉(zhuǎn)發(fā)機(jī)制原理這篇文章。
id returnValue = [someObject messageName:parameter];
編譯器會(huì)把它轉(zhuǎn)化為
id returnValue = objc_msgSend(someObject, @selector(messageName:),parameter);
objc_msgSend會(huì)依據(jù)接收者與選擇子的類型來(lái)調(diào)用適當(dāng)?shù)姆椒?。該方法?huì)在接受者所屬的類中搜尋方法列表,如果能找到相關(guān)方法就去執(zhí)行相關(guān)方法。若是找不到,就沿著體系上溯,直到找到方法。加入最終還是找不到方法,就要去執(zhí)行消息轉(zhuǎn)發(fā)。
在每個(gè)類里面,會(huì)講成功匹配的方法緩存起來(lái),若是稍后還向該類發(fā)送相同的消息,那么就會(huì)加速執(zhí)行了。
在每個(gè)類里面,會(huì)有一個(gè)方法表,selector則是查找這個(gè)表的鍵。objc_msgSend正式通過(guò)這張表格來(lái)尋找應(yīng)該執(zhí)行的方法并去實(shí)現(xiàn)了。注意,這里使用了尾調(diào)用優(yōu)化。
如果某函數(shù)的最后一項(xiàng)操作是調(diào)用另外一個(gè)函數(shù),那么就會(huì)調(diào)用這個(gè)方法。編譯器會(huì)生成調(diào)轉(zhuǎn)至另一函數(shù)所需的指令碼,而且不會(huì)向調(diào)用堆棧中推入新的“棧幀”。這么做法非常關(guān)鍵,如果不這么做的話,會(huì)過(guò)早的發(fā)生棧溢出。
消息發(fā)送的流程,我簡(jiǎn)單的歸納了一下:
執(zhí)行objc_msgSend之后,先檢查@selector方法時(shí)候?yàn)閚il。若是,則直接返回,若無(wú),下一步;
接著在緩存中查找是否有相關(guān)方法,若有,則執(zhí)行方法;若無(wú),則下一步;
然后在本類中的方法列表中查找是否有相關(guān)方法。若有,則執(zhí)行,并加入緩存中;若無(wú),則沿著父類上溯;
若是最終仍為找到方法,則執(zhí)行消息轉(zhuǎn)發(fā)。
消息轉(zhuǎn)發(fā)
在編譯期間向類發(fā)送了其無(wú)法解讀的消息并不會(huì)報(bào)錯(cuò),因?yàn)樵谶\(yùn)行期可以繼續(xù)向類中添加方法,所以編譯器會(huì)在編譯時(shí)還無(wú)法確定類中到底會(huì)不會(huì)有某個(gè)方法實(shí)現(xiàn)。當(dāng)對(duì)象收到了無(wú)法解讀的消息時(shí),就會(huì)啟動(dòng)“消息轉(zhuǎn)發(fā)”機(jī)制。
消息轉(zhuǎn)發(fā)分為兩大階段。第一階段先征詢接受者,所屬的類,看其是否能動(dòng)態(tài)添加方法,以處理這個(gè)未知的選擇子,這叫做動(dòng)態(tài)方法解析。第二階段涉及“完整的消息轉(zhuǎn)發(fā)機(jī)制”。如果運(yùn)行期系統(tǒng)已經(jīng)把第一階段執(zhí)行完了,那么接受者自己就無(wú)法再以動(dòng)態(tài)新增方法的手段來(lái)響應(yīng)包含該選擇子的消息了。此時(shí),運(yùn)行期系統(tǒng)會(huì)請(qǐng)求接受者以其他手段來(lái)處理與消息相關(guān)的方法調(diào)用。這里又分兩步,首先,請(qǐng)接受者看看有沒(méi)有其他對(duì)象能處理這條消息。若有,則運(yùn)行期系統(tǒng)會(huì)把消息轉(zhuǎn)給那個(gè)對(duì)象。若沒(méi)有備用的接受者,則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制,運(yùn)行期系統(tǒng)會(huì)把與消息相關(guān)的全部細(xì)節(jié)都封裝到NSInvocation當(dāng)中,再給接受者最后一次機(jī)會(huì),令其設(shè)法解決當(dāng)前還未處理的這條消息。
動(dòng)態(tài)方法解析
對(duì)象在收到無(wú)法解讀的消息后,先調(diào)用下述方法:
//實(shí)例方法
+ (BOOL)resolveInstanceMethod:(SEL)selector;
//類方法
+ (BOOL)resolveClassMethod:(SEL)selector;
不過(guò)使用該方法的前提是我們已經(jīng)實(shí)現(xiàn)了該”處理方法”,只需要在運(yùn)行時(shí)通過(guò)class_addMethod函數(shù)動(dòng)態(tài)添加到類里面就可以了。
備用接收者
如果在上一步無(wú)法處理消息,則會(huì)繼續(xù)調(diào)以下方法:
- (id)forwardingTargetForSelector:(SEL)aSelector
如果一個(gè)對(duì)象實(shí)現(xiàn)了這個(gè)方法,并返回一個(gè)非nil的結(jié)果,則這個(gè)對(duì)象會(huì)作為消息的新接收者,且消息會(huì)被分發(fā)到這個(gè)對(duì)象。當(dāng)然這個(gè)對(duì)象不能是self自身,否則就是出現(xiàn)無(wú)限循環(huán)。當(dāng)然,如果我們沒(méi)有指定相應(yīng)的對(duì)象來(lái)處理aSelector,則應(yīng)該調(diào)用父類的實(shí)現(xiàn)來(lái)返回結(jié)果。
完整消息轉(zhuǎn)發(fā)
如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了。此時(shí)會(huì)調(diào)用以下方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation
運(yùn)行時(shí)系統(tǒng)會(huì)在這一步給消息接收者最后一次機(jī)會(huì)將消息轉(zhuǎn)發(fā)給其它對(duì)象。對(duì)象會(huì)創(chuàng)建一個(gè)表示消息的NSInvocation對(duì)象,把與尚未處理的消息有關(guān)的全部細(xì)節(jié)都封裝在anInvocation中,包括selector,目標(biāo)(target)和參數(shù)。我們可以在forwardInvocation方法中選擇將消息轉(zhuǎn)發(fā)給其它對(duì)象,直到NSObject。NSObject的forwardInvocation:方法實(shí)現(xiàn)只是簡(jiǎn)單調(diào)用了doesNotRecognizeSelector:方法,它不會(huì)轉(zhuǎn)發(fā)任何消息,而是直接拋出異常。
Runtime的關(guān)聯(lián)對(duì)象
有時(shí)需要在對(duì)象中存放相關(guān)信息,這是我們通常對(duì)從對(duì)象所屬的類中繼承一個(gè)子類,然后改用這個(gè)子類對(duì)象。然而有時(shí)候我們無(wú)法這么做,這時(shí)候就要使用關(guān)聯(lián)對(duì)象了。
| 關(guān)聯(lián)類型 | 等效的@property屬性 |
|---|---|
| OBJC_ASSOCIATION_ASSIGN | assign |
| OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic,retain |
| OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic,copy |
| OBJC_ASSOCIATION_RETAIN | retain |
| OBJC_ASSOCIATION_COPY | copy |
下列方法可以管理關(guān)聯(lián)對(duì)象:
- void objc_setAssociatedObject(id object,void *key,id value,objc_AssociationPolicy policy)
此方法以給定的鍵和策略為某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象值- id objc_getAssociatedObject(id object,void *key)
此方法根據(jù)給定的鍵和策略為某對(duì)象中獲取相應(yīng)的關(guān)聯(lián)對(duì)象值- void objc_removeAssicuatedObjects(id object)
此方法移除指定對(duì)象的全部關(guān)聯(lián)對(duì)象
在設(shè)置關(guān)聯(lián)對(duì)象值時(shí),通常使用靜態(tài)全局變量做鍵
使用場(chǎng)景
1.為現(xiàn)有的類添加私有變量
2.為現(xiàn)有的類添加公有屬性
3.為KVO創(chuàng)建一個(gè)關(guān)聯(lián)的觀察者。
Runtime的方法替換
關(guān)于這個(gè),我們可以看Mattt Thompson發(fā)表于的Method Swizzling一文。
這個(gè)方法可能是我們接觸到Runtime最多的一個(gè)東西了。它可以通過(guò)Runtime的API實(shí)現(xiàn)更改任意的方法,理論上可以在運(yùn)行時(shí)通過(guò)類名/方法名hook到任何 OC 方法,替換任何類的實(shí)現(xiàn)以及新增任意類。實(shí)現(xiàn)的最多的就是關(guān)于AOP埋點(diǎn)的方法了。
我們?cè)诮o定的選擇子名稱相對(duì)的方法在運(yùn)行期改變,這種方法叫“方法調(diào)配”(method swizzling)。
類的方法列表會(huì)把選擇子的名稱映射到相關(guān)的方法實(shí)現(xiàn)上,是的“動(dòng)態(tài)消息派發(fā)系統(tǒng)”能夠據(jù)此找到應(yīng)該調(diào)用的方法。這些方法以函數(shù)指針的方法表示,這種指針叫做IMP,原型如下:
id (*IMP)(id,SEL,...)
我們要實(shí)現(xiàn)方法互換,需要以下方法
//交換方法實(shí)現(xiàn)
void method_exchangeImplementations(Method m1,Method m2)
//取出對(duì)應(yīng)方法
Method class_getInstanceMethod(Class aClass,SEL aSelector)
看起來(lái)沒(méi)有什么用處,但是結(jié)合添加方法和category,就可以達(dá)到讓人意想不到的效果。
在category中添加一個(gè)方法,與原本的方法互換,就會(huì)達(dá)到調(diào)用的效果。
注意
Swizzling應(yīng)該總是在+load中執(zhí)行
在Objective-C中,運(yùn)行時(shí)會(huì)自動(dòng)調(diào)用每個(gè)類的兩個(gè)方法。+load會(huì)在類初始加載時(shí)調(diào)用,+initialize會(huì)在第一次調(diào)用類的類方法或?qū)嵗椒ㄖ氨徽{(diào)用。這兩個(gè)方法是可選的,且只有在實(shí)現(xiàn)了它們時(shí)才會(huì)被調(diào)用。由于method swizzling會(huì)影響到類的全局狀態(tài),因此要盡量避免在并發(fā)處理中出現(xiàn)競(jìng)爭(zhēng)的情況。+load能保證在類的初始化過(guò)程中被加載,并保證這種改變應(yīng)用級(jí)別的行為的一致性。相比之下,+initialize在其執(zhí)行時(shí)不提供這種保證–事實(shí)上,如果在應(yīng)用中沒(méi)為給這個(gè)類發(fā)送消息,則它可能永遠(yuǎn)不會(huì)被調(diào)用。
Swizzling應(yīng)該總是在dispatch_once中執(zhí)行
與上面相同,因?yàn)?code>swizzling會(huì)改變?nèi)譅顟B(tài),所以我們需要在運(yùn)行時(shí)采取一些預(yù)防措施。原子性就是這樣一種措施,它確保代碼只被執(zhí)行一次,不管有多少個(gè)線程。GCD的dispatch_once可以確保這種行為,我們應(yīng)該將其作為method swizzling的最佳實(shí)踐。
實(shí)踐
這里的實(shí)踐太多了,我簡(jiǎn)單的介紹幾個(gè):
1.崩潰阻攔及統(tǒng)計(jì)
比如說(shuō)數(shù)組越界,button的點(diǎn)擊方法未實(shí)現(xiàn)等等,我們可以使用關(guān)聯(lián)對(duì)象和消息轉(zhuǎn)發(fā)等功能。為了防止數(shù)組越界,我們可以在分類中替換掉objectAtIndex方法,并做出保護(hù)處理;在button的點(diǎn)擊方法未實(shí)現(xiàn)時(shí),在調(diào)用- (id)forwardingTargetForSelector:(SEL)aSelector方法做出報(bào)警。
2.兼容版本
總所周知,我們經(jīng)常會(huì)遇到用戶手機(jī)版本過(guò)低導(dǎo)致的新方法不兼容,我們往往還要在代碼中加上版本判斷。
這里我們依然可以使用- (id)forwardingTargetForSelector:(SEL)aSelector方法,判斷在新方法未實(shí)現(xiàn)是,直接調(diào)用老版本方法。
3.模擬多繼承
在上圖中,一個(gè)對(duì)象對(duì)一個(gè)消息做出回應(yīng),類似于另一個(gè)對(duì)象中的方法借過(guò)來(lái)或是“繼承”過(guò)來(lái)一樣。 在圖中,warrior實(shí)例轉(zhuǎn)發(fā)了一個(gè)negotiate消息到Diplomat實(shí)例中,執(zhí)行Diplomat中的negotiate方法,結(jié)果看起來(lái)像是warrior實(shí)例執(zhí)行了一個(gè)和Diplomat實(shí)例一樣的negotiate方法,其實(shí)執(zhí)行者還是Diplomat實(shí)例。
消息轉(zhuǎn)發(fā)提供了許多類似于多繼承的特性,但是他們之間有一個(gè)很大的不同:
多繼承:合并了不同的行為特征在一個(gè)單獨(dú)的對(duì)象中,會(huì)得到一個(gè)重量級(jí)多層面的對(duì)象。
消息轉(zhuǎn)發(fā):將各個(gè)功能分散到不同的對(duì)象中,得到的一些輕量級(jí)的對(duì)象,這些對(duì)象通過(guò)消息通過(guò)消息轉(zhuǎn)發(fā)聯(lián)合起來(lái)。
這里值得說(shuō)明的一點(diǎn)是,即使我們利用轉(zhuǎn)發(fā)消息來(lái)實(shí)現(xiàn)了“假”繼承,但是NSObject類還是會(huì)將兩者區(qū)分開(kāi)。像respondsToSelector:和 isKindOfClass:這類方法只會(huì)考慮繼承體系,不會(huì)考慮轉(zhuǎn)發(fā)鏈。
4.給分類添加屬性
這個(gè)就不多說(shuō)了,算的上我們最精彩用到的方法了
5.給UIControl添加方法
我們可以利用這個(gè)方法給button添加block以實(shí)現(xiàn)類似于RAC的效果。
#import <UIKit/UIKit.h>
#import <objc/runtime.h> // 導(dǎo)入頭文件
// 聲明一個(gè)button點(diǎn)擊事件的回調(diào)block
typedef void(^ButtonClickCallBack)(UIButton *button);
@interface UIButton (Handle)
// 為UIButton增加的回調(diào)方法
- (void)handleClickCallBack:(ButtonClickCallBack)callBack;
@end
#import "UIButton+Handle.h"
// 聲明一個(gè)靜態(tài)的索引key,用于獲取被關(guān)聯(lián)對(duì)象的值
static char *buttonClickKey;
@implementation UIButton (Handle)
- (void)handleClickCallBack:(ButtonClickCallBack)callBack {
// 將button的實(shí)例與回調(diào)的block通過(guò)索引key進(jìn)行關(guān)聯(lián):
objc_setAssociatedObject(self, &buttonClickKey, callBack, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 設(shè)置button執(zhí)行的方法
[self addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonClicked {
// 通過(guò)靜態(tài)的索引key,獲取被關(guān)聯(lián)對(duì)象(這里就是回調(diào)的block)
ButtonClickCallBack callBack = objc_getAssociatedObject(self, &buttonClickKey);
if (callBack) {
callBack(self);
}
}
@end
我們利用這個(gè)方法給button添加了一個(gè)block。有名的BlockKit就是用相關(guān)方法實(shí)現(xiàn)的。
6.SDWebImage中設(shè)置緩存
- (SDOperationsDictionary *)sd_operationDictionary {
@synchronized(self) {
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
if (operations) {
return operations;
}
operations = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
}
7.KVO&KVC
Automatic key-value observing is implemented using a technique called isa-swizzling.
The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.
You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.
這個(gè)不多說(shuō)了,篇幅不夠,不過(guò)相關(guān)資料很多。
8.埋點(diǎn)
這個(gè)算的上重中之重了。
我們可以通過(guò)添加一個(gè)分類,交換掉一些我們想要了解的東西。比如果didAppear,button的touch,tableview的點(diǎn)擊等等方法。通過(guò)交換,我們將數(shù)據(jù)保存下來(lái),并發(fā)送給后臺(tái)。
9.AOP
有個(gè)很有名的第三方庫(kù)Aspects,實(shí)現(xiàn)了AOP。我們可以利用這個(gè),處理一些散落在app各處,但又必須處理的一些統(tǒng)一方法,比如說(shuō)身份驗(yàn)證,header。當(dāng)然,我們也可以用它來(lái)埋點(diǎn)。
10.字典模型互換
1.調(diào)用 class_getProperty 方法獲取當(dāng)前 Model 的所有屬性。
2.調(diào)用 property_copyAttributeList 獲取屬性列表。
3.根據(jù)屬性名稱生成 setter 方法。
4.使用 objc_msgSend 調(diào)用 setter 方法為 Model 的屬性賦值(或者 KVC)