ReactiveCocoa And ReactiveSwift

ReactiveSwift(简称RAS),好处:

①. 简化响应式函数的模式

      在Swift中,我们有几种响应式的开发模式:target-action、代理、通知中心、KVO等。以上每个模式对应的场景不同,我们需要根据场景选择合适的模式。RAS的整合了所有的模式的特点,开辟了一套事件流(信号)-观察者(发送)的模式,将以上种种模式替代。

②.高度聚集的代码实现

        我们使用闭包的原因 -- 将相关代码在同一环境中实现异步执行,提升代码的可读性。RAS将这种思想用到极致。

③. 与MVVM设计模式结合使用写出优雅简洁的代码

        简化MVC中因项目逐渐庞大导致Controller中代码的沉冗,MVVM模式被我们喜欢,但是它带来的另一个问题是V(View)和VM(viewModel)之间的数据传递过于复杂。而RAS的数据绑定解决了这个问题,我们使用这两者的结合,能够写出很优雅的代码。


RAC 之 Signal

RAC-Signal(信号)就 RAC 来说是构造单元. 它代表我们最终将要收到的信息. 当你能将未来某时刻收到的消息具体表示出来时,你可以开始预先运用逻辑并构建你的信息流,而不是必须等到事件发生(紧迫的).

    信号会为了控制通过应用的信息流而获得所有这些异步方法(委托, 回调 block, 通知, KVO, target/action 事件观察, 等)并将它们统一到一个接口下.这只是直观理解. 不仅是这些, 因为信息会流过你的应用, 它还提供给你轻松转换/分解/合并/过滤信息的能力.   


信号:

信号

    信号是一个发送一连串值的物体. 但是我们这儿的信号啥也不干, 因为它还没有订阅者. 如果有订阅者监听时(已订阅)信号才会发信息. 它将会向那个订阅者发送0或多个载有数值的”next”事件, 后面跟着一个”complete”事件或一个”error”事件. (信号类似于其他语言/工具包中的 “promise”, 但更强大, 因为它不仅限于向它的订阅者一次只传递一个返回值. )

    正如我之前提到的, 如果觉得需要的话你可以过滤, 转换, 分解和合并那些值. 不同的订阅者可能需要使用信号通过不同方式发送的值.    



信号的由来:

信号是一些等待某事发生的异步代码, 然后把结果值发送给它们的订阅者. 你可以用RAC-Signal的类方法createSignal或者手动创建信号:

    在这里用一个具有成功和失败 block (伪造)的网络操作创建了一个信号. (如果我想让信号在被订阅时才让网络请求发生, 还可以用RACSignal的类方法defer. )我在成功的 block 里使用提供的subscriber对象调用sendNext:和sendCompleted:方法, 或在失败的 block 中调用sendError:. 现在我可以订阅这个信号并将在响应返回时接收到 json 值或是 error.

RACSignal *usernameValidSignal = RACObserve(self. viewModel, usernameIsValid);


创建信号



订阅者:

    订阅者就是一段代码, 它等待信号给它发送一些值, 然后订阅者就能处理这些值了. (它也可以作用于 “complete” 和 “error” 事件. )    

    这有一个简单的订阅者, 是通过向信号的实例方法subscribeNext传入一个 block 来创建的. 我们在这通过RACObserve()宏创建信号来观察一个对象上属性的当前值, 并把它赋值给一个内部属性    

- (void) viewDidLoad {

// . . . // create and get a reference to the signal

RACSignal *usernameValidSignal = RACObserve(self. viewModel, isUsernameValid);

// update the local property when this value changes

[usernameValidSignal subscribeNext: ^(NSNumber *isValidNumber) { self. usernameIsValid = isValidNumber. boolValue }];}

注意: RAC 只处理对象, 而不处理像BOOL这样的原始值. 不过不用担心, RAC 通常会帮你这些转换.

    幸运的是 RAC 的创造者也意识到这种绑定行为的普遍必要性, 所以他们提供了另一个宏RAC(). 与RACObserve()相同, 你提供想要与即将到来的值绑定的对象和参数, 在其内部它所做的是创建一个订阅者并更新其属性的值. 我们的例子现在看起来像这样:

- (void) viewDidLoad {

RAC(self, usernameIsValid) = RACObserve(self. viewModel, isUsernameValid);

}



转换数据流

    RAC 为我们提供的用于转换数值流的方法. 我们将会利用RACSignal的实例方法map.

    我们将 view-model 上的isUsernameValid发生的变化直接绑定到goButton的enabled属性上. 对alpha的绑定更酷, 因为我们正在使用map方法将值转换成与alpha属性相关的值. (注意在这里我们返回的是一个NSNumber对象而不是原始float值. 这基本上是唯一的污点: 你需要负责为 RAC 将原始值转化为对象, 因为它不能帮你导出来.

    订阅信号链时要明白重要的一件事是每当一个新值通过信号链被发送出去时, 实际上会给每个订阅者都发送一次. 直到意识到这就我们而言是有意义的, 信号发出的值不存储在任何地方(除了 RAC 在内部实现中). 当信号需要发送一个新的值时, 它会遍历所有的订阅者并给每个订阅者发送那个值. 

这为什么重要?这意味着信号链某处存在的任何副作用, 任何影响应用世界的转变, 将会发生多次. 这对新接触 RAC 的用户来说是意想不到的. (这也违反了函数式构建的理念-数据输入, 数据输出).

一个做作的例子可能是: 信号链某处的信号在每次按钮被按下时更新self中的一个计数器属性. 如果信号链有多个订阅者, 计数器的增长将会比你想的还要多. 你需要努力从信号链中尽可能剔除副作用. 当副作用不可避免时, 你可以使用一些恰当的预防机制

除副作用之外, 你需要注意带有昂贵操作和可变数据的信号链. 网络请求就是一个三者兼得的例子:

a. 网络请求影响了应用的网络层(副作用).

b. 网络请求为信号链引入了可变数据. (两个完全一样请求可能返回了不同的数据. )

c. 网络请求反应慢



Tweetboat Plus

让我们着眼于如何用 ReactiveCocoa 将 view-model 与视图控制器连接起来:

- (void) viewDidLoad {

[super viewDidLoad];

RAC(self. viewModel, username) = [myTextfield rac_textSignal];

RACSignal *usernameIsValidSignal = RACObserve(self. viewModel, usernameValid);

RAC(self. goButton, alpha) = [usernameIsValidSignal map: ^(NSNumber *valid) { return valid. boolValue ? @1 : @0. 5; }];

RAC(self. goButton, enabled) = usernameIsValidSignal;

RAC(self. avatarImageView, image) = RACObserve(self. viewModel, userAvatarImage);

RAC(self. userNameLabel, text) = RACObserve(self. viewModel, userFullName);

@weakify(self);

[[[RACSignal merge: @[RACObserve(self. viewModel, tweets), RACObserve(self. viewModel, allTweetsLoaded)]] bufferWithTime: 0 onScheduler: [RACScheduler mainThreadScheduler]] subscribeNext: ^(id value) { @strongify(self); [self. tableView reloadData]; }];

[[self. goButton rac_signalForControlEvents: UIControlEventTouchUpInside] subscribeNext: ^(id value) { @strongify(self); [self. viewModel getTweetsForCurrentUsername]; }];

}

-(UITableViewCell*)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {

// if table section is the tweets section

if (indexPath. section == 0) { MYTwitterUserCell *cell = [self. tableView dequeueReusableCellWithIdentifier: @"MYTwitterUserCell" forIndexPath: indexPath];

// grab the cell view model from the vc view model and assign it

cell. viewModel = self. viewModel. tweets[indexPath. row];

return cell;

} else {

// else if the section is our loading cell

MYLoadingCell *cell = [self. tableView dequeueReusableCellWithIdentifier: @"MYLoadingCell" forIndexPath: indexPath];

[self. viewModel loadMoreTweets];

return cell;

}

}

// MYTwitterUserCell

// this could also be in cell init

- (void) awakeFromNib {

[super awakeFromNib];

RAC(self. avatarImageView, image) = RACObserve(self, viewModel. tweetAuthorAvatarImage);

RAC(self. userNameLabel, text) = RACObserve(self, viewModel. tweetAuthorFullName);

RAC(self. tweetTextLabel, text) = RACObserve(self, viewModel. tweetContent);

}

RAC(self.viewModel, username) = [myTextfield rac_textSignal];

        在这我们用 RAC 库中的方法从UITextField拉取一个信号. 这行代码将 view-model 上的可读写属性username绑定到文本框上的用户输入的任何更新.    

RACSignal *usernameIsValidSignal = RACObserve(self. viewModel, usernameValid);

RAC(self. goButton, alpha) = [usernameIsValidSignal map: ^(NSNumber *valid) { return valid. boolValue ? @1 : @0. 5; }];

RAC(self. goButton, enabled) = usernameIsValidSignal;

    在这我们用RACObserve方法在 view-model 的usernameValid属性上创建了一个信号usernameIsValidSignal. 无论何时属性发生变化, 它将会沿着管道发送一个新的@YES或@NO. 我们拿到那个值并将其绑定到goButton的两个属性上. 首先我们将alpha分别对应 YES 或 NO 更新到1或0. 5(记着在这必须返回NSNumber). 然后我们直接将信号绑定到enabled属性, 因为 YES 和 NO 在这无需转换就能完美地运作.

RAC(self. avatarImageView, image) = RACObserve(self. viewModel, userAvatarImage);

RAC(self. userNameLabel, text) = RACObserve(self. viewModel, userFullName);

参考资料: ReactiveCocoa和MVVM入门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容