美文网首页
iOS黑魔法-Method Swizzling

iOS黑魔法-Method Swizzling

作者: 塞北孤雁 | 来源:发表于2018-03-10 22:00 被阅读0次

    需求: 本身项目已经很大,要新增页面访问统计功能。

    Method Swizzling原理:Method Swizzling是发生在运行时的,主要是将两个method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有这段Method Swizzing代码执行完毕之后互换才起作用。

    Method Swizzling使用:在实现Method Swizzling时,核心代码主要就是一个runtime的C语言API:

    #import "UIViewController+swizzling.h"
    #import <objc/runtime.h>
    @implementation UIViewController (swizzling)
    
    + (void)load {
        // 通过class_getInstanceMethod()函数从当前对象中的method list获取method结构体,如果是类方法就使用class_getClassMethod()函数获取。
        Method fromMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
        Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingViewDidLoad));
        /**
         *  我们在这里使用class_addMethod()函数对Method Swizzling做了一层验证,如果self没有实现被交换的方法,会导致失败。
         *  而且self没有交换的方法实现,但是父类有这个方法,这样就会调用父类的方法,结果就不是我们想要的结果了。
         *  所以我们在这里通过class_addMethod()的验证,如果self实现了这个方法,class_addMethod()函数将会返回NO,我们就可以对其进行交换了。
         */
        if (!class_addMethod([self class], @selector(swizzlingViewDidLoad), method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
            method_exchangeImplementations(fromMethod, toMethod);
        }
    }
    
    // 我们自己实现的方法,也就是和self的viewDidLoad方法进行交换的方法。
    - (void)swizzlingViewDidLoad {
        NSString *str = [NSString stringWithFormat:@"%@", self.class];
        // 我们在这里加一个判断,将系统的UIViewController的对象剔除掉
        if(![str containsString:@"UI"]){
            NSLog(@"统计打点 : %@", self.class);
        }
        [self swizzlingViewDidLoad];
    }
    @end
    

    Method Swizzling类簇:项目开发过程中,经常因为NSArray数组越界或者NSDictionary的key或者value值为nil等问题导致的崩溃,对于这些问题苹果并不会报一个警告,而是直接崩溃,感觉苹果这样确实有点“太狠了”。

    由此,我们可以根据上面所学,对NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等类进行Method Swizzling,实现方式还是按照上面的例子来做。但是....你发现Method Swizzling根本就不起作用,代码也没写错啊,到底是什么鬼?

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

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

    #import "NSArray+LXZArray.h"
    #import "objc/runtime.h"
    @implementation NSArray (LXZArray)
    + (void)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  ----------\n", class_getName(self.class), __func__);
                NSLog(@"%@", [exception callStackSymbols]);
                return nil;
        }
            @finally {}
        } else {
            return [self lxz_objectAtIndex:index];
        }
    }
    @end
    

    __NSArrayI才是NSArray真正的类,而NSMutableArray又不一样。我们可以通过runtime函数获取真正的类:

    objc_getClass("__NSArrayI")
    

    相关文章

      网友评论

          本文标题:iOS黑魔法-Method Swizzling

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