这是 2017/05/26 我在知乎 iOS 团队内部做的一次分享

第一次做技术分享,最大的问题还是出在准备上。确定了分享的时间,但是到底要讲些什么内容内心却一直没有一个准确的范围,结果就是到了前一天还是往里面加东西,前一刻还在修改 Kyenote。分享的时候因为有很多写代码的环节,所以一开始有些小紧张,手还发抖,不过到后来慢慢好起来了。本来想把实况录屏全程记录下来的,但是因为 Ariplay 的录屏貌似有些小问题,为了不耽误大家时间,我就直接放弃了,还是有些小遗憾。

以下是 Keynote 和分享的主要内容

Functional Reactive Programming in Swift

What?

Functional Programming

First-class function

Treats functions as first-class citizens

Higher-order function

A function that does at least one of the following:

  • takes one or more functions as arguments
  • returns a function as its result

Pure function

Function which has no side-effects

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// pure function
func addTen(_ a: Int) -> Int {
return a + 10
}
// higher order function
func twice(_ f: @escaping (Int) -> (Int)) -> (Int) -> (Int) {
return {
f(f($0))
}
}
// first-class citizen
let addTenTwice = twice(addTen)
addTenTwice(10) //30
let addTenFourTimes = twice(addTenTwice)
addTenFourTimes(10) //50
// a little more harder
func multiplyBySelf(_ a: Int) -> Int {
return a * a
}
let g = twice(multiplyBySelf)
g(3) // 81
twice(g)(3) // 43046721
let a = 3 * 3 //9
let b = a * a //81
let c = b * b //6561
let d = c * c //43046721

Reactive Programming

Asynchronous Data Streams

1
--a---b-c---d---X---|->
  • A stream is a sequence of ongoing events ordered in time
  • Everything can be a stream
    • touch event
    • KVO
    • Notification
    • callback
    • Network response
    • timer

Functional + Reactive

Stream

  • Like an Array, it can hold anything
  • Unlike an Array, you can’t access it anytime you want, instread, you get notified when it’s value get changed
  • Like a pipe, if you missed the thing through it, it’s gone forever

Transformation

  • Change a stream to another stream, just like change a sequence to another
  • Higher-order functions, map, filter, reduce, flatMap, etc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
let reviewers = ["kimi", "qfu", "dhc", "x", "gaoji"]
// implement our own trnasformation functions
extension Array {
func xy_map<T>(_ transform: (Element) -> T) -> [T] {
var result: [T] = []
for i in self {
result.append(transform(i))
}
return result
}
func xy_filter(_ condition: (Element) -> Bool) -> [Element] {
var result: [Element] = []
for i in self {
if condition(i) {
result.append(i)
}
}
return result
}
func xy_reduce<T>(_ initialValue: T, _ combine: (T, Element) -> T) -> T {
var value = initialValue
for i in self {
value = combine(value, i)
}
return value
}
}
reviewers.xy_map {
$0.uppercased()
}
reviewers.xy_filter {
$0.characters.count > 3
}
// chain transformations
reviewers
.xy_filter { $0.characters.count > 3 }
.xy_reduce("") { return $0 + "\($1) review my code please~\n" }
// the original value hasn't been changed
reviewers
// a little bit about flatMap
let xxs = [[1, 2], [3, 4], [5, 6]]
let xso = [1, 2, 3, nil, 5]
// flatMap has 2 signature
xxs.flatMap { arr in
arr.map {$0}
} // [1, 2, 3, 4, 5, 6]
xso.flatMap {
$0
} // [1, 2, 3, 5]

Binding

Binding makes program more reactive

in Swift!

  • Functional language
  • Compiler & strong typed

Functional + Reactive + Swift, write awesome program!

Why

Good

  • Improve productivity
  • Less and more centralised code
  • Easy to maintain
  • Avoid complexity with mutable state growing over time
  • Change the way you think when coding

Bad

  • Learning curve is steep, but not that steep
  • Hard to debug

The benefits it brings are worth we give it a try

How

Unserstand the basic reactive unit

Observable

It send messages

Subscriber

It consume messages

Observables are like Sequence,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Observables are like Sequence
let xs = [1, 2, 3, 4, 5]
// iterate a sequence
for x in xs {
print(x)
}
// the operation above equals
var xsIte = xs.makeIterator()
while let x = xsIte.next() {
print(x)
}
// we can use Sequence feature to make a CountDown
struct CountDown: Sequence, IteratorProtocol {
var num: Int
var notify: (Int?) -> ()
mutating func next() -> Int? {
notify(num)
if num == 0 {
return nil
}
defer {
num -= 1
}
return num
}
}
var ite = CountDown(num: 10) {
// as a subscriber, we are consuming messages
print($0)
}.makeIterator()
// now it's kind like a stream
// once next() called, it'll print the latest value, it's reactive now
ite.next() //10
ite.next() //9
ite.next() //8

How can we make our own Observable/Subscriber pattern?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import Foundation
import UIKit
class KeyValueObserver<A>: NSObject {
let block: (A) -> ()
let keyPath: String
let object: NSObject
init(object: NSObject, keyPath: String, _ block: @escaping (A) -> ()) {
self.block = block
self.keyPath = keyPath
self.object = object
super.init()
object.addObserver(self, forKeyPath: keyPath, options: .new, context: nil)
}
deinit {
print("deinit")
object.removeObserver(self, forKeyPath: keyPath)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
block(change![.newKey] as! A)
}
}
class Observable<A> {
private var callbacks: [(A) -> ()] = []
var objects: [Any] = []
static func pipe() -> ((A) -> (), Observable<A>) {
let observable = Observable<A>()
return ({ [weak observable] value in
observable?.send(value)}, observable
)
}
private func send(_ value: A) {
for callback in callbacks {
callback(value)
}
}
func subscribe(callback: @escaping (A) -> ()) {
callbacks.append(callback)
}
}
extension UITextField {
func observable() -> Observable<String> {
let (sink, observable) = Observable<String>.pipe()
let observer = KeyValueObserver(object: self, keyPath: #keyPath(text)) {
sink($0)
}
observable.objects.append(observer)
return observable
}
}
var textField: UITextField? = UITextField()
textField?.text = "asd"
var observable = textField?.observable()
observable!.subscribe {
print($0)
}
textField?.text = "asdjlas"
textField?.text = "asdjk"
textField = nil
observable = nil

Integrate a reactive programming library

  • ReactiveSwift
  • ReactiveKit
  • RxSwift

Neither can goes wrong, but I prefer RxSwift because,

  • It’s a ReactiveX official Swift implementation which means
    • Developer won’t give it up (Maybe?)
    • You can easily switch to other platform
  • It has a greate community

Credits