上一篇我们主要从理论上讲述如何通过
转移依赖
来轻量化我们的
ViewController
,同时在
View
层和
Store
层之间传输数据。在这一篇中,我们将通过
Demo
来更清晰地描述
Minya
框架的实际操作,包括如何去构造
pipeline
,如何去构建三层对象对
pipeline
的依赖,以及数据如何通信。
Demo
的基本效果可查看公众号(这里无法上传视频)。
这里主要展示了两个页面,其对应的
ViewController
分别是
SearchViewController
和
InterestingnessViewController
。后面的示例主要以这两个类为主。
由于接口是基于
Frickr
的,所以如果想运行起来,需要去
Frickr
上注册一个应用,以获取
API KEY
和密钥,并填充到
AppDelegate.m
的下面一行代码中。(另外还请自备梯子)
[[FlickrKit sharedFlickrKit] initializeWithAPIKey:@"This is your API key" sharedSecret:@"This is the secret"];
Scene
在进入主题之前,先来了解一下
Scene
对象。我们将一个页面描述为一个
Scene
对象,其代码如下:
@interface MIScene : NSObject
@property (nonatomic, copy, nonnull) NSString *viewName; //!< view name
@property (nonatomic, copy, nonnull) NSString *controllerName; //!< controller name
@property (nonatomic, copy, nonnull) NSString *storeName; //!< store name
+ (instancetype _Nullable)sceneWithView:(NSString * _Nonnull)viewName controller:(NSString * _Nonnull)controllerName store:(NSString * _Nonnull)storeName;
@end
这个类很简单,只是用来组合一个页面的三层对象。这个类并不是必须的,只是为了表达清晰。注意这里三个属性的类型都是
NSString
,意味着我们将通过反射机制来创建一个
ViewController
对象及其关联的
Store
和
View
(同时也意味了更多的硬编码,我们会在后面说明)。
具体的创建过程在
MIMediator
中,代码如下所示:
- (UIViewController *)viewControllerWithScene:(MIScene *)scene context:(NSDictionary<NSString *,id> *)context callback:(MICallback)callback {
Class controllerClass = NSClassFromString(scene.controllerName);
Class storeClass = NSClassFromString(scene.storeName);
Class viewClass = NSClassFromString(scene.viewName);
id<MIStore> store = [[storeClass alloc] initWithContext:context];
return [[controllerClass alloc] initWithStore:store viewClass:viewClass callback:callback];
}
MIMediator
是页面跳转的一个中介者,主要是负责横向数据流操作,在这不多解释。
因此,创建及使用一个
Scene
看起来是下面这样:
MIScene *scene = [MIScene sceneWithView:@"SearchView" controller:@"SearchViewController" store:@"SearchStore"];
UIViewController *viewController = [[MIMediator sharedMediator] viewControllerWithScene:scene context:nil];
UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:viewController];
正如上一篇后面所说,
ViewController
对
View
层和
Store
层的了解仅限于一两个接口,而三层同时依赖于同一个
pipeline
,这就意味着如果两个
View
都依赖于同一个
pipeline
,那么这两个
View
可以相互替换,同理
ViewController
和
Store
也一样。这样,我们就可以根据
pipeline
来拼装三层对象。即如果
View
调整了,但整体展示的数据还是那些,那我们的
ViewController
和
Store
都不需要变动,在创建
Scene
时,我们换一个
View
名就行了。(当然这是一种理想状态)。
构造 pipeline
由于
View
、
ViewController
和
Store
都是依赖于
pipeline
,所以
pipeline
可以说是整个框架的核心。如何去构造
pipeline
,将决定整个结构的灵活性。可以从两个角度来考虑这个问题:
-
根据
View
层来构造pipeline
对于一个
App
来说,
View
层是和用户直接交互的,它即是用户输入数据的来源,也是业务数据的表述。采集和显示哪些数据,都需要根据
View
来确定。另外,一个复杂的
View
层可能由多个甚至多层子
View
构成,每个子
View
有不同的数据需求,所以它能更精细地去表述数据,包括页面点击这种
flag
数据。因此,我们可以根据视图的树形结构,构造一棵类似的
pipeline
树形结构。如下图所示:
这种方案还有一个好处,每一个子视图只需要依赖于其对应的
pipeline
即可,而不需要依赖于整个
pipeline
。
不过这种方案的问题在于,一旦
View
变更,将直接影响到
pipeline
的构造,进而可能影响
ViewController
及
Store
对
pipeline
属性的监听。
-
根据业务来构造
pipeline
这种方案是把业务相关的一组数据整合在一个
pipeline
对象里面(一个业务可能对应多个
View
),再把一个页面里面的多个
pipeline
组织成一棵
pipeline
树。
这种方式的优点是
pipeline
相对独立于
View
层,除了一些
View
相关的数据外,
pipeline
不会受
View
过多的影响。缺点是这种
pipeline
对数据的表述比较粗旷,
View
层可以监听到一些与其无关的数据。
在实际开发过程中,可以根据实际情况来确定使用哪种方案构造
pipeline
。构造
pipeline
的主要任务在
store
层中完成,因为这里是数据的处理中心。以
InterestingnessStore
为例,我们将
InterestingnessStore
的业务逻辑拆分到两个
store
中,每个
store
维护其自身的
pipeline
,然后在
InterestingnessStore
中构建起
pipeline
的层级结构。
InterestingnessPipeline.h
@interface InterestingnessPipeline : MIPipeline
@property (nonatomic, strong) TopImagePipeline *imagePipeline;
@property (nonatomic, strong) PhotoListPipeline *photoListPipeline;
- (void)setShowImageAtIndex:(NSUInteger)index;
@end
InterestingnessStore.m
@interface InterestingnessStore ()
@property (nonatomic, strong) InterestingnessPipeline *interestPipeline; // Pipeline
@property (nonatomic, strong) TopImageStore *imageStore; // Top image store
@property (nonatomic, strong) PhotoListStore *photoStore; // Photo List store
@end
@implementation InterestingnessStore
// ...
- (InterestingnessPipeline *)interestPipeline {
if (!_interestPipeline) {
_interestPipeline = [[InterestingnessPipeline alloc] init];
_interestPipeline.imagePipeline = self.imageStore.imagePipeline;
_interestPipeline.photoListPipeline = self.photoStore.photoListPipeline;
}
return _interestPipeline;
}
@end
以上
pipeline
层级结构与View
的层级结构对应,即我们采用方案一来构造pipeline
。
建立依赖
在
store
层中根据需求构造好
pipeline
后,就需要建立
ViewController
和
View
层对
pipeline
的依赖了,这个过程并不复杂,但是比较繁琐。这一操作主要是通过
ViewController
向上分发
pipeline
来完成的。我们再来看看
MIViewController
的实现。
@implementation MIViewController
// ...
- (void)viewDidLoad {
[super viewDidLoad];
// Set up pipeline
[self setupPipeline:self.store.pipeline];
[self.view setupPipeline:self.store.pipeline];
// Add observers of the pipeline data.
[self addObservers];
}
// ...
@end
在
- viewDidLoad
方法里面调用了两个
- setupPipeline
:
-
第一个是
ViewController
自身的方法,这可以构建ViewController
对pipeline
的依赖,当然如果ViewController
对pipeline
没有数据需求,则子类可以不实现这个方法; -
第二个是
ViewController
的根视图的设置方法,这个方法将pipeline
传递给View
层,View
层可以根据实际需要再去设置自身的pipeline
,以及将子pipeline
分发各子View
,例如InterestingnessView
的实现:
@interface InterestingnessView ()
@property (nonatomic, strong) InterestingnessPipeline *pipeline;
@property (nonatomic, strong) TopImageView *topImageView;
@property (nonatomic, strong) PhotoListView *photoListView;
@end
#pragma mark - InterestingnessView implementation
@implementation InterestingnessView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self addSubview:self.topImageView];
[self addSubview:self.photoListView];
// ...
}
return self;
}
- (void)setupPipeline:(__kindof MIPipeline *)pipeline {
self.pipeline = pipeline;
// 分发二级 pipeline
[self.topImageView setupPipeline:self.pipeline.imagePipeline];
[self.photoListView setupPipeline:self.pipeline.photoListPipeline];
}
// ...
@end
至此,
ViewController
及
View
对
pipeline
的依赖关系构建完成。构建完成后,各层就只需要与
pipeline
打交道了,从
pipeline
中读取数据,或者把数据写入
pipeline
中。
数据传输
最后来看看最主要的部分:数据传输。回到最上面的
Demo
,我们在第二个页面中点击列表中的一个单元格,顶部的图片信息就跟着变化。我们来看看这种变化时如何产生的。我们先从目标视图即
TopImageView
说起,这个
View
监听了
TopImagePipeline
的
url
属性: