翻译自http://kevin.sb.org/2006/11/16/objective-c-caching-and-method-swizzling/
本文链接:http://www.jianshu.com/p/c15d4690568e
Objective-C使用方法缓存机制,针对某个类中被一个或者多个对象反复调用的方法进行优化。上述情况很常见,举例来说,如果你遍历某个数组,对每个元素执行相同的操作,你会在一堆同一Class的不同实例上调用同一个方法。实际上-[NSEnumerator nextObject]
方法本身也被缓存了。
方法缓存存在一个潜在问题的原因是,没人知道它是怎么运作的,也没人知道方法缓存会不会对方法注入(Method Swizzling
)造成影响。
然而,简单的来说答案是NO。
Objective-C的方法缓存通过存储在每一个struct objc_class
的cache
属性中的一个小型散列表来工作的(类型是struct objc_cache
)。这个散列表包含最近使用过的Method
对象的指针,这个指针与从struct objc_class
的struct objc_method_list
中找到的Method
对象是相同的。重要的一点是缓存的Method
对象实际上可以是该类某一个父类的方法。这就使得缓存十分有用,如果一个方法被缓存了,方法分发系统就不必每次都在各个类的层级中寻找这个方法。
Method Swizzling
不需要担心方法缓存的原因是,缓存的Method
对象跟每个类里struct objc_method_list
中的是同一个。当Method Swizzling
修改了Method
对象,缓存也同时修改了,所以任何之后的缓存命中都会指向这个修改后的Method
。
如果你因为某些原因想刷新类的缓存(我想不到为啥要这么做),只需要在你的文件里添加:
void _objc_flush_caches(Class cls);
并调用它,传入你想刷新缓存的类。这个特殊的方法也会同时刷新该类所有父类的缓存。
Method Swizzling
如果你想在你的APP里面用Method Swizzling(or Input Manager, or SIMBL plugin, or……),我认为下面的实现是最佳实践。
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);
}
这段特殊的实现交换了两个方法的method_imp
指针,因此调用第一个方法会调用第二个方法的实现,反之亦然。优雅的部分是,你要想调用被修改方法的原实现,只需要调用你的新方法名就好了。接下来修改自SafariSource的代码,示范了这一点。
- (void) mySetString:(NSString *)string {
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
if ([defs boolForKey:SafariSourceEnabled]) {
// do stuff here
} else {
[self mySetString:string];
}
}
当任何代码试图调用这个类的setString:
方法,注入的方法mySetString:
就会被调用。由于两个方法进行了调换,调用mySetString:
实际会调用原来的setString:
方法。因此当看到代码看上去是递归调用时,实际是调用了原来的函数。
这个实现的最大瑕疵是,如果你想替换一个实际在父类里的实现的方法时,方法注入会影响父类的所有实例,而不是只影响子类。有些可能的方法,比如动态添加一个新类,用它来充当旧的类,但现在还没有人能实现。