Skip to content

Commit 74b906f

Browse files
committed
Add Content
1 parent 7dc581c commit 74b906f

File tree

1 file changed

+129
-1
lines changed

1 file changed

+129
-1
lines changed

chapter6/mvvm_in_practice.md

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,135 @@ self.title = [self.viewModel.initialPhotoModel photoName];
137137

138138
```
139139

140-
这样做的另一个优点是:业务逻辑不需要重复书写,而且也使得这个非常好进行单元测试。
140+
这样做的另一个优点是:业务逻辑不需要重复书写,而且也使得业务逻辑非常好进行单元测试。
141+
142+
最后,我们需要在高清视图控制器中设置该视图模型,否则屏幕上将不会显示任何东西。导航到我们的画廊视图控制器(那个我们实例化并推出高清视图控制器的地方)。用下面的代码来替换这个业务逻辑:
143+
144+
```Objective-C
145+
[[self rac_signalForSelector:@selector(collectionView:didSelectItemAtIndexPath:)
146+
fromProtocol:@protocol(UIcollectionViewDelegate)] subscribeNext:^(RACTuple *arguments) {
147+
@strongify(self);
148+
149+
NSIndexPath *indexPath = arguments.second;
150+
FRPFullSizePhotoViewModel *viewModel = [[FRPPhotoViewModel alloc]
151+
initWithPhotoArray:self.viewModel.model initialPhotoIndex:indexPath.item];
152+
153+
FRPFullSizePhotoViewController *viewController = [[FRPFullSizePhotoViewController alloc] init];
154+
155+
viewController.viewModel = viewModel;
156+
viewController.delegate = (id<FRPFullSizePhotoViewControllerDelegate>)self;
157+
158+
[self.navigationController pushViewController:viewController animated:YES];
159+
}];
160+
```
161+
在下一节开始之前,我们没有计划为视图模型撰写单元测试。下一节我们看到在视图模型上如何运行测试驱动开发的概念。现在我们来完成`FRPGalleryViewModel`吧,很基础。我们想要从视图控制器中抽象出来的逻辑是通过API加载`model`的数据内容。我们来看一下应该怎么做:
162+
163+
```Objective-C
164+
165+
@interface FRPGalleryViewModel : RVMViewModel
166+
167+
@property (nonatomic, readonly, strong) NSArray *model;
168+
169+
@end
170+
171+
```
172+
173+
基本的接口:将`model`申明为数组`NSArray`.接下来,我们简单实现它:
174+
175+
```Objective-C
176+
177+
//Utilities
178+
179+
#import "FRPPhotoImporter.h"
180+
181+
@interface FRPGalleryViewModel ()
182+
183+
@end
184+
185+
@implementation FRPGalleryViewModel
186+
187+
- (instancetype)init {
188+
self = [super init];
189+
if(!self) return nil;
190+
191+
RAC(self, model) = [[[FRPPhotoImporter importPhotos] logError] catchTo:[RACSignal empty]];
192+
193+
return self;
194+
}
195+
196+
@end
197+
198+
```
199+
200+
有争议的是,我们应该把从API加载数据的(RAC绑定的)逻辑放在初始化方法中,还是放在视图模型被激活的地方。接下来我们会讨论更多的关于激活的内容,但我想要展示给你们看这个视图模型到底能做到多简单。将直接在画廊视图控制器中加载数据内容的逻辑迁移到画廊的视图模型中是非常简单的:在视图控制器的初始化中初始化视图模型===》任何引用试图控制`self.model`属性的地方使用`self.viewModel.model`来代替即可。
201+
202+
我们可以进一步深挖视图模型的构造,甚至可以通过一系列的访问器把`model`的访问逻辑抽象出来,但在这个例子里就有点过多‘抽象’了。更重要的是你可以根据你的喜好将更多的或者更少的业务逻辑抽象到视图模型中。我发现,就我个人而言,这个架构使用的越多,业务逻辑抽象出来的越多,就意味着更轻量级的视图控制器以及高内聚和可测试的代码。
203+
204+
把注意力移到单元测试之前,我们来做多一次用视图模型来抽象业务逻辑的实践。
205+
206+
我们的最后一个例子是`FRPPhotoViewController`上的`FRPPhotoViewModel`:创建一个`RVMViewModel`的视图模型子类并放置在视图控制器中(很快我们会回到视图模型中)。
207+
208+
视图控制器的新的初始化方法如下:
209+
210+
```Objective-C
211+
- (instancetype)initWithViewModel:(FRPPhotoViewModel *)viewModel index:(NSInteger)photoIndex {
212+
213+
self = [self init];//NS_DESIGNATED_INITIALIZER
214+
if(!self) return nil;
215+
216+
self.viewModel = viewModel;
217+
self.photoIndex = photoIndex;
218+
219+
return self;
220+
}
221+
222+
```
223+
224+
确定导入必要的头文件并为视图模型申明私有属性。现在我们需要使用新的初始化方法初始化视图控制器。看一看视图控制器到页面视图控制器的方法`photoViewControllerForIndex:`.
225+
226+
```Objective-C
227+
- (FRPPhotoViewController *)photoViewControllerForIndex:(NSInteger)index {
228+
FRPPhotoModel *photoModel = [self.viewModel photoModelAtIndex:index];
229+
if(photoModel) {
230+
FRPPhotoViewModel *photoViewModel = [[FRPPhotoViewModel alloc] initWithModel:photoModel];
231+
FRPPhotoViewController *photoViewController = [[FRPPhotoViewController alloc] \
232+
initWithViewModel:photoViewModel
233+
index:index];
234+
235+
return photoViewController;
236+
}
237+
238+
return nil;
239+
}
240+
241+
```
242+
243+
新的初始化过程中我们创建了一个视图模型。
244+
245+
在我们的`viewDidLoad:`方法里,我们将使用这个新的视图模型为我们的图片视图提供数据,并且为用户显示图片的下载进度。这里有个貌似冲突的地方:图片的下载是视图的模型的业务逻辑之一,但视图什么时候显示开始加载数据(这个业务逻辑)视图模型中没有体现---记住一个好的视图模型不应该引用视图本身。那么我们如何来混合地使用这两个业务逻辑?
246+
247+
答案是我们借助视图模型的`active`状态来对付(上面的情况)。`RVMViewModel`提供了一个布尔属性`active`,当试图控制器变得"活跃"时(不管在语义的上下文里这是啥意思),在这里,我们可以在`viewWillAppear:``viewDidDisappear:`这些方法来设置这个属性。
248+
249+
```Objective-C
250+
- (void)viewWillAppear:(BOOL)animated {
251+
[super viewWillAppear:animated];
252+
253+
self.viewModel.active = YES;
254+
}
255+
256+
- (void)viewDidDisappear:(BOOL)animated {
257+
[super viewDidDisappear:animated];
258+
259+
self.viewModel.active = NO;
260+
}
261+
```
262+
相当简单吧,我们来看一下我们新的`viewDidLoad`方法:
263+
264+
```Objective-C
265+
266+
267+
```
268+
141269

142270

143271

0 commit comments

Comments
 (0)