iOS - Method Swizzing

Method Swizzling基础

1.Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换。

2.我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。

3.Method Swizzling是iOS中AOP(面相切面编程)的一种实现方式,我们可以利用苹果这一特性来实现AOP编程。

Method Swizzling使用

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

OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2)  __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
复制代码

Method Swizzling应用场景

如:页面统计的实现

创建一个Category,在Category中的+(void)load方法中添加Method Swizzling方法,我们用来替换的方法也写在这个Category中。

技巧:load类方法是程序运行时这个类被加载到内存中就调用的一个方法,执行比较早,并且不需要我们手动调用。而且这个方法具有唯一性,也就是只会被调用一次,不用担心资源抢夺的问题。

定义Method Swizzling中我们自定义的方法时,需要注意尽量加前缀,以防止和其他地方命名冲突,Method Swizzling的替换方法命名一定要是唯一的,至少在被替换的类中必须是唯一的。

#import "UIViewController+swizzling.h"
#import @implementation UIViewController (swizzling)
 
+ (void)load {
    [super 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(viewDidLoad), 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);
    }
    // 下面执行的实际是viewDidLoad,因为在load中互换了方法
    [self swizzlingViewDidLoad];
}
@end
复制代码

附:这类需求上的非Method Swizzing简单实现

创建一个Category来覆盖系统方法,系统会优先调用Category中的代码,然后在调用原类中的代码。

在所有控制器中引入这个Category(修改比较大)。 我们也可以添加一个PCH文件,然后将这个Category添加到PCH文件中。

#import "UIViewController+EventGather.h"
@implementation UIViewController (EventGather)
- (void)viewDidLoad {
   NSLog(@"页面统计:%@", self);
}
@end
复制代码

又如:防止NSArray数组越界崩溃或者NSDictionary的key、value空值崩溃

创建一个Category,在Category中的+(void)load方法中添加Method Swizzling方法swizzlingObjectAtIndex。

#import "NSArray+GDArray.h"
#import "objc/runtime.h"
@implementation NSArray (GDArray)
+ (void)load {
    [super load];
    Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
    Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(swizzlingObjectAtIndex:));
    method_exchangeImplementations(fromMethod, toMethod);
}
 
- (id)swizzlingObjectAtIndex:(NSUInteger)index {
    if (self.count-1 < index) {
        // 这里做一下异常处理,不然都不知道出错了。
        @try {
            return [self swizzlingObjectAtIndex: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 swizzlingObjectAtIndex:index];
    }
}
@end
复制代码

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

是否决定使用Method Swizzling

杀鸡焉用牛刀

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值