开源学习之DZNEmptyDataSet
这是一个为UITableView、UICollectionView在无数据状态下提供空态展示视图的组件,由于他们的父类都是UIScrollView,所以作者通过为UIScrollView提供分类接口来达到兼容两者的效果,接口头文件很简洁,如下:
1 | @interface UIScrollView (EmptyDataSet) |
分类本身很简单,需要关注的就是其中的两个协议
和一个方法
。两个协议中一个是代理属性,用来传递空态下视图的交互状态,还有一个是数据源属性,用来定制空态的显示效果,另外的方法就是刷新是否要展示空态视图。
DZNWeakObjectContainer
一般来说,对于代理、数据源这些属性,会通过使用week修饰来达到破除循环引用,在头文件中,作者也是这么修饰代理和数据源的,但是,同以往为已有类添加分类属性不同的是,作者这里使用一个弱引用对象来防止循环引用。
举设置数据源为例,在分类的setter方法中,使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
来修饰要添加的分类属性,这个属性是DZNWeakObjectContainer
类的示例,它本身会弱引用传入的datasource对象。
1 | - (void)setEmptyDataSetSource:(id<DZNEmptyDataSetSource>)datasource |
因此,看起来是为UIScrollView添加一个遵守DZNEmptyDataSetSource
协议的属性,但是内部却是一个DZNWeakObjectContainer
类的实例属性,而这个Container类的属性会弱引用遵守协议的实例对象。
通过其头文件也可以验证这点:
那么在UIScrollView分类对数据源属性的getter方法中,其实就是通过将Container解包,传递出去其弱引用的对象。
1 | - (id<DZNEmptyDataSetSource>)emptyDataSetSource |
除此之外,该类在组件中就没有其他用途了。
swizzle
上面在设置数据源和代理的时候,刻意没有提及其他的内容,其实在数据源的setter方法中除了设置关联对象,还有进行swizzle,完整的setter方法如下:
这里对reloadData方法和UITableView的endUpdates方法下钩子,和以往简单的swizzle不同的是,作者在这里使用了一个记录表(_impLookupTable)来确保每个类只被hook了一次,目前只有UITableView和UICollectionView两个类。
_impLookupTable
本身是一个全局的可变字典,这个字典以当前被hook的类名加SEL为key,以一个字典为value,具体的数据格式如下:
其中DZNSwizzleInfoPointerKey存储的是dzn_newImplementation
他其实是方法的实现指针:
1 | // Swizzle by injecting additional implementation |
具体的为_impLookupTable
添加元素的方法如下:
1 | // in -swizzleIfPossible: |
作者提到了一些关于swizzle的相关文章:right-way-to-swizzle 和 JUSEmptyViewController。
当_impLookupTable
有了数据之后,每次调用数据源的setter方法都会先根据类名和SEL进行判断,如果已经存在就不进行处理。
这里作者做了2个判断。
第一个是如果对应class和SEL已经存在了就不处理:
第二个是如果该class和SEL对应下的实例存在,也不处理:
dzn_reloadEmptyDataSet
上面在UIScrollView分类中的reloadEmptyDataSet
方法内部其实现逻辑是dzn_reloadEmptyDataSet
方法,而这个方法也是将reloadData方法hook
之后的替换方法,所以组件会在这个方法里面处理空态数据。
根据一系列的代理方法以及计算UITableView或者UICollectionView是否有数据之后,进入展示空态界面的逻辑中,这一系列方法列举如下:
在进入到展示空态界面的逻辑中,作者提供了一个私有默认的空态视图类:DZNEmptyDataSetView
,该类也是UIScrollView的一个私有分类属性:emptyDataSetView
,提供了市面上常见软件空态视图中的控件组合。
首先会去查看这个emptyDataSetView
是否有父类,判断是否要添加到当前ScrollView上,作者在每一次reloadEmptyDataSet
的时候都会将emptyDataSetView中已有的数据和布局进行重置:
1 | // in DZNEmptyDataSetView.m |
除了默认的空态视图,还提供了可以自定义的视图,假如没有使用自定义视图,组件内部会根据设置的空态数据源对emptyDataSetView进行UI上的处理,主要就是文本、图片、位置、颜色等。
上面是根据数据添加空态视图,移除空态视图的逻辑其实有三个地方:
- UIScrollView空态数据源的setter方法中
- UIScrollView空态代理的setter方法中
- 还有一个是在上面的dzn_reloadEmptyDataSet方法中如果不需要添加空态视图
由于dzn_reloadEmptyDataSet方法是对reloadData方法的hook,所以emptyDataSetView的添加和移除可以实时根据数据来完成。
在添加emptyDataSetView的时候,作者还为其添加了一个Tap手势,看效果也仅仅是为代理提供是否可以执行点击逻辑使用。
life cycle
在组件内部,作者提供了一套空态视图的生命周期,会传递给空态代理,他们分别是:
- emptyDataSetWillAppear
- emptyDataSetDidAppear
- emptyDataSetWillDisappear
- emptyDataSetDidDisappear
这些方法会在相应的位置来传递状态,就不详细的展开了。
参考文章
- 参考文章列表