使用Method swizzling方法修改常用函数行为
2015-05-26 10:59
751 查看
使用Method swizzling方法修改常用函数行为
我们知道,NSArray的objectAtIndex方法若使用了一个超出范围的index值,那么将会抛出异常导致程序终止运行。然而在发布的App中,其实NSArray的异常有些小题大做了。即使显示数据为空,也比Crash掉的用户体验要好的多啊。(例如初始化的Cell在填充数据时,objectAtIndex获取数据index越界导致异常)
因此,我就写了一个NSArray的Category:
- (id)safeObjectAtIndex:(NSUInteger)index
{
if (index < self.count) {
return [self objectAtIndex:index];
} else {
return nil;
}
}
1 2 3 4 5 6 7 8 | - (id)safeObjectAtIndex:(NSUInteger)index { if (index < self.count) { return [self objectAtIndex:index]; } else { return nil; } } |
突然想到了之前看过的Method swizzling的方法,利用Runtime特性,可以替换现有方法的实现。在这里使用在合适不过了。(深入阅读:深入浅出Cocoa之Method Swizzling)
那么试试看吧。如果你看了上面提到的那篇文章,应该发现了jrswizzle——Method swizzling方法的封装。我们就不造轮子了,用CocoaPods将它加入到我们的项目中吧。
首先创建NSArray的Category,当DEBUG模式时,index超出范围将抛出异常,而Release版本中只会返回nil:
@interface NSArray (SafeCategory)
- (id)TKSafe_objectAtIndex:(NSUInteger)index;
@end
@implementation NSArray (SafeCategory)
- (id)TKSafe_objectAtIndex:(NSUInteger)index
{
if (index < self.count) {
return [self TKSafe_objectAtIndex:index];
} else {
#ifdef DEBUG
NSAssert(NO, @"index %d > count %d", index, self.count);
#endif
return nil;
}
}
@end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @interface NSArray (SafeCategory) - (id)TKSafe_objectAtIndex:(NSUInteger)index; @end @implementation NSArray (SafeCategory) - (id)TKSafe_objectAtIndex:(NSUInteger)index { if (index < self.count) { return [self TKSafe_objectAtIndex:index]; } else { #ifdef DEBUG NSAssert(NO, @"index %d > count %d", index, self.count); #endif return nil; } } @end |
#import <JRSwizzle/JRSwizzle.h>
NSError *error = nil;
[NSArray jr_swizzleClassMethod:@selector(objectAtIndex:)
withClassMethod:@selector(TKSafe_objectAtIndex:)
error:&error];
NSLog(@"Err: %@", error);
1 2 3 4 5 6 7 | #import <JRSwizzle/JRSwizzle.h> NSError *error = nil; [NSArray jr_swizzleClassMethod:@selector(objectAtIndex:) withClassMethod:@selector(TKSafe_objectAtIndex:) error:&error]; NSLog(@"Err: %@", error); |
Err: Error Domain=NSCocoaErrorDomain Code=-1 "+[NSObject(JRSwizzle) jr_swizzleMethod:withMethod:error:]: original method objectAtIndex: not found for class NSArray" UserInfo=0x1754d770 {NSLocalizedDescription=+[NSObject(JRSwizzle) jr_swizzleMethod:withMethod:error:]: original method objectAtIndex: not found for class NSArray}
1 | Err: Error Domain=NSCocoaErrorDomain Code=-1 "+[NSObject(JRSwizzle) jr_swizzleMethod:withMethod:error:]: original method objectAtIndex: not found for class NSArray" UserInfo=0x1754d770 {NSLocalizedDescription=+[NSObject(JRSwizzle) jr_swizzleMethod:withMethod:error:]: original method objectAtIndex: not found for class NSArray} |
这里的NSArray是一种特殊的类,英文叫做Class cluster,中文翻译过来是类簇,在设计模式中,这个叫做工厂类,它在外层提供了很多方法接口,但是这些方法的实现是由具体的内部类来实现的。当使用NSArray生成一个对象时,初始化方法会判断哪个“自己内部的类”最适合生成这个对象,然后这个“工厂”就会生成这个具体的类对象返回给你。这种又外层类提供统一抽象的接口,然后具体实现让隐藏的,具体的内部类来实现,在设计模式中称为“抽象工厂”模式。(引用自objc’s
category and class cluster)
也就是说,对于普通的类,我们使用上述方法是没有问题的,然而对于Class cluster这种工厂类,就需要找到它的真身才行。
通过对Class cluster的了解,我们明白了平时使用的NSArray实例其实是另外一种类型,让我们看看它们的真身:
NSLog(@"%@", [NSArray class]);
NSLog(@"%@", [[NSArray array] class]);
NSLog(@"%@", [[NSMutableArray array] class]);
1 2 3 4 | NSLog(@"%@", [NSArray class]); NSLog(@"%@", [[NSArray array] class]); NSLog(@"%@", [[NSMutableArray array] class]); |
NSArray
__NSArrayI
__NSArrayM
1 2 3 4 | NSArray __NSArrayI __NSArrayM |
现在已经知道了待替换方法类的名称,获得它的Class就很随意了,我们有三种方法:
Class class = [[NSArray array] class];
Class class = NSClassFromString(@”__NSArrayI”);
Class class = objc_getClass(“__NSArrayI”);
第二种和第三种方式基本相同,都是根据类名字符串获得Class,但第三种写法编译器会报警告:Implicitly declaring library function ‘objc_getClass’ with type ‘id (const char *)’。第一种通过实体类获得Class,从安全性角度来说,我倾向于使用第一种。
最终代码如下,当然,我们还可以修改更多,这里有一份例子
[[[NSArray array] class] jr_swizzleMethod:@selector(objectAtIndex:)
withMethod:@selector(TKSafe_objectAtIndex:)
error:&error];
1 2 3 | [[[NSArray array] class] jr_swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(TKSafe_objectAtIndex:) error:&error]; |
扩展阅读:
Class Clusters – Concepts in Objective-C ProgrammingMethod Swizzling – NSHipster
相关文章推荐
- DataGrid使用小结(二)——常用函数方法
- DataGrid使用小结(二)——常用函数方法
- 子类重载父类的非虚成员函数是否对从父类中继承且使用该成员函数的方法的行为产生影响
- 【转帖】C语言的常用库函数使用方法分析及用途
- 常用的对话框函数使用方法
- fleaphp常用函数findAll方法的使用和示例
- ini_set()函数的使用 以及 post_max_size,upload_max_filesize的修改方法
- qt常用函数在PYQT中的使用方法
- ORACLE常用结构和函数使用方法总结
- Ajax常用的几个函数及Alexa查询的几个查询接口及使用方法
- C语言的常用库函数使用方法分析及用途 (1)
- C语言的常用库函数使用方法分析及用途(3)
- C语言的常用库函数使用方法分析及用途(2)
- C语言的常用库函数使用方法分析及用途(5)
- C语言的常用库函数使用方法分析及用途(4)
- C语言的常用库函数使用方法分析及用途(8)
- C语言的常用库函数使用方法分析及用途(7)
- C语言的常用库函数使用方法分析及用途(6)
- C语言的常用库函数使用方法分析及用途(10)
- C语言的常用库函数使用方法分析及用途(9)