DZNEmptyDataSet是iOS开发中常用的一个处理UITableView 或者 UICollectionView无数据视图的一个第三方框架,其使用方式可以参考iOS开发-使用DZNEmptyDataSet处理UITableView无数据情况
今天我们来主要来分析其实现思路
首先,设置 emptyDataSetSource 与 emptyDataSetDelegate
tableView.emptyDataSetSource = self;
tableView.emptyDataSetDelegate = self;
我们可以看一下这两个属性的setter实现
#pragma mark - Setters (Public)
- (void)setEmptyDataSetSource:(id<DZNEmptyDataSetSource>)datasource
{
if (!datasource || ![self dzn_canDisplay]) {
// 移除无数据视图
[self dzn_invalidate];
}
// 此处为运行时的动态绑定,常见于为Categary添加属性的代码中
objc_setAssociatedObject(self, kEmptyDataSetSource, [[DZNWeakObjectContainer alloc] initWithWeakObject:datasource], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 我们需要注意的地方
// We add method sizzling for injecting -dzn_reloadData implementation to the native -reloadData implementation
[self swizzleIfPossible:@selector(reloadData)];
// Exclusively for UITableView, we also inject -dzn_reloadData to -endUpdates
if ([self isKindOfClass:[UITableView class]]) {
[self swizzleIfPossible:@selector(endUpdates)];
}
}
- (void)setEmptyDataSetDelegate:(id<DZNEmptyDataSetDelegate>)delegate
{
if (!delegate) {
// 移除无数据视图
[self dzn_invalidate];
}
// 此处为运行时的动态绑定,常见于为Categary添加属性的代码中
objc_setAssociatedObject(self, kEmptyDataSetDelegate, [[DZNWeakObjectContainer alloc] initWithWeakObject:delegate], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
我们需要注意的地方
// We add method sizzling for injecting -dzn_reloadData implementation to the native -reloadData implementation
[self swizzleIfPossible:@selector(reloadData)];
// Exclusively for UITableView, we also inject -dzn_reloadData to -endUpdates
if ([self isKindOfClass:[UITableView class]]) {
[self swizzleIfPossible:@selector(endUpdates)];
}
先来看下这个方法的实现
- (void)swizzleIfPossible:(SEL)selector
{
// Check if the target responds to selector
if (![self respondsToSelector:selector]) {
return;
}
// Create the lookup table
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.
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!!
if (impValue || !key || !baseClass) {
return;
}
// Swizzle by injecting additional 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
NSDictionary *swizzledInfo = @{DZNSwizzleInfoOwnerKey: baseClass,
DZNSwizzleInfoSelectorKey: NSStringFromSelector(selector),
DZNSwizzleInfoPointerKey: [NSValue valueWithPointer:dzn_newImplementation]};
[_impLookupTable setObject:swizzledInfo forKey:key];
}
核心部分
// Swizzle by injecting additional implementation
Method method = class_getInstanceMethod(baseClass, selector);
IMP dzn_newImplementation = method_setImplementation(method, (IMP)dzn_original_implementation);
此处使用运行时的方式交换方法的实现,当我们调用
[self swizzleIfPossible:@selector(reloadData)];
之后,实际上已经交换了UITableView的reloadData的实现,当tableview或者collectionview需要调用reloadData时,真正调用的便是我们交换后方法dzn_original_implementation
再进一步看下dzn_original_implementation的实现
void dzn_original_implementation(id self, SEL _cmd)
{
// Fetch original implementation from lookup table
Class baseClass = dzn_baseClassToSwizzleForTarget(self);
NSString *key = dzn_implementationKey(baseClass, _cmd);
NSDictionary *swizzleInfo = [_impLookupTable objectForKey:key];
NSValue *impValue = [swizzleInfo valueForKey:DZNSwizzleInfoPointerKey];
IMP impPointer = [impValue pointerValue];
// We then inject the additional implementation for reloading the empty dataset
// Doing it before calling the original implementation does update the 'isEmptyDataSetVisible' flag on time.
[self dzn_reloadEmptyDataSet];
// If found, call original implementation
if (impPointer) {
((void(*)(id,SEL))impPointer)(self,_cmd);
}
}
在里面主要执行两个内容,第一个就是加载无数据时视图
[self dzn_reloadEmptyDataSet];
第二个则是调用原本的方法reloadData 或者是 endUpdates
// If found, call original implementation
if (impPointer) {
((void(*)(id,SEL))impPointer)(self,_cmd);
}
网友评论