美文网首页iOS开发
Objective-C caching and Method S

Objective-C caching and Method S

作者: zidaneno5 | 来源:发表于2016-08-11 21:16 被阅读61次

    翻译自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_classcache属性中的一个小型散列表来工作的(类型是struct objc_cache)。这个散列表包含最近使用过的Method对象的指针,这个指针与从struct objc_classstruct 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:方法。因此当看到代码看上去是递归调用时,实际是调用了原来的函数。
    这个实现的最大瑕疵是,如果你想替换一个实际在父类里的实现的方法时,方法注入会影响父类的所有实例,而不是只影响子类。有些可能的方法,比如动态添加一个新类,用它来充当旧的类,但现在还没有人能实现。

    相关文章

      网友评论

        本文标题:Objective-C caching and Method S

        本文链接:https://www.haomeiwen.com/subject/sesssttx.html