简要介绍
DZNEmptyDataSet 是APP在展示列表类数据功能,出现空数据情况下,给用户直观提示的UI控件。其特点及详细介绍图见前面超链接。
DZNEmptyDataSet 由 UIScrollView+EmptyDataSet
类别来实现所有功能逻辑,个人认为正适合此功能逻辑规模,不至于拆分成其他文件略显拖沓,单文件代码少。项目主要使用代理模式完成代码内部逻辑和外部调用者之间的通信,内部主要使用 runtime( Method Swizzling、get set AssociatedObject)
、Custome View
、Auto-Layout
技术和其他重要逻辑实现。
DZNEmptyDataSet 头文件代码注释很详细,就不再赘述了。具体使用也可看此文章DZNEmptyDataSet——空白数据集显示框架
实现文件中类结构为:
DZNWeakObjectContainer : NSObject --- manage weak delegate
DZNEmptyDataSetView : UIView --- custom view
UIScrollView+EmptyDataSet <category> --- functional logic
UIView+DZNConstraintBasedLayoutExtensions --- auto layout logic util
代码学习
-
#pragma mark - Method Swizzling
代码块
此代码块主要展示了如何将UITableView/UICollectionView/UIScrollView中的@selector(reloadData)方法和@selector(endUpdates)方法
与自定义C方法void dzn_original_implementation(id self, SEL _cmd)
进行正确的 Swizzling(交换) 。
1.创建一个符合IMP定义的C函数(更具体地说,是我们要Swizzling的自定义方法的签名)
void dzn_original_implementation(id self, SEL _cmd) {
//注入额外的方法:dzn_reloadEmptyDataSet(刷新空数据展示视图-DZNEmptyDataSetView、调用其他业务逻辑)
[self dzn_reloadEmptyDataSet];
//调用原始方法:
//Fetch original implementation from lookup table
IMP impPointer = //Fetch code ...
// If found, call original implementation
if (impPointer) {
((void(*)(id,SEL))impPointer)(self,_cmd);
}
}
- 正确使用
method_setImplementation
进行Swizzling的注意点:
将要Swizzling的函数转换为IMP
:
IMP swizzleImp = (IMP)dzn_original_implementation;
将此IMP
传递给method_setImplementation()
方法,并且此方法会返回原始方法的IMP-original IMP
:
IMP originalImp = method_setImplementation(method, (IMP)dzn_original_implementation);
那么使用得到的originalImp
可以调用原始的实现方法:
originalImp(self,_cmd);
另外,如果IMP调用返回一个空值,您可能不得不对其进行处理。
这是因为ARC假定所有的imp都返回一个id,并尝试retain void和基本类型。
IMP anImp; //represents objective-c function
// -UIViewController viewDidLoad;
((void(*)(id,SEL))anImp)(self,_cmd); //call with a cast to prevent
// ARC from retaining void.
- DZNEmptyDataSet中正确使用
method_setImplementation
进行Swizzling的Code:
[self swizzleIfPossible:@selector(reloadData)];
- (void)swizzleIfPossible:(SEL)selector
{
// Check if the target responds to selector
// 如果没有实现selector,return
if (![self respondsToSelector:selector]) {
return;
}
// Create the lookup table
//创建存储原始IMP的字典,供后续查找调用
if (!_impLookupTable) {
_impLookupTable = [[NSMutableDictionary alloc] initWithCapacity:3]; // 3 represent the supported base classes
}
// We make sure that setImplementation is called once per class kind, UITableView or UICollectionView.
//确保 method_setImplementation 在一种类中只调用一次,即_impLookupTable存有要被Swizzling的selector,说明已经被Swizzling了,这时return
for (NSDictionary *info in [_impLookupTable allValues]) {
Class class = [info objectForKey:DZNSwizzleInfoOwnerKey];
NSString *selectorName = [info objectForKey:DZNSwizzleInfoSelectorKey];
if ([selectorName isEqualToString:NSStringFromSelector(selector)]) {
if ([self isKindOfClass:class]) {
return;
}
}
}
Class baseClass = dzn_baseClassToSwizzleForTarget(self);
NSString *key = dzn_implementationKey(baseClass, selector);
NSValue *impValue = [[_impLookupTable objectForKey:key] valueForKey:DZNSwizzleInfoPointerKey];
// If the implementation for this class already exist, skip!!
// 与上面一样,已经被Swizzling则return
if (impValue || !key || !baseClass) {
return;
}
// Swizzle by injecting additional implementation
// 开始Swizzle,method-被替换的方法实现 (IMP)dzn_original_implementation)-自定义的方法实现
Method method = class_getInstanceMethod(baseClass, selector);
IMP dzn_newImplementation = method_setImplementation(method, (IMP)dzn_original_implementation);
// Store the new implementation in the lookup table
//存储被替换的原始方法信息:
//baseClass-类 NSStringFromSelector(selector)-selector字符串, [NSValue valueWithPointer:dzn_newImplementation]-指针值,
NSDictionary *swizzledInfo = @{DZNSwizzleInfoOwnerKey: baseClass,
DZNSwizzleInfoSelectorKey: NSStringFromSelector(selector),
DZNSwizzleInfoPointerKey: [NSValue valueWithPointer:dzn_newImplementation]};
[_impLookupTable setObject:swizzledInfo forKey:key];
}
扩展
根据DZNEmptyDataSet的源码,自己写了一个用于在UIViewController显示空白提示的控件库 - UIViewControllerLoadResultHint