说到图片浏览器,项目中比较常用的成熟框架有Objective-C版本的MWPhotoBrowser,IDMPhotoBrowser或者Swift版本的SKPhotoBrowser。
从核心功能来看,MWPhotoBrowser,IDMPhotoBrowser这两个框架,都很好地实现了对本地资源、相册资源、网络资源的获取与显示。并且很好地封装了网络和相册的获取方式,在我看来,这是他的优势,但同时,高度的集成也催生了一些不足。
这样做的优势不言而喻,调用者只需要很少的几行代码,就可以集成一个图片浏览器框架,省时省力。以MWPhotoBrowser为例,在不设置额外属性的情况下,只需要下面两行代码就可以创建:
1 | MWPhotoBrowser *browser = [[MWPhotoBrowser alloc] initWithPhotos:self.photos]; |
使用者只要关注如何提供MWPhotoBrowser所要展示资源就可以了,不需要做额外的操作,非常地简洁方便。
关于不足,由于MWPhotoBrowser内部实现了获取网络图片功能,在追求内部实现尽量精简的前提下,不可避免地要依赖加载图片的第三方库(SDWebImage)。如果原来项目并没有使用SDWebImage,而是用YYWebImage或者Kingfisher,那么使用MWPhotoBrowser便会引入冗余的框架,从而让项目额外增加了一种图片缓存机制,不利于内存以及磁盘使用率的优化。
对于相册资源的访问,MWPhotoBrowser内部也实现了通过PHAsset或者ALAsset获取相片的功能。不过一般来说,项目会有自己的一套相册选择器,进而会有相应的相册资源获取策略。所以以个人观点来看,如何获取相册资源,应该由使用者告知,而不是在框架内部自己实现一套,这样更加符合DRY。
接下来,我会针对上面的不足,实现一套兼容本地资源、相册资源、网络资源的简易图片选择器。
本文章对应的所有代码在仓库TBVImageBrowser中。
框架概览
TBVImageBrowser的主要组成如下:
从图一可以看出,TBVImageBrowserView持有了一个遵守TBVImageProviderManagerProtocol的对象。根据此持有的策略管理对象,可以通过抽象策略接口TBVImageProviderProtocol访问对应的具体策略类:TBVWebImageProvider、TBVLocalImageProvider、TBVAssetImageProvider和自定义的Provider。
实际上具体的策略都可以由使用者实现,也就是说图一中的TBVWebImageProvider、TBVLocalImageProvider、TBVAssetImageProvider都可以去除,只要提供遵守策略接口TBVImageProviderProtocol的具体策略类就行了。一般来说,访问资源的策略由使用者提供,因为使用者知道自己实际的获取方式。
从图二中可以看出,TBVImageBrowserView持有的策略管理对象的内部组成。只要遵守TBVImageProviderManagerProtocol协议,都可以成为策略管理对象。
除了以上几个协议,我还抽出了TBVImageIdentifierProtocol、TBVImageElementProtocol以及TBVImageProgressPresenterProtocol协议。
TBVImageProviderIdentifierProtocol的声明如下:
1 | @protocol TBVImageIdentifierProtocol <NSObject> |
identifier作为匹配Provider和资源类型的标志,是每个策略必须要实现的。
TBVImageElementProtocol的声明如下:
1 | @protocol TBVImageElementProtocol <TBVImageIdentifierProtocol> |
TBVImageElementProtocol遵守了TBVImageProviderIdentifierProtocol协议,提供解析自身资源的Provider标志。resource用来存储实际需要获取的资源,progress则表示获取的进度。
TBVImageProgressPresenterProtocol的声明如下:
1 | @protocol TBVImageProgressPresenterProtocol <NSObject> |
由于项目中可能有自己的一套loading progress控件,仅仅为了图片选择器而引入另一套控件是不划算的,所以BVImageBrowser的loading progress控件也让使用者来提供,尽量减少不必要依赖。
TBVImageProviderManager
TBVImageProviderManager帮助TBVImageBrowserView管理所有添加的策略,让TBVImageBrowserView得以关注其浏览业务本身,而不必掺杂获取资源的具体逻辑。
首先是添加删除策略接口:
1 | - (void)addImageProvider:(id<TBVImageProviderProtocol>)provider { |
TBVImageProviderManager中会声明一个providerMap字典,以策略的identifier作key,策略作为value。
接下来是获取资源的接口:
1 | - (RACSignal *)imageSignalForElement:(id<TBVImageElementProtocol>)element { |
TBVImageProviderManager根据element提供的identifier,去providerMap字典中查找匹配的策略,并调用策略接口,获取element的resource中存储的资源。
载入自定义loading progress控件
在加载一个loading progress控件时,我需要什么样的接口?
首先是控件本身,TBVImageBrowserView需要使用者创建这个控件的实体给TBVImageBrowserView,而控件的具体属性则由调用者在创建控件时一并设置。然后因为是loading progress控件,理所当然地应该提供设置progress的接口。由这两个需求催生TBVImageProgressPresenterProtocol协议,来对使用者提供的loading progress控件进行限定。
有了满足要求的控件,如何在内部进行创建?TBVImageBrowserView需要使用者提供控件对应Class,然后在内部以以下方式进行添加:
1 | - (void)setupProgressPresenter:(Class)progressPresenter{ |
至此,载入自定义的loading progress控件已经实现了。接下来以DACircularProgress控件为例,说明如何使用。
首先,创建DALabeledCircularProgressView的分类,然后在分类中遵守TBVImageProgressPresenterProtocol协议,并实现其中的接口:
1 | @implementation DALabeledCircularProgressView (TBVImageProgressPresenter) |
并且在初始化TBVImageBrowserView时,传入DALabeledCircularProgressView类:
1 | _configuration.progressPresenterClass = [DALabeledCircularProgressView class]; |
总结
TBVImageBrowser是在自己做IM发送相册图片时造的轮子,由于后期项目本身并没有使用SDWebImage,并且有一套自己访问相册的策略,所以MWPhotoBrowser并不是很符合自己的需求。
TBVImageBrowser遵循了一个原则:使用者应该知道自己如何得到资源,并向框架提供获取资源的方法,这样才能让框架具有更好的扩展性。
详细的使用方法在仓库说明中。