Skip to content

Commit deb077b

Browse files
committed
Add content in 5.3 section
1 parent 672e4ee commit deb077b

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

chapter5/revisiting_functionalReactivePixels.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,106 @@ self.collectionViewDelegate = [[RACDelegateProxy alloc] initWithProtocol:@protoc
3939
4040
}];
4141
```
42+
我们也可以在`self`上调用`rac_signalForSelector:`,使用同样的block块。然而,我们有必要在视图控制器实现里提供一个空存根方法以避免编译器发出"实现不完全"之类的警告。
43+
44+
> 空存根方法:源于C++的一个非常不错的函数设计方法。在设计整个程序时,一般会先编写完所有的代码,然后开始编译和测试,但这样有时候会出现一大堆错误而不知从哪里入手,这时我们可以采用空存根技术。
45+
46+
> 存根是一个仅仅返回某个意义不大的值的空函数。存根可以用来测试整个程序的逻辑关系,以及分块实现程序的不同部分。
47+
48+
> 设计一个程序时,先分析设计程序的各个函数完成的功能;然后直接设计函数的存根并编译,编译通过,证明程序的逻辑关系没有问题的情况下,再来分别实现各个不同的函数(存根)。
49+
50+
接下来,我们有更多的机会来抽象这个类中的方法。`loadPopularPhotos`方法除了改变我们的状态之外,并没有什么卵用。如果`ReactiveCocoa`能够很好地监控这些状态,让我们不在这方面担心的话,那肯定是极好的!幸运的是,我恰好知道这个~
51+
52+
我们移除这个方法,在viewDidLoad中键入下面的代码来代码这个方法的调用:
53+
54+
```
55+
RACSignal *photoSignal = [FRPPhotoImporter importPhotos];
56+
RACSignal *photosLoaded = [photoSignal catch:^RACSignal *(NSError *error) {
57+
NSLog(@"Couldn't fetch photos from 500px : %@",error);
58+
return [RACSignal empty];
59+
}];
60+
RAC(self, photosArray) = photosLoaded;
61+
[photosLoaded subscribeCompleted: ^{
62+
@strongify(self);
63+
[self.conllectionView reloadData];
64+
}];
65+
66+
```
67+
68+
一开始我们只是进行了`importPhotos`方法调用,不同的是,我们用`signal`来存放其返回值。
69+
然后,我们“捕抓”这个信号上的错误并将它打印出来(跟我们之前做的一样,只不过语法不同而已)。比起`subscribeError:`方法,`catch:`方法处理的更为巧妙:它允许无错误值的信号穿透它,仅在信号有错误事件发生时才会调用它的block并发送其在发生错误时的返回值。这里我们使用`catch:`方法,来过滤无错误的值。这个`catch:`块仅仅返回一个空信号。更多关于这方面知识的细节[请参考StackOverFlow的问题](http://stackoverflow.com/questions/19439636/difference-between-catch-and-subscribeerror)
70+
71+
上面的方式,有一点点污染了我们的局部变量作用域,这可以用下面的更简洁的等效方法:
72+
73+
```Objective-C
74+
RAC(self, photosArray) = [[[[FRPPhotoImporter importPhotos]
75+
doCompleted:^{
76+
@strongify(self);
77+
[self.collectionView reloadData];
78+
}] logError] catchTo:[RACSignal empty]];
79+
80+
```
81+
使用RAC宏,我们创建了`photosLoaded`信号的最新值到`photoArray`属性的单向绑定。太好了,保持状态!
82+
83+
我们来看一下,我们的collectionViewCell的子类实现:
84+
85+
```Objective-C
86+
@interface FRPCell ()
87+
88+
@property (nonatomic, weak) UIImageView *imageView;
89+
@property (nonatomic, strong) RACDisposable *subscription;
90+
91+
@end
92+
93+
@implementation FRPCell
94+
95+
- (instancetype)initWithFrame:(CGRect)frame {
96+
...
97+
}
98+
99+
- (void)perpareForReuse {
100+
[super perpareForReuse];
101+
102+
[self.subscription dispose], self.subscription = nil;
103+
}
104+
105+
- (void)setPhotoModel:(FRPPhotoModel *)photoModel {
106+
self.subscription = [[[RACObserve(photoModel, thumbnailData) filter:^BOOL(id value) {
107+
return value != nil;
108+
}] map:^id(id value) {
109+
return [UIImage imageWithData:value];
110+
}] setKeyPath:@keypath(self.imageView, image) onObject:self.imageView];
111+
}
112+
113+
@end
114+
115+
```
116+
117+
这里有两个标志性的点表明了一个使用ReactiveCocoa来抽象的机会。
118+
119+
1. 我们有状态(`subscription`属性)
120+
2. 我们手动处理`RACDisposable`的生命周期
121+
122+
无论何时调用一个`RACDisposable`对象的`dispose`方法,就是一个**"这里有更加响应式的方法来作某件事"**的好信号。在我们的例子中,这种嗅觉是对的。
123+
124+
通过在`FRPCell`创建一个新的属性,我们能够抽象掉使用`prepareForReuse`方法的必要性。这个属性就是`photoModel`(我们之前的行为就像是一个只写的属性,现在它将变为可读写的了)。把属性放在文件顶部:
125+
126+
```
127+
@property (nonatomic, strong ) FRPPhotoModel *photoModel;
128+
```
129+
130+
下一步我们将彻底摆脱`setPhotoModel:`方法。我们将为`photoModel的thumbnailData`观察我们自己的关键路径。将下面的代码添加到cell的初始化函数中。
131+
132+
```Objective-C
133+
134+
RAC(self.imageView, image) = [[RACObserve(self, photoModel.thumbnailData) ignore:nil]
135+
map:^(NSData *data){
136+
return [UIImage imageWithData:data];
137+
}];
138+
139+
```
140+
141+
注意看我们观察的是`self`的`photoModel.thumbnailData`的关键路径,而非`self.photoModel`的`thumbnailData`的关键路径。这点微妙的区别,作用却大大不同。当`self`的属性`photoModel`或者`photoModel`的`thumbnailData`属性改变时,关键路径`photoModel.thumbnailData`将会收到一个被(这种变化所)引发的KVO消息。
142+
143+
现在我们总算彻底摆脱了`subscription`属性!
42144

0 commit comments

Comments
 (0)