接上一篇的iOS黑魔法来看下NSArray的拦截方法。拦截的主要就是一些常用的方法,比如index访问越界,add remove操作等,笔者列出自己的封装,不过你也可以根据自己的需要添加更多的拦截方法。
首先要明白NSArray是用了类簇的方法隐藏了内部的实现细节,NSArray也只是一个抽象的基类,用NSArray创建的对象并不是NSArray对象,而是它所封装的实体子类。
OC中并不能指明哪个类是抽象基类,需要我们自己去阅读文档来判断,
弄懂了这一点你就可以正确的进行方法的拦截了。说到这里就直接上代码了:
代码块
@implementation NSArray (xzSwizzle)
+ (void)load {
[objc_getClass("__NSArray0") xzSwizzleMethod:@selector(objectAtIndex:) withMethod:@selector(xz_objectAtIndex:) error:nil];
[objc_getClass("__NSSingleObjectArrayI") xzSwizzleMethod:@selector(objectAtIndex:) withMethod:@selector(xz_objectAtIndex:) error:nil];
[objc_getClass("__NSArrayI") xzSwizzleMethod:@selector(objectAtIndex:) withMethod:@selector(xz_objectAtIndex:) error:nil];
}
- (id)xz_objectAtIndex:(NSUInteger)index {
if (index >= self.count) {
NSAssert(NO, @"---index越界!!!");
return nil;
}
return [self xz_objectAtIndex:index];
}
@end
@implementation NSMutableArray (xzSwizzle)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[objc_getClass("__NSArrayM") xzSwizzleMethod:@selector(insertObject:atIndex:) withMethod:@selector(xz_insertObject:atIndex:) error:nil];
[objc_getClass("__NSArrayM") xzSwizzleMethod:@selector(removeObjectAtIndex:) withMethod:@selector(xz_removeObjectAtIndex:) error:nil];
[objc_getClass("__NSArrayM") xzSwizzleMethod:@selector(replaceObjectAtIndex:withObject:) withMethod:@selector(xz_replaceObjectAtIndex:withObject:) error:nil];
});
}
- (void)xz_insertObject:(id)anObject atIndex:(NSUInteger)index {
if (!anObject || index > self.count) {
NSAssert(NO, @"---index越界或obj为nil!!!");
return;
}
[self xz_insertObject:anObject atIndex:index];
}
- (void)xz_removeObjectAtIndex:(NSUInteger)index {
if (index >= self.count) {
NSAssert(NO, @"---index越界!!!");
return;
}
[self xz_removeObjectAtIndex:index];
}
- (void)xz_replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
if (!anObject || index >= self.count) {
NSAssert(NO, @"---index越界或obj为nil!!!");
return;
}
[self xz_replaceObjectAtIndex:index withObject:anObject];
}
@end
拦截的方法中添加了NSAssert,为了在debug环境中能够及时的发现这个问题,让其crash,不然发到线上岂不是很尴尬!当然上面的只是一部分拦截,你可以根据需要添加。
+load方法中的xzSwizzleMethod:withMethod:
就是上篇文章提到的对method swizzle的封装,没有看到的朋友可以看下上篇文章。
另外发现有的博客中在+load中添加了
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{}
我想说,完全没必要,因为+load只在APP启动的时候执行一次,没必要添加单例,类对象本身就是一个单例!!!更多对于+load的知识可以百度一下,已经有很多大牛分析过。
问题
在添加这个拦截之后也不是一帆风顺的,因为线上莫名其妙的出现了几个崩溃:
[UIKeyboardLayoutStar release]: message sent to deallocated
发现这个问题之后我立马去排查,因为之前没有这个crash,在添加这个拦截之后就出现了,怀疑是这拦截机制有关系。复现方法在设备连接xcode的时候呼出键盘然后按home键,回到后台就会崩溃。而去掉objectAtIndex
拦截之后就会正常,果然不出所料,猜测objectAtIndex
应该不仅仅是取index下的对象还做了其他的操作,最终解决方案:添加子啊MRC下的同样的拦截就可以了,
@implementation NSMutableArray (xzSWizzleMRC)
+ (void)load {
[objc_getClass("__NSArrayM") xzSwizzleMethod:@selector(objectAtIndex:) withMethod:@selector(safeMRC_objectAtIndex:) error:nil];
}
- (id)safeMRC_objectAtIndex:(NSUInteger)index {
@autoreleasepool {
if (index >= self.count) {
NSAssert(NO, @"---index越界!!!");
return nil;
}
return [self safeMRC_objectAtIndex:index];
}
}
@end
因为笔者整个工程是在ARC环境下,所以不要忘记对文件进行标记-fno-objc-arc