iOS中有各类容器的概念,容器分可变容器和非可变容器,可变容器一般内部在实现上是一个链表,在进行各类(insert 、remove、 delete、 update )难免有空操作、指针越界的问题。
最粗暴的方式就是在使用可变容器的时间,每次操作都必须手动做空判断、索引比较这些操作:

 NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
    if (obj) {
        [dic setObject:obj forKey:@"key"];
    }

 NSMutableArray *array = [[NSMutableArray alloc] init];
    if (index < array.count) {
        NSLog(@"%@",[array objectAtIndex:index]);
    }

在代码中大量的使用这鞋操作实在是太过于繁琐了,试想如果可变容器自身如何能做这些兼容岂不是更好。可能会想到继承的方法来解决,但是项目中尽可能的避免过多的派生(至于派生的弊端这里就不多说了);或者想到分类,这里也不尽人意。

Method Swizzling 移花接木

OC中每个方法的名字(SEL)跟函数的实现(IMP)是一一对应,Swizzle的原理只是在这个地方做下手脚,将原来方法名与实现的指向交叉处理,就能达到一个新的效果。

这里使用NSMutableArray 做实例,为NSMutableArray追加一个新的方法

@implementation NSMutableArray (safe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        id obj = [[self alloc] init];
        [obj swizzleMethod:@selector(addObject:) withMethod:@selector(safeAddObject:)];
        [obj swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(safeObjectAtIndex:)];
        [obj swizzleMethod:@selector(insertObject:atIndex:) withMethod:@selector(safeInsertObject:atIndex:)];
        [obj swizzleMethod:@selector(removeObjectAtIndex:) withMethod:@selector(safeRemoveObjectAtIndex:)];
        [obj swizzleMethod:@selector(replaceObjectAtIndex:withObject:) withMethod:@selector(safeReplaceObjectAtIndex:withObject:)];
    });
}

- (void)safeAddObject:(id)anObject
{
    if (anObject) {
        [self safeAddObject:anObject];
    }else{
        NSLog(@"obj is nil");

    }
}

- (id)safeObjectAtIndex:(NSInteger)index
{
    if(index<[self count]){
        return [self safeObjectAtIndex:index];
    }else{
        NSLog(@"index is beyond bounds ");
    }
    return nil;
}

- (void)swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector
{
    Class cls = [self class];

    Method originalMethod = class_getInstanceMethod(cls, origSelector);
    Method swizzledMethod = class_getInstanceMethod(cls, newSelector);

    BOOL didAddMethod = class_addMethod(cls,
                                        origSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        class_replaceMethod(cls,
                            newSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

这里唯一可能需要解释的是 class_addMethod 。要先尝试添加原 selector 是为了做一层保护,因为如果这个类没有实现 originalSelector ,但其父类实现了,那 class_getInstanceMethod 会返回父类的方法。这样 method_exchangeImplementations 替换的是父类的那个方法,这当然不是你想要的。所以我们先尝试添加 orginalSelector ,如果已经存在,再用 method_exchangeImplementations 把原方法的实现跟新的方法实现给交换掉。

safeAddObject 代码看起来可能有点奇怪,像递归不是么。当然不会是递归,因为在 runtime 的时候,函数实现已经被交换了。调用 objectAtIndex: 会调用你实现的 safeObjectAtIndex:,而在 NSMutableArray: 里调用 safeObjectAtIndex: 实际上调用的是原来的 objectAtIndex: 。

如此以来,一直担心的问题就迎刃而解了,不仅在可变数组、可变字典等容器内都可以做自己想做的事情。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐