iOS源码阅读之DZNEmptyDataSet

作者: 来者可追文过饰非 | 来源:发表于2019-05-13 13:56 被阅读32次
    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);
        }
    
    至此,DZNEmptyDataSet大致流程分析完毕,具体的部分大家可以下载源码去研读🐶

    相关文章

      网友评论

        本文标题:iOS源码阅读之DZNEmptyDataSet

        本文链接:https://www.haomeiwen.com/subject/iiklaqtx.html