Why This Post
毫无疑问的,ReactiveCocoa是iOS开发中极其强大的辅助,但是函数响应式编程的概念对于大多数人还是相当新颖,甚至有一些古怪的。Swift和ReactiveCocoa是绝配,但是现在已有的ReactiveCocoa的教程多是Objective-C的,也不是非常详细,因此只能摸索。这篇文章不是ReactiveCocoa的教程,而是作者探索的过程,希望对新接触这个框架的人有帮助。
函数响应式编程是什么
编程的范式从计算机程序刚开始时便有分歧,而随着计算机性能的不断增长和编译器的进化,原本由于执行效率低下而被认为是纯学术的函数式编程开始进入人们的视野。函数式编程中没有所谓的变量,一切都是不可变的。而响应式编程是基于数据流的,应用逻辑被抽象为管道。这两种思想对于现代的并发程序设计尤其有利,以至于MS,Google,Apple相继在自己的语言中支持函数式编程。这里就不再在学理上深究了,而是直接打开XCode,用代码来感受一下函数响应式编程的魅力。
安装ReactiveCocoa
作者自己由于初次接触外部的框架,折腾了很久才把ReactiveCocoa的框架安装完毕。
按照Github上的提示,作者用Carthage安装ReactiveCocoa。当把生成的.framework文件拖到Linked Frameworks and Libraries
下之后,发现在编辑器中无论如何都无法找到框架。最终找到Carthage的官方仓库,才知道还需要在Build Phases
一栏下加入一个Run Script
,并执行/usr/local/bin/carthage copy-frameworks
才可以。网上的Carthage教程少有提到这点的。
ReactiveCocoa的基本概念
官方的仓库中对整个框架有一个概览,可以参考这个。
Framework Overview
Operators Overview
建立一个简单的字符计数程序
这是这篇文章最终将要实现的效果。当用户输入时,下方的label上会显示出目前文本框中的字符数量。
接下来就来一步步完成这个程序。
UI布局
这是一个Single View Application,界面上方是一个UITextField
,用以输入字符;中间是一个UILabel
,用以显示目前已输入的字符数量。界面布局通过AutoLayout实现。和一般的程序不同的是,这里在连接UI和代码的时候,不是把Text Field用valueChanged
方法连接到ViewController
的一个selector上,而是把它也作为一个outlet连接。ReactiveCocoa已经设计了API来捕获事件,并在事件发生时发送一个signal。
核心代码
这里先把ViewController.swift
中所有的代码贴出来。由于作者自己还在摸索,代码或许不是最漂亮的写法。
import UIKit import ReactiveCocoa import Result class ViewController: UIViewController { @IBOutlet weak var outputLabel: UILabel! @IBOutlet weak var inputField: UITextField! override func viewDidLoad() { super.viewDidLoad() inputField.rac_textSignal() .toSignalProducer() .map{ text in text as! String} .throttle(0.5,onScheduler: QueueScheduler.mainQueueScheduler) .map {text in return "\(text.characters.count)" } .startWithNext { [weak self] text in if let strongSelf = self { strongSelf.outputLabel.text = text } } } }
这里主要就是在viewDidLoad
方法中设置程序逻辑,随后的事件处理全部交给ReactiveCocoa。这里有一个令人觉得好笑的事情,也就是说,每一个链式调用都必须换行,否则swift编译器会报一个莫名其妙的错误:Expression too complex to be resolved in reasonable time
。
下面就来逐一分析这些链式调用。
第一步,取出一个
rac_textSignal
这个signal会在文本框的值改变后发送,并且会包含当时文本框中的文本。第二步,把它变成一个
SignalProducer
这一步是官方文档上提到的,用来接收信号第三步,强制转换成
String
类型。官方的说明是由于支持ObjectiveC的原因,目前这个转换还是必需的(期待WWDC 16)第五步,把原本的字符串转化成记载原字符串字符个数的字符串。在这里可以把这个调用合并到上面的map中,但是随着程序越来越复杂,把这个转化放在过滤后面会显得愈发有必要。这里面有网路请求比如说。
第六步,处理信号。在这里就是让label显示字符串
注意到在第六步里面,用了一个[weak self]
修饰符。这是为了避免循环引用。这里要说明一下。闭包的循环引用在swift里面是一个很严重的语法陷阱。创建闭包时,self
持有一个对闭包的引用;而在闭包里面出现self
时,闭包也持有一个对self
的引用。这样就导致swift的基于ARC的内存管理机制失灵,闭包和self
都永远不会被释放。因此,需要用weak
来声明我们不希望闭包持有对self
的引用。在这个应用中,两者的确都不需要被释放,不用weak也不会有问题,但是仍然应该保持良好的编码习惯,,,
Cmd-R,应用正常运行了,,