在iOS开发过程中,我们常见的集合类中主要包括:
- 不可变的数组–NSArray
- 可变的数组–NSMutableArray
- 不可变的字典–NSDictionary
- 可变的字典–NSMutableDictionary
- 不可变的集合–NSSet
- 可变的集合–NSMutableSet
我们最常用且容易出错的集合类就是:NSArray,NSMutableArray,NSMutableDictionary!
NSArray主要的问题是数组越界问题
NSMutableArray主要的问题是数组越界,和设置了nil到数组中
NSMutableDictionary 主要是在我们网络请求时,设置参数时若参数为nil则闪退!
为了解决以上问题,我们想出了两种方案:
- 为这些类添加类别,对之前的读取方法进一步包装,在包装中添加错误判断!这种方法入侵性过大,若工程之前已经开始了,大量的地方需要替换方法!
- 为这些类添加类别,使用runtime的method swizzling方法,为原读取方法添加错误判断,这种方法完全无侵入,我们只需要为其添加类别即可!
为此下面我们着重讨论第二种方法
按照我们的第一想法肯定是类似下面的代码:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzlingMethod];
});
}
+ (void)swizzlingMethod {
Class class = NSClassFromString(@"NSArray");
Method originMethod = class_getInstanceMethod(class, @selector(objectAtIndex:));
Method destMethod = class_getInstanceMethod(class, @selector(rh_objectAtIndex:));
method_exchangeImplementations(originMethod, destMethod);
}
- (id)rh_objectAtIndex:(NSUInteger)index {
if (self.count <= index) {
return nil;
} else {
return [self rh_IobjectAtIndex:index];
}
}
代码写完,模拟数组越界访问,结果根本没有调用!
经过资料查询:
Foundation框架里面有很多类使用类簇(class cluster)设计模式,来实现一些类如:UIButton,NSNumber,集合类等!其本质就是工厂设计模式;思路就是比如我们有一个类有很多的子类,如果把这些子类全部暴露给用户,用户会很晕,不如只暴露父类给用户,然后在父类中提供初始化方法:直接获取子类实例!如我们的NSNumber类他提供了:
+ (NSNumber *)numberWithChar:(char)value;
+ (NSNumber *)numberWithUnsignedChar:(unsigned char)value;
+ (NSNumber *)numberWithShort:(short)value;
+ (NSNumber *)numberWithUnsignedShort:(unsigned short)value;
+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithUnsignedInt:(unsigned int)value;
+ (NSNumber *)numberWithLong:(long)value;
+ (NSNumber *)numberWithUnsignedLong:(unsigned long)value;
+ (NSNumber *)numberWithLongLong:(long long)value;
+ (NSNumber *)numberWithUnsignedLongLong:(unsigned long long)value;
+ (NSNumber *)numberWithFloat:(float)value;
+ (NSNumber *)numberWithDouble:(double)value;
+ (NSNumber *)numberWithBool:(BOOL)value;
+ (NSNumber *)numberWithInteger:(NSInteger)value API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
+ (NSNumber *)numberWithUnsignedInteger:(NSUInteger)value API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
这么多子类如果全部暴露给用户,多复杂,我们只需要通过以上方法,想要获取哪个子类的实例,就调用不同的初始化方法,多好!
回到我们的集合类,Foundation框架对集合类的实现也是采取这种模式;
比如我们的NSArray就有__NSArray0,__NSSingleObjectArrayI,__NSArrayI!为什么要分这几种实例呢?首先__NSArray0是个单例,表示空数组,所有的空数组都一样,__NSSingleObjectArrayI表示单个元素的数组!__NSArrayI表示多个元素的数组!
所以我们在method swizzling的时候必须使用真正的class,否则无效果!
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzlingMethod];
});
}
+ (void)swizzlingMethod {
// __NSArrayI,__NSArray0,__NSSingleObjectArrayI
[self swizzlingClass:NSClassFromString(@"__NSArray0")
selector:@selector(objectAtIndex:)
destSelector:@selector(rh_0objectAtIndex:)];
[self swizzlingClass:NSClassFromString(@"__NSArray0")
selector:@selector(objectAtIndexedSubscript:)
destSelector:@selector(rh_0objectAtIndexedSubscript:)];
[self swizzlingClass:NSClassFromString(@"__NSArrayI")
selector:@selector(objectAtIndex:)
destSelector:@selector(rh_1objectAtIndex:)];
[self swizzlingClass:NSClassFromString(@"__NSArrayI")
selector:@selector(objectAtIndexedSubscript:)
destSelector:@selector(rh_1objectAtIndexedSubscript:)];
[self swizzlingClass:NSClassFromString(@"__NSSingleObjectArrayI")
selector:@selector(objectAtIndex:)
destSelector:@selector(rh_IobjectAtIndex:)];
[self swizzlingClass:NSClassFromString(@"__NSSingleObjectArrayI")
selector:@selector(objectAtIndexedSubscript:)
destSelector:@selector(rh_IobjectAtIndexedSubscript:)];
}
+ (void)swizzlingClass:(Class)class
selector:(SEL)selector
destSelector:(SEL)destSelector {
Method originMethod = class_getInstanceMethod(class, selector);
Method destMethod = class_getInstanceMethod(class, destSelector);
method_exchangeImplementations(originMethod, destMethod);
}
- (id)rh_0objectAtIndexedSubscript:(NSUInteger)idx {
if (self.count <= idx) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_0objectAtIndexedSubscript:idx];
}
}
- (id)rh_0objectAtIndex:(NSUInteger)index {
if (self.count <= index) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_0objectAtIndex:index];
}
}
- (id)rh_1objectAtIndexedSubscript:(NSUInteger)idx {
if (self.count <= idx) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_1objectAtIndexedSubscript:idx];
}
}
- (id)rh_1objectAtIndex:(NSUInteger)index {
if (self.count <= index) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_1objectAtIndex:index];
}
}
- (id)rh_IobjectAtIndexedSubscript:(NSUInteger)idx {
if (self.count <= idx) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_IobjectAtIndexedSubscript:idx];
}
}
- (id)rh_IobjectAtIndex:(NSUInteger)index {
if (self.count <= index) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_IobjectAtIndex:index];
}
}
这里面有个知识点是:当我们使用arr[]这种语法获取数组值的时候,我们调用的是:objectAtIndexedSubscript:方法,若该方法未method swizzling的话当你使用arr[]这种语法的时候还是会有数组越界问题的出现!
在method swizzling过程中还遇到了一个坑:就是为了偷懒只写一个
- (id)rh_objectAtIndex:(NSUInteger)index {
if (self.count <= index) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_IobjectAtIndex:index];
}
}
然后不同的类都exchange这个方法,逻辑肯定是错的,多次交换实现,最终成功的只有一个!
此外我们还加了一个异常处理,原因就是,我们在调试的时候还是希望数组越界等这种问题暴露出来,及时的修改bug,所以我们添加了一个方法实现:
+ (void)raiseException:(NSString *)reason {
#if DEBUG
[[NSException exceptionWithName:reason
reason:reason
userInfo:nil] raise];
#else
#endif
}