iOS DZNEmptyDataSet源码解析

DZNEmptyDataSet是一个无数据时的空白页展示框架,支持自定义文字和图片,用起来很方便。最近研究了一下它的源代码,学习到了很多编程上的技巧。

基本用法以UITableView为例,UITableView只需要遵循DZNEmptyDataSet的代理方法,代理方法设置空白页显示文字和图片即可,具体这里不详细讲解:

_table.emptyDataSetSource = self;
_table.emptyDataSetDelegate = self;

此框架其实就是UIScrollView类的一个分类,因为UITableView,UICollectionView都继承自UIScrollView,设计思路是给UIScrollView动态添加代理属性,在setter方法中判断当前类是否可以对reloadData进行Swizzle,也就是自定义方法的注入。作者没有用 method_exchangedImplementations() 方法进行方法交换,因为目的就是指明了要对原生的reloadData方法进行修改,所以为了防止reloadData方法在其他第三方里被提前替换了,作者这里建了一张表,以key:value 的形式对原生方法进行储存, 用C函数定义IMP,假如其他第三方用method_exchangedImplementations()方法对reloadData做了运行时的替换,[self swizzle_reloadData]虽然调用的是原生方法,但是在IMP接收到的 _cmd参数不是@selector(reloadData)而是@selector(swizzle_reloadData)(这里可以查看我的上一篇文章),这时用查询表在IMP中进行检测判断_cmd是否是原生方法,是的话在调用原生方法之前插入自己的操作;不是就返回。

 

就从这两个代理入手,第一个emptyDataSetSource点进去:

是其中的一个属性,其set方法为:


objc_setAssociatedObject动态添加set方法,关键部分在于[self swizzleIfPossible:@selector(reloadData)];

方法的字面意思是:是否可以进行swizzle;因为UIScrollView不响应reloadData方法。上面注释说:我们添加swizzing方法是为了在原生的reloadData方法的实现里注入-dzn_reloadData的实现。也就是在原生方法 reloadData 之前添加自定义的方法实现。接下来看具体操作:

- (void)swizzleIfPossible:(SEL)selector
{
    // Check if the target responds to selector
    //检查当前类是否实现了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.
    //确保每个类中当前的selector的实现方法被调用了一次
    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;
            }
        }
    }
    
    //如果查询表单中啥都没有,那么拿到当前的类,一个以当前类名和方法名复合成的字符串key,反向查找对应的method中的IMP值是否存在
    //获取当前类
    Class baseClass = dzn_baseClassToSwizzleForTarget(self);
    //这里是封装了一个方法,拼接baseClass和selector成为一个字符串,如:UITableView_reloadData
    NSString *key = dzn_implementationKey(baseClass, selector);
    //查看一下这个key有没有在列表里存在过
    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代替,method_setImplementation()返回的还是原生方法只不过IMP改变了实现方式
    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];
}

dzn_original_implementation是一个C语言方法:

//此方法是获取method中的IMP指针,并在指针指向原生方法之前注入自定义方法
void dzn_original_implementation(id self, SEL _cmd)
{
    // Fetch original implementation from lookup table
    // _cmd代表当前调用方法,注意:如果项目中其他地方有用method_exchangedImplementations()对reloadData进行交换的,_cmd 传过来的就是新的方法名
    Class baseClass = dzn_baseClassToSwizzleForTarget(self);
    //类名和方法名组成一个key
    NSString *key = dzn_implementationKey(baseClass, _cmd);
    //查看这个key有没有对应的信息
    NSDictionary *swizzleInfo = [_impLookupTable objectForKey:key];
    NSValue *impValue = [swizzleInfo valueForKey:DZNSwizzleInfoPointerKey];
    //这里正常情况下获取的是原生reloadData方法的IMP指针
    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);
    }
}

上面的操作只有一个目的:看有没有人篡改reloadData的_cmd,表里存的是以@“UITableView_reloadData”为key的信息,如果_cmd被改变,这个key也会变,查不到对应的信息。

[self  dzn_reloadEmptyDataSet] 函数

- (void)dzn_reloadEmptyDataSet
{

    //dzn_canDisplay里面是判断当前类是否是UITablVeiw或者UICollectionView类,是否遵循了DZN的代理方法,不遵循不满足则返回
    if (![self dzn_canDisplay]) {
        return;
    }

    //dzn_itemsCount返回一个NSInteger类型,里面是对原生代理方法的拦截,比如获得sections个数,items个数;只有等于0的时候才显示空白布局
    //下面操作就是什么时候开始布局,怎么布局等
    
    if (([self dzn_shouldDisplay] && [self dzn_itemsCount] == 0) || [self dzn_shouldBeForcedToDisplay])
    {
        // Notifies that the empty dataset view will appear
        [self dzn_willAppear];
        
        DZNEmptyDataSetView *view = self.emptyDataSetView;
        
        if (!view.superview) {
            // Send the view all the way to the back, in case a header and/or footer is present, as well as for sectionHeaders or any other content
            if (([self isKindOfClass:[UITableView class]] || [self isKindOfClass:[UICollectionView class]]) && self.subviews.count > 1) {
                [self insertSubview:view atIndex:0];
            }
            else {
                [self addSubview:view];
            }
        }

    ......
}

以上就是DZN的核心代码部分,主要是C函数定义IMP检查_cmd有没有被修改和查表建表的设计。具体的属性添加,方法绑定,方法拦截等设计有时间了再写出来。

如果有写的不当的地方,还请大佬们多多指教。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值