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