您的位置:首页 > 其它

使用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;
}

}

解决问题是解决了,可是很不好看,也没有办法保证其他成员也一定使用这个方法啊。再者,这样是无法对新语法(Objective-C Literals)起作用的(这点是硬伤……)。

突然想到了之前看过的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中找到objectAtIndex方法,Why?

这里的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

第一行调用NSArray类方法class,当然是它自己;第二行调用[NSArray array]方法返回了一个实例,类型是__NSArrayI;第三行调用了[NSMutableArray array]方法返回了可变数组的实例,类型是__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];

这下新版本的Crash数量又降低了,想想还有点小激动呢。

扩展阅读:

Class Clusters – Concepts in Objective-C Programming
Method Swizzling – NSHipster
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: