一个空数据界面引发的思考
现在项目中有很多很多列表界面,虽然产品经理没有提出要加空数据界面(因为没有产品经理~),但是我不想我自己写的产品很辣鸡, 于是就想怎么让所有的列表都加上空数据界面。
到每一个 ViewController 中写一套逻辑显然很蠢,肯定要抽象出来。
如何让 UITableView 无数据的时候显示空数据界面,有数据的时候只显示列表呢?
1. 第一反应是监听数据源 NSMutableArray
于是写了一个方法,外部传入需要监听的NSMutableArray
- (Void *)showEmptyViewWithObserveArray:(NSMutableArray *)arrayM image:(NSString *)imageName description:(NSString *)description
{
...
[arrayM addObserver:self forKeyPath:@"count" options:NSKeyValueObservingOptionNew context:nil];
...
}
写好 KVO 后发现
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '[<__NSArrayM 0x600000250380> addObserver:forKeyPath:options:context:]
is not supported. Key path: count'
为什么 NSMutableArray 的 count 不能被监听?
于是 stackoverflow
NSArray itself just doesn’t support KVO, period. It’s the controller in front of the array that you need to observe. For example, if you have an NSArrayController, you can set an observer for arrangedObjects.count.
“just doesn’t support KVO” ??? 不服。
Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A 的新类,该类继承自对象A的本类,且 KVO 为 NSKVONotifying_A 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。
In order to be key-value coding compliant for a mutable ordered to-many relationship you must implement the following methods:
-insertObject:in
AtIndex: or -insert :atIndexes:. At least one of these methods must be implemented. These are analogous to the NSMutableArray methods insertObject:atIndex: and insertObjects:atIndexes:. -removeObjectFrom
AtIndex: or -remove AtIndexes:. At least one of these methods must be implemented. These methods correspond to the NSMutableArray methods removeObjectAtIndex: and removeObjectsAtIndexes: respectively. -replaceObjectIn
AtIndex:withObject: or -replace AtIndexes:with :. Optional. Implement if benchmarking indicates that performance is an issue. The -insertObject:in
AtIndex: method is passed the object to insert, and an NSUInteger that specifies the index where it should be inserted. The -insert :atIndexes: method inserts an array of objects into the collection at the indices specified by the passed NSIndexSet. You are only required to implement one of these two methods.
好吧,用 kvo 监听 NSMutableArray 也不是不可以,但是需要在使用 NSMutableArray 的增删改操作时遵从 KVC 的规则。
而且不能监听NSMutableArray的 count,只能将NSMutableArray 作为某个对象的属性去监听。
显然不行啊,散落各处的对NSMutableArray 的操作都是直接 addObject removeObject。
2. planB 监听NSMutableArray 的 addobject 方法
本来就有 hook 掉NSMutableArray 的 addobject 方法,来避免在NSMutableArray中加入 nil 导致应用崩溃。
想来现在在addobject 的同时回调一下应该没什么问题。
然而以前写的方法好像失效了
Method originalMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(addObject:));
Method newMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(safe_addObject:));
method_exchangeImplementations(originalMethod, newMethod);
很奇怪 没有走safe_addObject方法
而NSMutableDictionary还是成功的
Method originalMethod = class_getInstanceMethod(NSClassFromString(@"__NSDictionaryM"), @selector(setObject:forKey:));
Method newMethod = class_getInstanceMethod(NSClassFromString(@"__NSDictionaryM"), @selector(safe_setObject:forKey:));
method_exchangeImplementations(originalMethod, newMethod);
??? 怎么都看不出错误..
在各种查阅资料的同时了解到一个关于 AOP 的开源库 Aspects
详细信息可以参考微信阅读的一篇博客:面向切面编程之 Aspects 源码解析及应用
微信阅读的博客质量是真的高..
于是我使用Aspects来监听
[NSClassFromString(@"__NSArrayM") aspect_hookSelector:@selector(addObject:) withOptions:AspectOptionAutomaticRemoval usingBlock:^(id<AspectInfo> aspectInfo){
NSLog(@"success");
} error:NULL];
成功了..但是量实在太大了..planB 有点蠢..
3. planC hook UITableView 的 reloadData 方法,监听 UITableView 的 DataSource
说实话我并没有想到这个方式,也是Google出来的,这样做的确解决了问题。
尽管这并没有避免要在所有写UITableView的地方植入展示空数据界面的代码。
由于空数据界面的展示图片和文字都跟业务相关,所以这样也无妨。
end
写提示信息的时候想到,不能把这些提示文字散落到项目各处,于是顺手去学了一波使用Localizable.strings,强行国际化项目。
继续加油吧。