美文网首页学习思想为主,代码为辅iOS Developer
利用策略模式增强图片浏览器的扩展性

利用策略模式增强图片浏览器的扩展性

作者: tripleCC | 来源:发表于2016-09-15 14:08 被阅读521次

说到图片浏览器,项目中比较常用的成熟框架有Objective-C版本的MWPhotoBrowserIDMPhotoBrowser或者Swift版本的SKPhotoBrowser

从核心功能来看,MWPhotoBrowser,IDMPhotoBrowser这两个框架,都很好地实现了对本地资源、相册资源、网络资源的获取与显示。并且很好地封装了网络和相册的获取方式,这在我看来这是他的优势,同时也是他的不足。

这样做的优势不言而喻,调用者只需要很少的几行代码,就可以集成一个图片浏览器框架,省时省力。以MWPhotoBrowser为例,在不设置额外属性的情况下,只需要下面两行代码就可以创建:

MWPhotoBrowser *browser = [[MWPhotoBrowser alloc] initWithPhotos:self.photos];
[self.navigationController pushViewController:browser animated:YES];

使用者只要关注如何提供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的声明如下:

@protocol TBVImageIdentifierProtocol <NSObject>
@required
@property (strong, nonatomic, readonly) NSString *identifier;
@end

identifier作为匹配Provider和资源类型的标志,是每个策略必须要实现的。

TBVImageElementProtocol的声明如下:

@protocol TBVImageElementProtocol <TBVImageIdentifierProtocol>
@required
@property (strong, nonatomic) NSObject *resource;
@property (assign, nonatomic) CGFloat progress;
@optional
@property (strong, nonatomic) NSDictionary *options;
@end

TBVImageElementProtocol遵守了TBVImageProviderIdentifierProtocol协议,提供解析自身资源的Provider标志。resource用来存储实际需要获取的资源,progress则表示获取的进度。

TBVImageProgressPresenterProtocol的声明如下:

@protocol TBVImageProgressPresenterProtocol <NSObject>
+ (instancetype)presenter;
- (void)setPresenterProgress:(CGFloat)progress animated:(BOOL)animated;
@end

由于项目中可能有自己的一套loading progress控件,仅仅为了图片选择器而引入另一套控件是不划算的,所以BVImageBrowser的loading progress控件也让使用者来提供,尽量减少不必要依赖。

TBVImageProviderManager

TBVImageProviderManager帮助TBVImageBrowserView管理所有添加的策略,让TBVImageBrowserView得以关注其浏览业务本身,而不必掺杂获取资源的具体逻辑。

首先是添加删除策略接口:

- (void)addImageProvider:(id<TBVImageProviderProtocol>)provider {
    NSCParameterAssert(provider);
    NSAssert(provider.identifier, @"identifier of %@ can not be nil.", provider);
    TBVLogInfo(@"add provider %@", provider);
    
    @synchronized (self) {
        self.providerMap[provider.identifier] = provider;
    }
}

- (BOOL)removeImageProvider:(id<TBVImageProviderProtocol>)provider {
    NSAssert(provider.identifier, @"identifier of %@ can not be nil.", provider);
    TBVLogInfo(@"remove provider %@", provider);
    
    @synchronized (self) {
        [self.providerMap removeObjectForKey:provider.identifier];
        return [self.providerMap.allKeys containsObject:provider.identifier];
    }
}

TBVImageProviderManager中会声明一个providerMap字典,以策略的identifier作key,策略作为value。
接下来是获取资源的接口:

- (RACSignal *)imageSignalForElement:(id<TBVImageElementProtocol>)element {
    NSAssert(element.identifier, @"identifier of %@ can not be nil.", element);
    
    @weakify(self)
    return [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        @strongify(self)
        TBVLogInfo(@"\nimage resource:\n\t%@;\nidentifier:\n\t%@;\n", element.resource, element.identifier);
        if ([self.providerMap.allKeys containsObject:element.identifier]) {
            [subscriber sendNext:[self.providerMap[element.identifier]
                    imageSignalForElement:element
                    progress:^(CGFloat progress) {
                        element.progress = progress;
                    }]];
            [subscriber sendCompleted];
        } else {
            NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
            userInfo[kTBVImageBrowserErrorKey] =
            [NSString stringWithFormat:@"image provider with identifier %@ was not found", element.identifier];
            [subscriber sendError:[NSError errorWithDomain:@"TBVImageProviderManager"
                                                        code:-1
                                                    userInfo:userInfo]];
        }
        return nil;
    }]
        switchToLatest]
        catch:^RACSignal *(NSError *error) {
            TBVLogError(@"\nerror domain: \n\t%@; \nerror code: \n\t%ld; \nerror info: \n\t%@;\n", error.domain, error.code, error.userInfo);
            return [RACSignal empty];
    }];
}

TBVImageProviderManager根据element提供的identifier,去providerMap字典中查找匹配的策略,并调用策略接口,获取element的resource中存储的资源。

载入自定义loading progress控件

在加载一个loading progress控件时,我需要什么样的接口?

首先是控件本身,TBVImageBrowserView需要使用者创建这个控件的实体给TBVImageBrowserView,而控件的具体属性则由调用者在创建控件时一并设置。然后因为是loading progress控件,理所当然地应该提供设置progress的接口。由这两个需求催生TBVImageProgressPresenterProtocol协议,来对使用者提供的loading progress控件进行限定。

有了满足要求的控件,如何在内部进行创建?TBVImageBrowserView需要使用者提供控件对应Class,然后在内部以以下方式进行添加:

- (void)setupProgressPresenter:(Class)progressPresenter{
    if (self.progressView || !progressPresenter) return;
    
    if ([progressPresenter conformsToProtocol:@protocol(TBVImageProgressPresenterProtocol)]) {
        id presenter = [progressPresenter presenter];
        if ([presenter isKindOfClass:[UIView class]]) {
            self.progressView = presenter;
            [self.contentView addSubview:self.progressView];
            CGSize size = CGSizeEqualToSize(CGSizeZero, self.progressView.frame.size) ?
                CGSizeMake(40.0f, 40.0f) : self.progressView.frame.size ;
            [self.progressView mas_makeConstraints:^(MASConstraintMaker *make) {
                make.width.equalTo(@(size.width));
                make.height.equalTo(@(size.height));
                make.center.equalTo(self.contentView);
            }];
        } else {
            TBVLogError(@"progressPresenter should be subclass of UIView.");
        }
    } else {
        TBVLogError(@"progressPresenter should comfirm TBVImageProgressPresenterProtocol.");
    }
}

至此,载入自定义的loading progress控件已经实现了。接下来以DACircularProgress控件为例,说明如何使用。

首先,创建DALabeledCircularProgressView的分类,然后在分类中遵守TBVImageProgressPresenterProtocol协议,并实现其中的接口:

@implementation DALabeledCircularProgressView (TBVImageProgressPresenter)
+ (instancetype)presenter {
    DALabeledCircularProgressView *progressView = [[DALabeledCircularProgressView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
    progressView.thicknessRatio = 0.1;
    progressView.progressLabel.textColor = [UIColor whiteColor];
    progressView.progressLabel.font = [UIFont systemFontOfSize:12];
    progressView.userInteractionEnabled = NO;
    return progressView;
}

- (void)setPresenterProgress:(CGFloat)progress animated:(BOOL)animated {
    [self setProgress:progress animated:animated];
    if (progress != 0 && progress != 1) TBVLogDebug(@"load progress %f", progress);
    
    self.progressLabel.text = [NSString stringWithFormat:@"%.02f", progress];
}
@end

并且在初始化TBVImageBrowserView时,传入DALabeledCircularProgressView类:

_configuration.progressPresenterClass = [DALabeledCircularProgressView class];

总结

TBVImageBrowser是在自己做IM发送相册图片时造的轮子,由于后期项目本身并没有使用SDWebImage,并且有一套自己访问相册的策略,所以MWPhotoBrowser并不是很符合自己的需求。

TBVImageBrowser遵循了一个原则:使用者应该知道自己如何得到资源,并向框架提供获取资源的方法,这样才能让框架具有更好的扩展性。

详细的使用方法在仓库说明中,欢迎试用并一起完善这个项目。

相关文章

  • 利用策略模式增强图片浏览器的扩展性

    说到图片浏览器,项目中比较常用的成熟框架有Objective-C版本的MWPhotoBrowser,IDMPhot...

  • js常用设计模式总结

    策略模式:优点:减少不必要的判断,代码解偶,扩展性良好缺点:策略类增多,对外暴露 代理模式:优点:职责清晰、高扩展...

  • 【设计模式】之策略模式

    策略模式 策略模式属于行为模式,可以在运行时不修改类本身而通过变更内部算法来处理类的行为变更。这允许对象的可扩展性...

  • Spring中如何使用设计模式

    关于设计模式,如果使用得当,将会使我们的代码更加简洁,并且更具扩展性。本文主要讲解Spring中如何使用策略模式,...

  • 指数增强策略

    指数增强是什么意思? ​ 指数增强策略并不是被动的跟踪某个指数波动,而是采用量化增强模型,利用多因子alpha模型...

  • mootools无刷新上传文件(图片)插件

    无刷新上传的方法很多,比如: 利用现代浏览器的FormData。 利用现代浏览器的FileReader将图片文件编...

  • 设计模式(二十二) 策略模式

    有时候对象需要按照某种策略改变行为,我们可以利用策略模式,将策略或算法提取出来,作为单独的类实现。使用策略模式,可...

  • 第六章:使用一等函数实现设计模式

    6.1 案例分析:重构“策略”模式 如果合理利用作为一等对象的函数,某些设计模式可以简化,“策略”模式就是一个很...

  • Android利用Picasso+PhotoView+ViewP

    写一篇利用Picasso图片加载框架,PhotoView图片缩放控件以及ViewPager的图片浏览器 首先建立依...

  • 2019年《生涯线》作业三

    策略2:面对险境,利用毅力脱颖而出 【片断3】 主题:利用内外驱力增强毅力,达成生涯目标。 【R:原文阅读】 P...

网友评论

  • 33a02bf71691:item有多组的话 怎么整 我试着wm改的有点懵😳
  • 3bf782eaf5a0:IDM 本身也具有很强的可扩展性,并不像MW写的那么完善,你可以继承他的model或实现他的协议去写你自己的加载逻辑和decode逻辑

本文标题:利用策略模式增强图片浏览器的扩展性

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