翻译:ReactiveCocoa for a better world

ReactiveCocoa创造更美好的世界
原文:ReactiveCocoa for a better world

首先说一下为什么要翻译这篇2012年的文章。ReactiveCocoa for a better world是由Josh Abernathy在2012年发表,那个时间刚好是ReactiveCocoa开源的时间,而作者Josh Abernathy正是这一开源框架的主要作者,在这篇文章中Josh Abernathy从多个角度为我们介绍了为什么要使用ReactiveCocoa以及ReactiveCocoa能为我们做什么,在这篇文章中你可能会看到一些其他介绍ReactiveCocoa文章的影子,或者更应该说是在一些其他的关于ReactiveCocoa文章中看到ReactiveCocoa for a better world的影子。因为它的影响太深了,在时隔多年重新看到它后我有了把它翻译成中文的想法。

原生App花费了大量的时间来等待和响应,我们等待用户在UI界面上做一些操作,等待网络请求返回响应,等待异步操作的完成,等待一些依赖数据发生改变,然后做出响应。

但是所有的这些事情-所有的这些等待和响应-通常都会有许多不同的处理方式,这让我们在使用统一的方法来推断他们,chain them,或者是完成它们来变得非常困难。其实,我们可以通过高级方法来做的更好。

这就为什么我们开源这个神奇的魔法: GitHub for Mac: :ReactiveCocoa

RAC是一个组合和转换序列值得框架。

认真讲,他是什么?


让我们来看一些干货,RAC给我们带来了很多非常酷的东西:

  1. 组合操作一些未来数据的能力
  2. 最小化的使用状态量和可变数据的能力
  3. 用一种方式定义行为和属性之间的联系
  4. 一个统一的,更高级的异步操作接口
  5. 在KVO之上封装的友好的API

这些功能看起来可能像是随机的,直到你了解到RAC可以处理这些包含了将要等待的一些新值和响应的所有事件。

最美妙的地方在于RAC是那个能够适应许多不同的,常见的场景。
说了这么多,让我们来看看RAC实际上的样子

Examples


RAC可以将遵循了KVO-compliant属性利用KVO(key-value observing)l来带来序列的值.举个例子,我们可以看到username属性的变化

1
2
3
[RACAble(self.username) subscribeNext:^(NSString *newName) {
NSLog(@"%@", newName);
}];

That’s cool,但是如果只有这些,那它只是一个比KOV更友好的API,RAC最酷的地方在于我们可以组合序列来一表示一些复杂的行为。
假设我们想要检查用户输入的一个特殊用户名,当用用户输入前三个值之内的时候:

1
2
3
4
5
6
7
8
9
[[[RACAble(self.username)
distinctUntilChanged]
take:3]
filter:^(NSString *newUsername) {
return [newUsername isEqualToString:@"joshaber"];
}]
subscribeNext:^(id _) {
NSLog(@"Hi me!");
}];

我们发现username发生了变化,使用filter过滤发生的变化,使用take取前三个发生变化的值,如果新值是joshaber,我们就打印一个“hi mi

所以呢?


考虑一下,如果我们不使用RAC来完成这个操作,我们将不得不:

  • 为username添加上KVO进行监听
  • 添加一个属性来记住我们通过KVO监听到的发生变化的最后一个值
  • 添加一个属性来记录我们接收到了多少个发生了变化的值
  • 任何时间获取到发生变化的值得时候都要添加属性
  • 对数据进行比较

还有其他的么?

我们可以组合序列:

1
2
3
4
5
6
7
8
[[RACSignal
combineLatest:@[ RACAble(self.password), RACAble(self.passwordConfirmation) ]
reduce:^(NSString *currentPassword, NSString *currentConfirmPassword) {
return [NSNumber numberWithBool:[currentConfirmPassword isEqualToString:currentPassword]];
}]
subscribeNext:^(NSNumber *passwordsMatch) {
self.createEnabled = [passwordsMatch boolValue];
}];

当任何时间password和passwordConfirmation发生变化的时候,我们组合这两个属性最后的值,然后判断这两个值是否符合我们的要求,最后返回一个BOOL。我们就可以通过这个BOOL结果来设置button是否可以点击

Bindings

We can adapt RAC to give us powerful bindings with conditions and transformations:

1
2
3
4
5
RAC(self, help) = [[RACObserve(self.helpLabel, text) filter:^(NSString *newHelp){
return newHelp != nil;
}] map:^(NSString *newHelp){
return [newHelp uppercaseString];
}]

绑定help label的值得到属性help上,当流出的新值不是nil,就将这个新值转换成小写。(因为用户总喜欢being YELLED AT)、

Async

RAC同样非常适合做异步操作

举个例子,我们通过调用一个block来完成多次并发操作:

1
2
3
4
5
[[RACSignal
merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]]
subscribeCompleted:^{
NSLog(@"They're both done!");
}];

或是 大专栏  翻译:ReactiveCocoa for a better world链式调用异步操作:

1
2
3
4
5
6
7
8
9
10
11
[[[[client
loginUser]
flattenMap:^(id _) {
return [client loadCachedMessages];
}]
flattenMap:^(id _) {
return [client fetchMessages];
}]
subscribeCompleted:^{
NSLog(@"Fetched all messages.");
}];

用户登录后,首先加载缓存的消息,然后获取远程的消息,然后打印“Fetched all messages”

我们也可以方便的讲执行的操作移动到background queue

1
2
3
4
5
6
7
8
9
10
11
12
[[[[[client
fetchUserWithUsername:@"joshaber"]
deliverOn:[RACScheduler scheduler]]
map:^(User *user) {
// this is on a background queue
return [[NSImage alloc] initWithContentsOfURL:user.avatarURL];
}]
deliverOn:RACScheduler.mainThreadScheduler]
subscribeNext:^(NSImage *image) {
// now we're back on the main queue
self.imageView.image = image;
}];

或者可以更简单的处理潜在的竞争条件,比如,我们可以使用异步操作中通过结果来更新属性,但是除非这个属性在异步操作完成前没有发生变化

1
2
3
4
[[[self
loadDefaultMessageInBackground]
takeUntil:RACAble(self.message)]
toProperty:@keypath(self.message) onObject:self];

How does it work?

RAC从根本上来看是非常简单的,它是以信号的方式来流转数据。Until you reach turtles.

Subscribers来订阅signals,Signals为它的订阅者发送 ’next’, ‘error’, ‘completed’事件,如果所有的事情都是signals来发送时间,那么关键的问题就编程了:这些事件会在什么时候被发送?

Creating Signals

信号根据合适发送时间来定义自己的行为,我们可以通过+[RACSignal createSignal:]来创建自己的signal:

1
2
3
4
5
6
RACSignal *helloWorld = [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"Hello, "];
[subscriber sendNext:@"world!"];
[subscriber sendCompleted];
return nil;
}];

当这个signal获取一个新的订阅者,+[RACSignal createSignal:]会通过这个block进行回调。这个新的订阅者是通过里边的block我们可以发送这个事件。在上边的例子中,我们创建了一个signal会发送一个“hello”和“world”,然后发送完成。

嵌套signal

我们可以基于helloWorld signal来创建另一个signal:

1
2
3
4
5
6
7
8
9
10
11
RACSignal *joiner = [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
NSMutableArray *strings = [NSMutableArray array];
return [helloWorld subscribeNext:^(NSString *x) {
[strings addObject:x];
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
[subscriber sendNext:[strings componentsJoinedByString:@""]];
[subscriber sendCompleted];
}];
}];

现在我们有了一个joiner signal,当有订阅者订阅了joiner,它会定义我们的helloworld signal.

它从helloworld和helloword完成时候增加了所有数据的接收,它将所有接收到的string连接在一起,然后发送他们和完成

在这种方式,我们可以创建signal来表达一些复杂的行为。

RAC实现了一套operations来恰好的实现他们,他们通过使用一些默认的行为来获取一些source signal 和返回一个新的signal

More info

ReactiveCocoa可以用于MAC和iOS开发,可以查看README来获取更多的信息,然后来导出MAC demo project 来获得一些实际的例子

对于.NET 开发者,所有这些听起来会很熟悉, ReactiveCocoa实际上是一个Objective-c版本的.NET Reactive Extensions)(Rx).

大多数的Rx规则应用于RAC上是非常合适的,这里是一些非常好的Rx资源:

上一篇:acm新生赛-who is better


下一篇:A Better Finder Rename 10 Mac都含有哪些有趣的功能呢?