翻譯自http://kevin.sb.org/2006/11/16/objective-c-caching-and-method-swizzling/
本文鏈接:http://www.reibang.com/p/c15d4690568e
Objective-C使用方法緩存機制道逗,針對某個類中被一個或者多個對象反復(fù)調(diào)用的方法進行優(yōu)化。上述情況很常見献烦,舉例來說滓窍,如果你遍歷某個數(shù)組,對每個元素執(zhí)行相同的操作巩那,你會在一堆同一Class的不同實例上調(diào)用同一個方法吏夯。實際上-[NSEnumerator nextObject]
方法本身也被緩存了。
方法緩存存在一個潛在問題的原因是即横,沒人知道它是怎么運作的噪生,也沒人知道方法緩存會不會對方法注入(Method Swizzling
)造成影響。
然而东囚,簡單的來說答案是NO跺嗽。
Objective-C的方法緩存通過存儲在每一個struct objc_class
的cache
屬性中的一個小型散列表來工作的(類型是struct objc_cache
)。這個散列表包含最近使用過的Method
對象的指針,這個指針與從struct objc_class
的struct objc_method_list
中找到的Method
對象是相同的桨嫁。重要的一點是緩存的Method
對象實際上可以是該類某一個父類的方法植兰。這就使得緩存十分有用,如果一個方法被緩存了瞧甩,方法分發(fā)系統(tǒng)就不必每次都在各個類的層級中尋找這個方法钉跷。
Method Swizzling
不需要擔(dān)心方法緩存的原因是,緩存的Method
對象跟每個類里struct objc_method_list
中的是同一個肚逸。當(dāng)Method Swizzling
修改了Method
對象爷辙,緩存也同時修改了,所以任何之后的緩存命中都會指向這個修改后的Method
朦促。
如果你因為某些原因想刷新類的緩存(我想不到為啥要這么做)膝晾,只需要在你的文件里添加:
void _objc_flush_caches(Class cls);
并調(diào)用它,傳入你想刷新緩存的類务冕。這個特殊的方法也會同時刷新該類所有父類的緩存血当。
Method Swizzling
如果你想在你的APP里面用Method Swizzling(or Input Manager, or SIMBL plugin, or……),我認為下面的實現(xiàn)是最佳實踐禀忆。
void PerformSwizzle(Class aClass, SEL orig_sel, SEL alt_sel, BOOL forInstance)
{
// First, make sure the class isn't nil
if (aClass != nil) {
Method orig_method = nil, alt_method = nil;
// Next, look for the methods
if (forInstance) {
orig_method = class_getInstanceMethod(aClass, orig_sel);
alt_method = class_getInstanceMethod(aClass, alt_sel);
} else {
orig_method = class_getClassMethod(aClass, orig_sel);
alt_method = class_getClassMethod(aClass, alt_sel);
}
// If both are found, swizzle them
if ((orig_method != nil) && (alt_method != nil)) {
IMP temp;
temp = orig_method->method_imp;
orig_method->method_imp = alt_method->method_imp;
alt_method->method_imp = temp;
} else {
#if DEBUG
NSLog(@"PerformSwizzle Error: Original %@, Alternate %@",(orig_method == nil)?@" not found":@" found",(alt_method == nil)?@" not found":@" found");
#endif
}
} else {
#if DEBUG
NSLog(@"PerformSwizzle Error: Class not found");
#endif
}
}
void MethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel)
{
PerformSwizzle(aClass, orig_sel, alt_sel, YES);
}
void ClassMethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel)
{
PerformSwizzle(aClass, orig_sel, alt_sel, NO);
}
這段特殊的實現(xiàn)交換了兩個方法的method_imp
指針臊旭,因此調(diào)用第一個方法會調(diào)用第二個方法的實現(xiàn),反之亦然箩退。優(yōu)雅的部分是离熏,你要想調(diào)用被修改方法的原實現(xiàn),只需要調(diào)用你的新方法名就好了戴涝。接下來修改自SafariSource的代碼滋戳,示范了這一點。
- (void) mySetString:(NSString *)string {
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
if ([defs boolForKey:SafariSourceEnabled]) {
// do stuff here
} else {
[self mySetString:string];
}
}
當(dāng)任何代碼試圖調(diào)用這個類的setString:
方法啥刻,注入的方法mySetString:
就會被調(diào)用奸鸯。由于兩個方法進行了調(diào)換,調(diào)用mySetString:
實際會調(diào)用原來的setString:
方法可帽。因此當(dāng)看到代碼看上去是遞歸調(diào)用時娄涩,實際是調(diào)用了原來的函數(shù)。
這個實現(xiàn)的最大瑕疵是映跟,如果你想替換一個實際在父類里的實現(xiàn)的方法時钝满,方法注入會影響父類的所有實例,而不是只影響子類申窘。有些可能的方法,比如動態(tài)添加一個新類孔轴,用它來充當(dāng)舊的類剃法,但現(xiàn)在還沒有人能實現(xiàn)。