我们先来看看有可能会出现的数组越界Crash的地方;
1
2
3
4
5
6
7
|
- (
void
)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
WelfareItem *item = [_datasourceArray objectAtIndex:indexPath.row];
//有可能会越界,你在下拉刷新时会用[_datasourceArray removeAllObjects],这时你又点了某个cell就会Crash
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
WelfareItem *item = _datasourceArray[indexPath.row];
//有可能会越界,两个地方用了[tableView reloadData];后一个有[_datasourceArray removeAllObjects];前一个还没有执行完,就会Crash
}
|
上面代码是有可能会越界的;出现Crash也不好复现,发出去的App总是能收到几条Crash;解决这个问题也很简单代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
- (
void
)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
WelfareItem *item = nil;
if
(indexPath.row < [_datasourceArray count]) {
//无论你武功有多高,有时也会忘记加
item = [_datasourceArray objectAtIndex:indexPath.row];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
WelfareItem *item = nil;
if
(indexPath.row < [_datasourceArray count]) {
item = [_datasourceArray objectAtIndex:indexPath.row];
}
}
|
问题又来了,无论你武功有多高,有时也会忘记加;所以我们要想一招制敌办法;我是想到了用Runtime把objectAtIndex方法替换一下;代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
/*!
@category
@abstract NSObject的Category
*/
@interface
NSObject (Util)
/*!
@method swizzleMethod:withMethod:error:
@abstract 对实例方法进行替换
@param oldSelector 想要替换的方法
@param newSelector 实际替换为的方法
@param error 替换过程中出现的错误,如果没有错误为nil
*/
+ (BOOL)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector error:(NSError **)error;
@end
#
import
"NSObject+Util.h"
#
import
<objc runtime.h=
""
>
@implementation
NSObject (Util)
+ (BOOL)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector error:(NSError **)error
{
Method originalMethod = class_getInstanceMethod(self, originalSelector);
if
(!originalMethod) {
NSString *string = [NSString stringWithFormat:@
" %@ 类没有找到 %@ 方法"
,NSStringFromClass([self
class
]),NSStringFromSelector(originalSelector)];
*error = [NSError errorWithDomain:@
"NSCocoaErrorDomain"
code:-
1
userInfo:[NSDictionary dictionaryWithObject:string forKey:NSLocalizedDescriptionKey]];
return
NO;
}
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
if
(!swizzledMethod) {
NSString *string = [NSString stringWithFormat:@
" %@ 类没有找到 %@ 方法"
,NSStringFromClass([self
class
]),NSStringFromSelector(swizzledSelector)];
*error = [NSError errorWithDomain:@
"NSCocoaErrorDomain"
code:-
1
userInfo:[NSDictionary dictionaryWithObject:string forKey:NSLocalizedDescriptionKey]];
return
NO;
}
if
(class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else
{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
return
YES;
}
@end
@implementation
NSArray (ErrerManager)
+ (
void
)load
{
static
dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool
{
[objc_getClass(
"__NSArrayI"
) swizzleMethod:
@selector
(objectAtIndex:) withMethod:
@selector
(swizzleObjectAtIndex:) error:nil];
[objc_getClass(
"__NSArrayM"
) swizzleMethod:
@selector
(objectAtIndex:) withMethod:
@selector
(swizzleObjectAtIndex:) error:nil];
};
});
}
- (id)swizzleObjectAtIndex:(NSUInteger)index
{
if
(index < self.count)
{
return
[self swizzleObjectAtIndex:index];
}
NSLog(@
"%@ 越界"
,self);
return
nil;
//越界返回为nil
}
@end
</objc>
|
有了上面代码我们用 [_datasourceArray objectAtIndex:indexPath.row] 就不会发生越界Crash了;越界
了会返回nil;看来是一个比较不错的解决方案;把app发出去吧,结果我们Crash比之前高了好几倍(越界的Crash没有了,出新的Crash了);Crash如下
1
2
3
4
5
6
7
8
9
10
11
12
13
|
1
tbreader
0x002b93e9
tbreader +
2098153
2
libsystem_platform.dylib
0x33a66873
_sigtramp +
34
3
libsystem_blocks.dylib
0x33941ae1
_Block_release +
216
4
libobjc.A.dylib
0x333c11a9
+
404
5
CoreFoundation
0x25ba23a9
_CFAutoreleasePoolPop +
16
6
UIKit
0x2912317f
+
42
7
CoreFoundation
0x25c565cd
+
20
8
CoreFoundation
0x25c53c8b
+
278
9
CoreFoundation
0x25c54093
+
914
10
CoreFoundation
0x25ba2621
CFRunLoopRunSpecific +
476
11
CoreFoundation
0x25ba2433
CFRunLoopRunInMode +
106
12
GraphicsServices
0x2cf0a0a9
GSEventRunModal +
136
13
UIKit
0x2918c809
UIApplicationMain +
1440
|
都是这个Crash,出现在iOS7以上(含iOS7),关键还没有用户反馈有问题,Crash高了几倍没有一个用户反馈这种情况还是少见的,大家测试还复现不了;测试了一周终于复现了一样的Crash;是这样出现的,替换了objectAtIndex方法有输入的地方出来了软键盘按手机Home键就Crash了;此法不行只,只能另寻他策了。后来我们就给数组新增扩展方法代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@interface
NSArray (SHYUtil)
/*!
@method objectAtIndexCheck:
@abstract 检查是否越界和NSNull如果是返回nil
@result 返回对象
*/
- (id)objectAtIndexCheck:(NSUInteger)index;
@end
#
import
"NSArray+SHYUtil.h"
@implementation
NSArray (SHYUtil)
- (id)objectAtIndexCheck:(NSUInteger)index
{
if
(index >= [self count]) {
return
nil;
}
id value = [self objectAtIndex:index];
if
(value == [NSNull
null
]) {
return
nil;
}
return
value;
}
@end
|
把之前的代码 WelfareItem *item = [_datasourceArray objectAtIndex:indexPath.row] 改为 WelfareItem *item = [_datasourceArray objectAtIndexCheck:indexPath.row] 就可以了。这样就可以彻底解决数组越界 -[__NSArrayI objectAtIndex:]: index 100 beyond bounds [0 .. 1]' 错误了