zoukankan      html  css  js  c++  java
  • Method Swizzling

    学习博客:《iOS黑魔法-Method Swizzling》 (这个作者太牛了,写了我一直想知道的类簇的swizz方法)

    前言:

    使用另一份实现来替换原有的方法实现,这道工序叫做“方法调配”,开发者常用此技术向原有实现中添加新功能。

    一、 一般的swizz

    先给要替换的方法的类添加一个Category,然后在Category中的+(void)load方法中添加Method Swizzling方法,我们用来替换的方法也写在这个Category中。由于load类方法是程序运行时这个类被加载到内存中就调用的一个方法,执行比较早,并且不需要我们手动调用。而且这个方法具有唯一性,也就是只会被调用一次,不用担心资源抢夺的问题。

    + (void)load{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
       
            SEL originalSelector = @selector(showSplash:);
            SEL swizzledSelector = @selector(swizzledShowSplash:);
           // 通过class_getInstanceMethod()函数从当前对象中的method list获取method结构体,如果是类方法就使用class_getClassMethod()函数获取。
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
         /**
         *  我们在这里使用class_addMethod()函数对Method Swizzling做了一层验证,如果self没有实现被交换的方法,会导致失败。
         *  而且self没有交换的方法实现,但是父类有这个方法,这样就会调用父类的方法,结果就不是我们想要的结果了。
         *  所以我们在这里通过class_addMethod()的验证,如果self实现了这个方法,class_addMethod()函数将会返回NO,我们就可以对其进行交换了。
         */
    
            BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
            if (didAddMethod) {
                class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
            }else{
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
    - (void)swizzledShowSplash:(NSString *)message { if(message && message.length > 0){ [self swizzledShowSplash]; // 此时swizzledShowSplash已经和showSplash交换了实现,所以这里实际是在调用showSplash } }

     二、Method Swizzling类簇

    按照上面的例子对NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等类进行Method Swizzling来做swizz。发现Method Swizzling根本就不起作用。

    这是因为Method Swizzling对NSArray这些的类簇是不起作用的。因为这些类簇类,其实是一种抽象工厂的设计模式。抽象工厂内部有很多其它继承自当前类的子类,抽象工厂类会根据不同情况,创建不同的抽象对象来进行使用。例如我们调用NSArray的objectAtIndex:方法,这个类会在方法内部判断,内部创建不同抽象类进行操作。

    所以也就是我们对NSArray类进行操作其实只是对父类进行了操作,在NSArray内部会创建其他子类来执行操作,真正执行操作的并不是NSArray自身,所以我们应该对其“真身”进行操作。

    “真身”
    NSArray

    __NSArrayI

    NSMutableArray

    __NSArrayM

    NSDictionary

    __NSDictionaryI

    NSMutableArray

    __NSDictionaryM

    例如:防止NSArray因为调用objectAtIndex:方法,取下标时数组越界导致的崩溃的swizz方法:

    #import "NSArray+CRMArray.h"
    
    @implementation NSArray (CRMArray)
    + (void)load {
        [super load];
        Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
        Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(lxz_objectAtIndex:));
        method_exchangeImplementations(fromMethod, toMethod);
    }
    
    - (id)lxz_objectAtIndex:(NSUInteger)index {
        if (self.count-1 < index) {
            // 这里做一下异常处理,不然都不知道出错了。
            @try {
                return [self lxz_objectAtIndex:index];
            }
            @catch (NSException *exception) {
                // 在崩溃后会打印崩溃信息,方便我们调试。
                NSLog(@"---------- %s Crash Because Method %s  ----------
    ", class_getName(self.class), __func__);
                NSLog(@"%@", [exception callStackSymbols]);
                return nil;
            }
            @finally {}
        } else {
            return [self lxz_objectAtIndex:index];
        }
    }
    @end

     三、使用场景

      通过此方案,开发者可以为那些“完全不知道其具体实现的”黑盒方法添加日志记录功能,这非常有助于调试程序。然而,此做法只在调试程序时用,很少有人在调试程序之外的场合用上述“方法调配技术”来永久改动某个类的功能。

  • 相关阅读:
    MapReduce-shuffle过程详解
    YARN中的失败分析
    HBase协处理器的使用(添加Solr二级索引)
    Flume具体应用(多案例)
    Flume架构及运行机制
    python Cmd实例之网络爬虫应用
    mongodb3 权限认证问题总结
    webpack配置
    apt软件包管理
    python笔记之编程风格大比拼
  • 原文地址:https://www.cnblogs.com/Xylophone/p/5983749.html
Copyright © 2011-2022 走看看