简单Method Swizzling应用之数组保护

 * author:conowen@大钟                                                                                                                          
 * E-mail:[email protected]      

Method Swizzling

由这一篇博文可知,OC的方法调用实质是消息发送,OC语言具有动态性,在运行的时候才会寻找方法的实现,因此,可以利用这个属性,动态更改方法体。
简单来说,Method Swizzling就是交换两个方法体,因为每个类都维护一个方法(Method)列表,Method则包含SEL和其对应IMP的信息,方法交换做的事情就是把SEL和IMP的对应关系互换。

主要API

//获取通过SEL获取一个方法
class_getInstanceMethod
//获取一个方法的实现
method_getImplementation
//給方法添加实现
class_addMethod
//用一个方法的实现替换另一个方法的实现
class_replaceMethod
//交换两个方法的实现
method_exchangeImplementations

数组越界保护

在OC编程中,数组是经常使用的对象,使用过程中经常出现数组越界,或者非空问题,利用Method Swizzling技术,可以避免这个问题。

主要流程是新建一个NSArray+Safe.hCategory文件,在+ (void)load完成关键的方法交互即可。


//
//  NSArray+Safe.m
//  TestRuntime
//
//  Created by Johnny Zhong on 2017/2/3.
//  Copyright © 2017年 Royole. All rights reserved.
//

#import "NSArray+Safe.h"
#import 

@implementation NSArray (Safe)
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        @autoreleasepool {
            /** 不可变数组 */
            //空
            [objc_getClass("__NSArray0") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(emptyObjectAtIndex:)];
            //非空NSArray
            [objc_getClass("__NSArrayI") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(nonEmptyObjectAtIndex:)];
            
            //非空NSMutableArray
            [objc_getClass("__NSArrayM") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(mutableObjectAtIndex:)];
            
        }
    });
}

#pragma mark - 不可变

// 空
- (id)emptyObjectAtIndex:(NSInteger)index{
    return nil;
}

// NSArray 不为空
- (id)nonEmptyObjectAtIndex:(NSInteger)index{
    if (index >= self.count || index < 0) {
        NSLog(@"NSArray数组越界");
        return nil;
    }
    id obj = [self nonEmptyObjectAtIndex:index];
    if ([obj isKindOfClass:[NSNull class]]) {
        NSLog(@"NSArray数组取出的元素类型为 NSNull");
        return nil;
    }
    return obj;
}

// NSMutableArray 不为空
- (id)mutableObjectAtIndex:(NSInteger)index{
    if (index >= self.count || index < 0) {
        NSLog(@"NSMutableArray数组越界");
        return nil;
    }
    id obj = [self mutableObjectAtIndex:index];
    if ([obj isKindOfClass:[NSNull class]]) {
        NSLog(@"NSMutableArray数组取出的元素类型为 NSNull");
        return nil;
    }
    return obj;
}

#pragma mark - 方法交换
+ (void)swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector{
    Class class = [self class];
    
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    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);
    }
}

@end

需要注意的是:

  • 通过在Category的+ (void)load方法中添加Method Swizzling的代码,在类初始加载时自动被调用,load方法按照父类到子类,类自身到Category的顺序被调用.

  • 在dispatch_once中执行Method Swizzling是一种防护措施,以保证代码块只会被执行一次并且线程安全,不过此处并不需要,因为当前Category中的load方法并不会被多次调用.

  • 尝试先调用class_addMethod方法,以保证即便originalSelector只在父类中实现,也能达到Method Swizzling的目的.

递归疑问

// NSArray 不为空
- (id)nonEmptyObjectAtIndex:(NSInteger)index{
    if (index >= self.count || index < 0) {
        NSLog(@"NSArray数组越界");
        return nil;
    }
    id obj = [self nonEmptyObjectAtIndex:index];
    if ([obj isKindOfClass:[NSNull class]]) {
        NSLog(@"NSArray数组取出的元素类型为 NSNull");
        return nil;
    }
    return obj;
}

看到id obj = [self nonEmptyObjectAtIndex:index];有些人可能疑问,这里不是导致递归了吗?
其实并不是,Method Swizzling可以理解为"方法互换"。假设我们将A和B两个方法进行互换,向A方法发送消息时执行的却是B方法,向B方法发送消息时执行的是A方法。所以id obj = [self nonEmptyObjectAtIndex:index];执行的其实是id obj = [self objectAtIndex:index];

你可能感兴趣的:(简单Method Swizzling应用之数组保护)