iOS技术积累

不管生活有多不容易,都要守住自己的那一份优雅。

ReactiveCocoa-Swift部分入门指南-SignalProducer

学习过ReactiCocoa(以下简称RAC)的同学一般都会使用Objective-C的部分,不过RAC3之后支持了Swift,目前RAC3.x支持的是Swift1.x系列,RAC4支持的是Swift2.x系列。今天花了一点时间学习了下Swift部分示例代码。这里做些记录。Swift是支持playground,可以使用Markdown编写文档,并且所见即所得的界面方便学习。更可以插入图片。方便阅读。

学习知识必备

默认你已经学过RAC-OC部分, Swift语言,并对Monad,functional Programming有些简单的了解,或者,如果你学习了RXSwift更好。

Start

1.  git clone  git@github.com:ReactiveCocoa/ReactiveCocoa.git
    •   执行   script/bootstrap 脚本
    •   如果你安装了 [Cartheage](https://github.com/Carthage/Carthage) 使用  carthage checkout
2.  打开 ReactiveCocoa.xcworkspace
3.  编译 Result-Mac scheme
4.  编译 ReactiveCocoa-Mac scheme
5.  在workSpace目录中打开ReactiveCocoa.playground
6.  Choose View > Show Debug Area

PlaygroundUtility

先来观察一下这个里面有两个方法

public func scopedExample(exampleDescription: String, _ action: () -> Void) {
    print("\n--- \(exampleDescription) ---\n")
    action()
}

public enum Error: ErrorType {
    case Example(String)
}

scopedExample 方便测试,并分割日志输出,Error也是为了测试方便。

SignalProducer

一个信号发生器,是SignalProducer类型的实例,它可以创建信号(signals)并施加附作用(side effects)。

信号发生器用来表示操作或者任务,比如网络请求,每一次对它调用start()将会生成一个新的潜在操作,并允许调用者观察它的结果。还有一个startWithSignal()方法,会给出产生的信号,允许在必要的情况下被监听多次。

根据start()方法的动作方式,被同一个信号发生器生成的信号可能会有不同的事件顺序或版本,甚至事件流完全不一样!和普通的信号不同,在观察者连接上之前,信号发生器不会开始工作(也就没有事件会生成),并且在每一个新的监听器连接上时其工作都会重新开始一个单独的工作流

启动一个信号发生器会返回一个销毁器(disposable,我也不知道怎么翻译合适),它可用来打断或取消被生成信号的工作。

和信号一样,信号生成器可以通过map、filter等原函数操作。使用lift方法,所有信号的原函数可以被提升成为以信号生成器为对象的操作。除此以外,还有一些用来控制何时与如何启动信号生成器的原函数,比如times。

补充,这段我是参考自ReactiveCocoa 4 图解之六——信号发生器(SignalProducer),不过我觉得说的相当之晦涩,在学习这部份的时候,我已经学习了,RAC-OC部分,RXSwift,冷热信号等概念。突然出了一个SignalProducer的概念会让读者很难理解,其实很容易就发现,SignalProducer会带来附作用(冷信号),Signal不会带来副作用(热信号),那他们就很好理解了,SignalProducer类似RACOC中的RACDynamicSignal,Signal类似RACSubject。
通过lift函数可以让热信号转变为冷信号。
如果想学习Signal部分参考文章底部链接

Subscription

一个信号生成器代表了一种可以在需要的时候才被启动的操作(不像Signal是自启动的),这种信号是冷信号,在刚开始这个信号的状态也为冷(未激活), 既然是冷信号,那么就意味这一个观察者不会错过任何被信号生成器发出的值。

补充: 像signal 是创建的时候状态为cold(理解为未激活) ,被订阅时状态为hot (理解为激活)。
但是冷信号和热信号与状态为冷热是两个不同的概念。冷信号会带来附作用,热信号不会
这里我把副作用(side effect) 理解为附作用,有时候这是一个很必要的附加操作,并不一定都是无益的,不然就没有存在的价值了。
如果读者觉得理解的不对,请指出后续会继续修改。

scopedExample("Subscription") {
    let producer = SignalProducer<Int, NoError> { observer, _ in
        print("New subscription, starting operation")
        observer.sendNext(1)
        observer.sendNext(2)
    }

    let subscriber1 = Observer<Int, NoError>(next: { print("Subscriber 1 received \($0)") })
    let subscriber2 = Observer<Int, NoError>(next: { print("Subscriber 2 received \($0)") })

    print("Subscriber 1 subscribes to producer")
    producer.start(subscriber1)

    print("Subscriber 2 subscribes to producer")
    // Notice, how the producer will start the work again
    producer.start(subscriber2)
}


--- Subscription ---

Subscriber 1 subscribes to producer
New subscription, starting operation
Subscriber 1 received 1
Subscriber 1 received 2
Subscriber 2 subscribes to producer
New subscription, starting operation
Subscriber 2 received 1
Subscriber 2 received 2

像不像是RACDynamicSignal的创建方式,这不过不同与Sinal的是 这里的发送信号的观察者是在内部通过 Signal.pipe() 生成的,不需要外部创建。
SignalProduce是冷信号,任何一个订阅者/观察者都不会错过任何事件
start方法类似与Signal的 signal.observe()方法,只不过Signal的方法只有一个作用,就是关联一个观察者,而SignalProduce的start方法还多了一个激活信号的功能。

Empty

一个会立即调用complete事件的信号生成器

/*:
 ### `empty`
 A producer for a Signal that will immediately complete without sending
 any values.
 */
scopedExample("`empty`") {
    let emptyProducer = SignalProducer<Int, NoError>.empty

    let observer = Observer<Int, NoError>(
        failed: { _ in print("error not called") },
        completed: { print("completed called") },
        interrupted: { print("interrupted called") },
        next: { _ in print("next not called") }
    )

    emptyProducer.start(observer)
}

--- `empty` ---

completed called

Signal调用的是interrup方法,暂时不知道为什么,可能是为了区分语义把。Signal是有时序的,SignalProduce是没有时序的。使用中断更合适吧。

Never

一个什么都不会发送的信号生成器

/*:
 ### `never`
 A producer for a Signal that never sends any events to its observers.
 */
scopedExample("`never`") {
    let neverProducer = SignalProducer<Int, NoError>.never

    let observer = Observer<Int, NoError>(
        failed: { _ in print("error not called") },
        completed: { print("completed not called") },
        next: { _ in print("next not called") }
    )

    neverProducer.start(observer)
}

--- `never` ---

buffer

创建一个事件队列可以回放已经发送的事件。

当一个值被发送的时候,它会被放进缓冲区内,如果缓冲区已经溢出,就会丢弃旧的值,
这些被缓存的值将会被保留,直到这个信号被终结。当一个信号启动的时候,如果队列里没有任何值,所有被发送的新值都会被自动转发到观察者哪里,知道观察者收到一个终止事件。

当一个终止事件被发送到队列中,观察者不会再收到任何值,并且这个事件不会被计算buffer的缓冲区大小,所以没有缓存的值都会被丢弃。

scopedExample("`buffer`") {
    let (producer, observer) = SignalProducer<Int, NoError>.buffer(2)

    observer.sendNext(1)
    observer.sendNext(2)
    observer.sendNext(3)

    var values: [Int] = []
    producer.start { event in
        switch event {
        case let .Next(value):
            values.append(value)
        default:
            break
        }
    }
    print(values)

    observer.sendNext(4)

    print(values)



    let subscriber = Observer<Int,NoError>(next:{ bufferdValue in
        print("\(bufferdValue)")
    })
    producer.start(subscriber)

}

--- `buffer` ---

[2, 3]
[2, 3, 4]
3
4

Buffer更像RACReplaySubject,所以Buffer应该也算一个热信号

startWithSignal

通过Producer返回一个Signal,当闭包调用时返会signal开始发送事件。
闭包返回一个Disposable 可以用来中断Signal或者完成。

scopedExample("`startWithSignal`") {
    var started = false
    var value: Int?

    SignalProducer<Int, NoError>(value: 42)
        .on(next: {
            value = $0
        })
        .startWithSignal { signal, disposable in
            print(signal)

            print(value) // nil 
        }

    print(value)
}
--- `startWithSignal` ---

ReactiveCocoa.Signal<Swift.Int, Result.NoError>
nil
Optional(42)


startWithNext

通过信号生成器创建一个信号,并且给这个信号内部直接构建一个观察者,在指定的闭包中会直接订阅next事件。

返回一个Disposable,可以中断这个信号。中断之后这个闭包不会再被调用


scopedExample("`startWithNext`") {
    SignalProducer<Int, NoError>(value: 42)
        .startWithNext { value in
            print(value)
        }
}
--- `startWithNext` ---

42

其实类似与OC部分的[subscribeNext: ^(id value){}],观察者不用自己构建了

注意: 这个订阅只能接受next事件

startWithCompleted

同startWithNext, 只不过只能接受complete事件


scopedExample("`startWithCompleted`") {
    SignalProducer<Int, NoError>(value: 42)
        .startWithCompleted {
            print("completed called")
        }
}

--- `startWithCompleted` ---

completed called

startWithFailed

同startWithNext, 只不过只能接受Failer事件事件


scopedExample("`startWithFailed`") {
    SignalProducer<Int, NSError>(error: NSError(domain: "example", code: 42, userInfo: nil))
        .startWithFailed { error in
            print(error)
        }
}

--- `startWithFailed` ---

Error Domain=example Code=42 "(null)"

startWithInterrupted

同startWithNext, 只不过只能接受interrupted事件事件

scopedExample("`startWithInterrupted`") {
    let disposable = SignalProducer<Int, NoError>.never
        .startWithInterrupted {
            print("interrupted called")
        }

    disposable.dispose()
}

--- `startWithInterrupted` ---

interrupted called

operators

lift

这个相对难理解点。大致类似与RAC-OC部分中的bind函数,monad中的bind函数

可以理解为所有的原函数都是通过lift去实现的,接用中间信号来实现一系列的信号变换

scopedExample("`lift`") {
    var counter = 0
    let transform: Signal<Int, NoError> -> Signal<Int, NoError> = { signal in
        counter = 42
        return signal
    }

    SignalProducer<Int, NoError>(value: 0)
        .lift(transform)
        .startWithNext { _ in
            print(counter)
        }

}

--- `lift` ---

42

map

把每个值都转换为新的值

scopedExample("`map`") {
    SignalProducer<Int, NoError>(value: 1)
        .map { $0 + 41 }
        .startWithNext { value in
            print(value)
        }
}

--- `map` ---

42

mapError

把收到的error转换为新的error

scopedExample("`mapError`") {
    SignalProducer<Int, NSError>(error: NSError(domain: "mapError", code: 42, userInfo: nil))
        .mapError { Error.Example($0.description) }
        .startWithFailed { error in
            print(error)
        }
}


--- `mapError` ---

Example("Error Domain=mapError Code=42 \"(null)\"")

filter

过滤不符合条件的值


scopedExample("`filter`") {
    SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
        .filter { $0 > 3}
        .startWithNext { value in
            print(value)
        }
}


--- `filter` ---

4

take

take(num) 只取前几次的值

scopedExample("`take`") {
    SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
        .take(2)
        .startWithNext { value in
            print(value)
        }
}

--- `take` ---

1
2

observeOn

在指定的调度器上分发事件


/*:
 ### `observeOn`
 Forwards all events onto the given scheduler, instead of whichever
 scheduler they originally arrived upon.
 */
scopedExample("`observeOn`") {
    let baseProducer = SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
    let completion = { print("is main thread? \(NSThread.currentThread().isMainThread)") }

    if #available(OSX 10.10, *) {
    baseProducer
        .observeOn(QueueScheduler(qos: QOS_CLASS_DEFAULT, name: "test"))
        .startWithCompleted(completion)
    }

    baseProducer
        .startWithCompleted(completion)
}


--- `observeOn` ---

is main thread? true

collect

在发送完成的时候将一系列的值聚合为一个数组


scopedExample("`collect()`") {
    SignalProducer<Int, NoError> { observer, disposable in
            observer.sendNext(1)
            observer.sendNext(2)
            observer.sendNext(3)
            observer.sendNext(4)
            observer.sendCompleted()
        }
        .collect()
        .startWithNext { value in
            print(value)
        }
}

--- `collect()` ---

[1, 2, 3, 4]

collect(count:)

在发送数据的时候(不需要发送complete)的时候将一系列的值聚合为数组,数组的长度为count,如果又很多数据,将会返回多个数组

scopedExample("`collect(count:)`") {
    SignalProducer<Int, NoError> { observer, disposable in
            observer.sendNext(1)
            observer.sendNext(2)
            observer.sendNext(3)
            observer.sendNext(4)
        observer.sendNext(5)

//            observer.sendCompleted()
        }
        .collect(count: 2)
        .startWithNext { value in
            print(value)
        }
}


--- `collect(count:)` ---

[1, 2]
[3, 4]

collect(predicate:) matching values inclusively

通过谓词将一系列的值聚合为一个数组,注意在发送complete时候,如果前面只剩下一个值,就不需要聚合,(因为没有其他元素和最后一个元素聚合),直接返回一个只有一个元素的数组。 如果没有数据则返回一个空数组。

scopedExample("`collect(predicate:)` matching values inclusively") {
    SignalProducer<Int, NoError> { observer, disposable in
//            observer.sendNext(1)
//            observer.sendNext(2)
//            observer.sendNext(3)
//            observer.sendNext(4)
            observer.sendCompleted()
        }
        .collect { values in values.reduce(0, combine: +) == 3 }
        .startWithNext { value in
            print(value)
        }
}

--- `collect(predicate:)` matching values inclusively ---

[]
  1. 尝试把所有数据打开,看看什么结果,
  2. 尝试只注释4看什么结果

collect(predicate:) matching values exclusively

和上一个不同的是,如果谓词成功就把之前的聚合在一起。 可以理解为把成功的界限当作分隔符

scopedExample("`collect(predicate:)` matching values exclusively") {
    SignalProducer<Int, NoError> { observer, disposable in
            observer.sendNext(1)
            observer.sendNext(2)
            observer.sendNext(3)
            observer.sendNext(4)
            observer.sendNext(5)
            observer.sendCompleted()
        }
        .collect { values, next in next == 3 || next == 5  }
        .startWithNext { value in
            print(value)
        }
}

--- `collect(predicate:)` matching values exclusively ---

[1, 2]
[3, 4] // 3满足了条件所以被分开
[5] // 5也是

combineLatestWith

将第一个信号生成器的values 和被聚合信号生成器的最后一个值聚合为一个元组

新产生的信号生成器不会发送任何值,只是转发,任何一个原来的信号被中断,这个新的信号生成器也会中断

scopedExample("`combineLatestWith`") {
    let producer1 = SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
    let producer2 = SignalProducer<Int, NoError>(values: [ 1, 2 ])

    producer1
        .combineLatestWith(producer2)
        .startWithNext { value in
            print("\(value)")
        }
}
--- `combineLatestWith` ---

(1, 2)
(2, 2)
(3, 2)
(4, 2)

skip

skip(num), 跳过num此发送的事件


scopedExample("`skip`") {
    let producer1 = SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])

    producer1
        .skip(2)
        .startWithNext { value in
            print(value)
        }
}

--- `skip` ---

3
4

materialize

将被发送的值(value)变成Event, 允许他们被修改。换句话说把一个值变成一个Monad (在前文中写到Event就是一个monad)

当收到一个compelet或者Failure事件,这个新的信号生成器,会发送事件并且结束。当收到一个interruped事件,这个新的信号生成器也会中断

scopedExample("`materialize`") {
    SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
        .materialize()
        .startWithNext { value in
            print(value)
        }
}

--- `materialize` ---

NEXT 1
NEXT 2
NEXT 3
NEXT 4
COMPLETED


// 注意 value  如果不做materialize就是Int类型
// 现在是Event<Int,NoError>类型,也就是一个monad

sampleOn

  1. 当Sampler(被操作的信号生成器)发送任何事件的时候,都转发原来信号生成器的最后一个值

  2. 如果当一个sampler (被操作的信号生成器)启动是,当前的值没有被观察者,没有任何事情发生

  3. 新产生的信号生成器从源信号生成器哪里发送数据,如果两个信号生成器任何一个complete或者interrupt 新产生的都会中断

/*:
 ### `sampleOn`
 Forwards the latest value from `self` whenever `sampler` sends a Next
 event.

 If `sampler` fires before a value has been observed on `self`, nothing
 happens.

 Returns a producer that will send values from `self`, sampled (possibly
 multiple times) by `sampler`, then complete once both input producers have
 completed, or interrupt if either input producer is interrupted.
 */
scopedExample("`sampleOn`") {
    let baseProducer = SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
    let sampledOnProducer = SignalProducer<Int, NoError>(values: [ 1, 2 ])
        .map { _ in () }

    let newProduce = baseProducer
        .sampleOn(sampledOnProducer)

      newProduce  .startWithNext { value in
            print(value)
        }
}
--- `sampleOn` ---

4
4

sampler发送的2次值都被变换成baseProduce 的comlete前的最后一个值

combinePrevious

向前合并,每发送一个值就结合历史发送数据的最后一个构造成一个新的元组返回。在第一个发送时由于没有历史数据,所以combinePrevioud传递了一个默认值。当作第一次的合并


scopedExample("`combinePrevious`") {
    SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
        .combinePrevious(42)
        .startWithNext { value in
            print("\(value)")
        }
}
--- `combinePrevious` ---

(42, 1) // 第一次没有历史记录默认值是42
(1, 2) // 第二次默认记录是1 
(2, 3)
(3, 4)

scan

类似reduce,将值聚合为一个新的值,每次聚合都保留结果作为下次的默认值。首次需给出默认值。

每次聚合都会发送这个值


scopedExample("`scan`") {
    SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
        .scan(0, +)
        .startWithNext { value in
            print(value)
        }
}
--- `scan` ---

1
3
6
10

reduce

和scan类似 ,区别为reduce只发送聚合后的值并且立即结束


scopedExample("`reduce`") {
    SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
        .reduce(0, +)
        .startWithNext { value in
            print(value)
    }
}

--- `reduce` ---

10

skipRepeats

跳过表达式里返回true的值,第一个值不会被跳过

scopedExample("`skipWhile`") {
    SignalProducer<Int, NoError>(values: [ 3, 3, 3, 3, 1, 2, 3, 4 ])
        .skipWhile { $0 > 2 }
        .startWithNext { value in
            print(value)
        }
}

--- `skipRepeats` ---

1
2
3
1
2
4
1

// 注意并不是去重,只是两两比较,true的值被忽略

skipWhile

对每个值都去做判断,直到返回faslse,之前的值会被跳过

scopedExample("`skipWhile`") {
    SignalProducer<Int, NoError>(values: [ 3, 3, 3, 3, 1, 2, 3, 4 ])
        .skipWhile { $0 > 2 }
        .startWithNext { value in
            print(value)
        }
}

--- `skipWhile` ---

1  // 到1 返回false  之前的值被忽略掉
2
3
4

takeUntilReplacement

在被替换的信号发生器发送信号之后,发送被替换的信号。

scopedExample("`takeUntilReplacement`") {
    let (replacementSignal, incomingReplacementObserver) = Signal<Int, NoError>.pipe()

    let baseProducer = SignalProducer<Int, NoError> { incomingObserver, _ in
        incomingObserver.sendNext(1)
        incomingObserver.sendNext(2)
        incomingObserver.sendNext(3)
// 下面被替换的信号生成器发送了事件,之后就不再发送baseProducer的事件了
// 相当于被替换了
        incomingReplacementObserver.sendNext(42)

        incomingObserver.sendNext(4)

        incomingReplacementObserver.sendNext(42)
    }

    let producer = baseProducer.takeUntilReplacement(replacementSignal)

    producer.startWithNext { value in
        print(value)
    }
}
--- `takeUntilReplacement` ---

1
2
3  
42
42

takeLast

在发送complete事件后支取count此数据

scopedExample("`takeLast`") {
    SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
        .takeLast(2)
        .startWithNext { value in
            print(value)
        }
}

只取了2次数据
--- `takeLast` ---

3
4

ignoreNil

如果发送的事件是可选类型,解包这些可选类型,并且丢弃nil值

scopedExample("`ignoreNil`") {
    SignalProducer<Int?, NoError>(values: [ nil, 1, 2, nil, 3, 4, nil ])
        .ignoreNil()
        .startWithNext { value in
            print(value)
        }
}
--- `ignoreNil` ---

1
2
3
4

zipWith

压缩信号生成器,只有在两个信号都有数据发送之后,新的信号生成器才会发送数据。
新的数据被组合为元组。

scopedExample("`zipWith`") {
    let baseProducer = SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
    let zippedProducer = SignalProducer<Int, NoError>(values: [ 42, 43 ])

    baseProducer
        .zipWith(zippedProducer)
        .startWithNext { value in
            print("\(value)")
        }
}

--- `zipWith` ---

(1, 42)
(2, 43)

后面因为第二个没有数据了所以不会在聚合了

times

time(count) 重复发送count数据,每次重复必须上次发送完成事件

scopedExample("`times`") {
    var counter = 0

    SignalProducer<(), NoError> { observer, disposable in
            counter += 1
            observer.sendCompleted()
        }
        .times(42)
        .start()

    print(counter)
}

--- `times` ---

42

retry

如果收到失败事件重试retry(count)次


scopedExample("`retry`") {
    var tries = 0

    SignalProducer<Int, NSError> { observer, disposable in
            if tries == 0 {
                tries += 1
                observer.sendFailed(NSError(domain: "retry", code: 0, userInfo: nil))
            } else {
                observer.sendNext(42)
                observer.sendCompleted()
            }
        }
        .retry(1)
        .startWithResult { result in
            print(result)
        }
}

--- `retry` ---

.Success(42)

then

当第一个信号发送complete时,第二个信号被替换到信号发送线路上,如果有任何失败事件,后面的就替换失败。

第一个信号发送的所有事件都会被忽略
这个没有使用场景比较难理解。看图
then


scopedExample("`then`") {
    let baseProducer = SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
    let thenProducer = SignalProducer<Int, NoError>(value: 42)

    baseProducer
        .then(thenProducer)
        .startWithNext { value in
            print(value)
        }
}

--- `then` ---

42

replayLazily

创建一个新的SignaProduce,内部包含了一个生产者去组播的发送事件。直到capacity(可以保留的数量)。这意味者所有的观者者看到是相同版本的值或者错误。

类似RAC-OC部分中的replayLazily ,底层生产者是懒加载的。第一次被观察的时候才会启动。在启动时所有的被缓存的值将会被直接发送。
如果你发现你需要实时的值,建议你使用Signal(热信号)代替,这个信号默认会缓存一些值,在某些时候这些缓存值无用。
使用方法类似SignalProducer.buffer(count)
这时候冷信号变成了热信号

补充: OC部分中的replay是直接启动的,replayLazily是懒加载的,Swift部分没有直接启动的


scopedExample("`replayLazily`") {
    let baseProducer = SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4, 42 ])
        .replayLazily(2)

    baseProducer.startWithNext { value in
        print(value)
    }

    baseProducer.startWithNext { value in
        print(value)
    }

    baseProducer.startWithNext { value in
        print(value)
    }
}

--- `replayLazily` ---

1
2
3
4
42
4
42
4
42

flatMap(.Latest)

将收到的每个事件 都映射为新的Producer,然后摊平,如果原来的producer发送失败,新产生也的立即失败。


scopedExample("`flatMap(.Latest)`") {
    SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
        .flatMap(.Latest) { SignalProducer(value: $0 + 3) }
        .startWithNext { value in
            print(value)
        }
}
--- `flatMap(.Latest)` ---

4
5
6
7

flatMapError

把收到的failure事件映射为新的Producer,并且摊平它


scopedExample("`flatMapError`") {
    SignalProducer<Int, NSError>(error: NSError(domain: "flatMapError", code: 42, userInfo: nil))
        .flatMapError { SignalProducer<Int, NoError>(value: $0.code) }
        .startWithNext { value in
            print(value)
        }
}
--- `flatMapError` ---

42

sampleWith

在sampler发送nextEvents对源Producer的最后一次值组合形成一个新的元组,如果sample发送的时候,源Producer没有任何事件,则什么都不发生。

一旦源Producer和Sampler都任何一个发送了complete或者interruperd事件,则新产生的Producer理解结束。


/*:
 ### `sampleWith`
 Forwards the latest value from `self` with the value from `sampler` as a tuple,
 only when `sampler` sends a Next event.

 If `sampler` fires before a value has been observed on `self`, nothing happens.
 Returns a producer that will send values from `self` and `sampler`,
 sampled (possibly multiple times) by `sampler`, then complete once both
 input producers have completed, or interrupt if either input producer is interrupted.
 */
scopedExample("`sampleWith`") {
    let producer = SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4 ])
    let sampler = SignalProducer<String, NoError>(values: [ "a", "b" ])

    let result = producer.sampleWith(sampler)

    result.startWithNext { left, right in
        print("\(left) \(right)")
    }
}
--- `sampleWith` ---

4 a
4 b

logEvents

把所有收到的事件都输出一份日志。

scopedExample("`log events`") {
    let baseProducer = SignalProducer<Int, NoError>(values: [ 1, 2, 3, 4, 42 ])

    baseProducer
        .logEvents(identifier: "Playground is fun!")
        .start()
}


[Playground is fun!] Started fileName: /var/folders/d_/9kczd9ld7c3ckq_prb99wnn00000gn/T/lldb/31540/playground103.swift, functionName: __lldb_expr_103, lineNumber: 811
[Playground is fun!] Next 1 fileName: /var/folders/d_/9kczd9ld7c3ckq_prb99wnn00000gn/T/lldb/31540/playground103.swift, functionName: __lldb_expr_103, lineNumber: 811
[Playground is fun!] Next 2 fileName: /var/folders/d_/9kczd9ld7c3ckq_prb99wnn00000gn/T/lldb/31540/playground103.swift, functionName: __lldb_expr_103, lineNumber: 811
[Playground is fun!] Next 3 fileName: /var/folders/d_/9kczd9ld7c3ckq_prb99wnn00000gn/T/lldb/31540/playground103.swift, functionName: __lldb_expr_103, lineNumber: 811
[Playground is fun!] Next 4 fileName: /var/folders/d_/9kczd9ld7c3ckq_prb99wnn00000gn/T/lldb/31540/playground103.swift, functionName: __lldb_expr_103, lineNumber: 811
[Playground is fun!] Next 42 fileName: /var/folders/d_/9kczd9ld7c3ckq_prb99wnn00000gn/T/lldb/31540/playground103.swift, functionName: __lldb_expr_103, lineNumber: 811
[Playground is fun!] Completed fileName: /var/folders/d_/9kczd9ld7c3ckq_prb99wnn00000gn/T/lldb/31540/playground103.swift, functionName: __lldb_expr_103, lineNumber: 811
[Playground is fun!] Terminated fileName: /var/folders/d_/9kczd9ld7c3ckq_prb99wnn00000gn/T/lldb/31540/playground103.swift, functionName: __lldb_expr_103, lineNumber: 811
[Playground is fun!] Disposed fileName: /var/folders/d_/9kczd9ld7c3ckq_prb99wnn00000gn/T/lldb/31540/playground103.swift, functionName: __lldb_expr_103, lineNumber: 811

 


Reference

  1. ReactiveCocoa 4 图解之六——信号发生器(SignalProducer)
  2. ReactiveCocoa-Swift部分入门指南-Signal
阅读更多

Swift-Code-Style

最近工作也比较稳定了,公司在做代码规范和组件化的跳转。 鉴于使用Objective-C的开发成员比较多, 我们架构师就整理了Objective-C的代码规范。不过作为Swift开发的老司机,也整理了一套适用与Swift的代码规范。 以后可以直接拿来用了。

注释

// 单行注释 / 多行注释 /

/// 标记注释1

/* 标记注释2 /

建议使用VVDocument-Xode插件

文档注释

以/* ..../ 标记, 不用再没一行开头都加*号 支持markdown书写 例如

/**
 ## Feature Support

 This class does some awesome things. It supports:

 - Feature 1
 - Feature 2
 - Feature 3

 ## Examples

 Here is an example use case indented by four spaces because that indicates a
 code block:

     let myAwesomeThing = MyAwesomeClass()
     myAwesomeThing.makeMoney()

 ## Warnings

 There are some things you should be careful of:

 1\. Thing one
 2\. Thing two
 3\. Thing three
 */
class MyAwesomeClass {
    /* ... */
}

方法注释

用 - parameter注释来标记参数等

/**
 This does something with a `UIViewController`, perchance.
 - warning: Make sure that `someValue` is `true` before running this function.
 */
func myFunction() {
    /* ... */
}

命名

使用可读的驼峰命名法去给 方法 变量 命名。 class struct protocol enum 应使用大写,变量名使用小写

private let maximumWidgetCount = 100

class WidgetContainer {
  var widgetButton: UIButton
  let widgetHeightPercentage = 0.85
}

对于全局函数,init方法 ,建议每个参数都使用外部变量,来保证可读性

func dateFromString(dateString: String) -> NSDate
func convertPointAt(column column: Int, row: Int) -> CGPoint
func timedAction(afterDelay delay: NSTimeInterval, perform action: SKAction) -> SKAction!

// would be called like this:
dateFromString("2014-03-14")
convertPointAt(column: 42, row: 13)
timedAction(afterDelay: 1.0, perform: someOtherAction)

Protocols 协议命名

建议遵守Apple's API DesignGuidelines使用名词来描述,如 ing able ible 例如

Collection
WidgerFactory
Equtable
Resizing

Emumerations 枚举命名规范

使用首字母小写的驼峰命名法给每个case命名

enum Shape {
  case rectangle
  case square
  case rightTriangle
  case equilateralTriangle
}

Class Prefixes类型前缀

官方建议不使用前缀,因为swift有命名空间的概念 但是由于在项目开发中不可避免使用开源库,大部分使用pods管理,但是有时候需要针对需要定制功能,直接修改源码,这时候是直接将源码放在工程中,而且大部分的项目都是混编项目。可能导致命名冲突,此处还建议用LJ(Lianjia)当作命名前缀

class LJHomeViewController: UIViewController {}

Selector选择器

建议使用可推测的上下文环境,来创建选择器,而不是点击Xcode的Fix it ,这样会产生一个全名称 选择器

let sel = #selector(viewDidLoad)

不推荐

let sel = #selector(ViewController.viewDidLoad)

Generics泛型

泛型命名应该使用大写的驼峰命名法,,如果给一个泛型起名字其实没意义,可以使用常见的T,U,V来命名 推荐

struct Stack<Element> { ... }
func writeTo<Target: OutputStream>(inout target: Target)
func max<T: Comparable>(x: T, _ y: T) -> T

不推荐

struct Stack<T> { ... }//命名无意义
func writeTo<target: OutputStream>(inout t: target)// 首字母未大写
func max<Thing: Comparable>(x: Thing, _ y: Thing) -> Thing//简称即可

Code Formatting 代码格式

留空白

  • 建议使用tabs 而不是使用空格

  • 文件结束时留一行空白

  • 用足够的空行把代码分割为合理的逻辑块,而不是非常紧凑

  • 不要在一行代码结尾处留空格

    • 更不要在空行(\n)中使用缩进(\t)

声明类型时,将冒号与标识符连在一起

当声明一个变量时冒号紧跟变量,空一格再写类型

class SmallBatchSustainableFairtrade: Coffee { ... }

let timeToCoffee: NSTimeInterval = 2

func makeCoffee(type: CoffeeType) -> Coffee { ... }

Control Flow 控制流

建议使用Swift范的for in 循环而不是 while or c 式for循环

for _ in 0..<3 {
  print("Hello three times")
}

for (index, person) in attendeeList.enumerate() {
  print("\(person) is at position #\(index)")
}

for index in 0.stride(to: items.count, by: 2) {
  print(index)
}

for index in (0...3).reverse() {   //3,2,1,0
  print(index)
}

代码块缩进

(if/else/switch/while etc.)或者method function 的大括号留在当前行,并前保留一个空格 ,能省略的不要添加 如

if user.isHappy {
  // Do something
} else {
  // Do something else
}

不推荐

if (user.isHappy )          多余空格
{                  换行位置不对
  // Do something
}
else {
  // Do something else
}

Early Return

当你遇到某些操作需要条件判断去执行,应该使用防御式编程 尽早返回 如

guard n.isNumber else {
    return
}
guard let number1 = number1, number2 = number2, number3 = number3 else { fatalError("impossible") }
// do something with numbers
// Use n here
//guard 理解为确保的意思,  如 确保n是一个数字

不推荐使用if判断

if n.isNumber {
    // Use n here
} else {
    return
}


if let number1 = number1 {
  if let number2 = number2 {
    if let number3 = number3 {
      // do something with numbers
    }
    else {
      fatalError("impossible")
    }
  }
  else {
    fatalError("impossible")
  }
}
else {
  fatalError("impossible")
}

Semicolons 分号

不要写分号,不要写分号,不要写分号 Swift不同于JavaScript ,详情参看 generally considered unsafe---Do you recommend using semicolons after every statement in JavaScript?

更不建议把多句代码块放在一行中

自定义运算符的时候左右尽量各保留一个空格

func <|(lhs: Int, rhs: Int) -> Int
func <|<<A>(lhs: A, rhs: A) -> A
// 重构后
func <| (lhs: Int, rhs: Int) -> Int
func <|< <A>(lhs: A, rhs: A) -> A

代码分割

使用良好的代码分割让你的代码块更具有逻辑性

// MARK: -                   类似@parma mark -
// MARK:                      类似@parma mark

ProtocolConformance 协议保持一致性

一个类型实现一个协议时建议单独声明一个扩展,保证逻辑性分离 如

class MyViewcontroller: UIViewController {
  // class stuff here
}

// MARK: - UITableViewDataSource
extension MyViewcontroller: UITableViewDataSource {
  // table view data source methods
}

// MARK: - UIScrollViewDelegate
extension MyViewcontroller: UIScrollViewDelegate {
  // scroll view delegate methods
}

不推荐实现的所有协议写在一起

class MyViewcontroller: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
  // all methods
}

无用的代码要删除

无用的代码和注释要删除 ,避免给阅读代码的人造成困惑和疑问

类型定义

尽可能的使用swift自带类型,在必须的时候才做桥接 ,String-> NSString , Set->NSSet

更多的使用let,而不是var

尽量let foo = something 而非 var for = somthing

let-有保障 并且它的值的永远不会改变,对同事也是个 清晰的标记,对于它的用法,之后的代码可以做个强而有力的推断。更容易明白代码的含义。否则的话一旦你用了 var,还要去考虑值会不会改变,这时候你就不得不人肉去检查。 这样,无论何时你看到 var,就假设它会变,并找到原因。

常量

不建议直接命名顶级变量,建议定义在结构体或者枚举内部,用static let 声明。 可以给这些变量一个合适的命名空间

enum Math {
  static let e  = 2.718281828459045235360287
  static let pi = 3.141592653589793238462643
}

radius * Math.pi * 2 // circumference

Optional可选类型

尽量不要使用强制解包

对于一个可选类型var foo = Type? 不要使用强制解包

    foo!.doSomethind()

使用可选绑定,或者 可选链操作

if let foo = foo {
    // Use unwrapped `foo` value in here
} else {
    // If appropriate, handle the case where the optional is nil
}
//或者
// Call the function if `foo` is not nil. If `foo` is nil, ignore we ever tried to make the call
foo?.callSomethingIfFooIsNotNil()

避免使用隐式可选类型

如果 foo 可能为 nil ,尽可能的用 let foo: FooType? 代替 let foo: FooType!(注意:一般情况下,?可以代替!)

Struct Initializers 结构体初始化

使用结构体初始化而不是CGGet。。。之类的创建方法

let bounds = CGRect(x: 40, y: 20, width: 120, height: 80)
let centerPoint = CGPoint(x: 96, y: 42)

Lazy Initialization

对于较大开销的初始化或者配置较多的初始化建议放在加载属性里

lazy var locationManager: CLLocationManager = self.makeLocationManager()

private func makeLocationManager() -> CLLocationManager {
  let manager = CLLocationManager()
  manager.desiredAccuracy = kCLLocationAccuracyBest
  manager.delegate = self
  manager.requestAlwaysAuthorization()
  return manager
}

Classes and Structures 结构体和类

首选struct而非class

在非必需(比如没有生命周期)的时候使用struct,因为多态可以使用protocl实现 继承可以使用组合实现 值类型容易辨别,更可以用let去推测不可变的行为

只有在必须时才使用self

忘掉Objective-C到底时使用self.pro 还是_ivar的访问方式,对于swift内部调用properties或者method省略掉self

private class History {
    var events: [Event]

    func rewrite() {
        events = []
    }
}

只有在使用闭包或者命名冲突时再加上self

extension History {
    init(events: [Event]) {
        self.events = events
    }

    var whenVictorious: () -> () {
        return {
            self.rewrite()
        }
    }
}

只有在使用闭包时self 增强了被捕获的语义,其它时候是冗余的

对于只读的属性或者下标语法,使用隐式的getter方法

建议

var myGreatProperty: Int {
    return 4
}
subscript(index: Int) -> T {
    return objects[index]
}

不建议完整的写法,比较繁琐

var myGreatProperty: Int {
    get {
        return 4
    }
}
subscript(index: Int) -> T {
    get {
        return objects[index]
    }
}

请把class默认标记为final

组合通常比继承更合适,而且不用 继承意味着考虑的更加健壮

// Turn any generic type into a reference type using this Box class.
final class Box<T> {
  let value: T
  init(_ value: T) {
    self.value = value
  }
}

类型推断

能让系统推断的类型不要显示指明 如

struct Composite<T> {
    func compose(other: Composite<T>) -> Composite<T> {
        return Composite<T>(self, other)
    }
}
 let num:Int = 4

重构为

struct Composite<T> {
    func compose(other: Composite) -> Composite {
        return Composite(self, other)
    }
}
let num = 4

空的字典和空数组的类型 使用类型标记 加强语义

var names: [String] = []
var lookup: [String: Int] = [:]

函数声明

函数名要简短清晰,如果能保持在一行内,大括号也要保持在一行,如果不能换行并用Tab\b缩进

func reticulateSplines(spline: [Double]) -> Bool {
  // reticulate code goes here
}
func reticulateSplines(spline: [Double], adjustmentFactor: Double,
    translateConstant: Int, comment: String) -> Bool {
  // reticulate code goes here
}

闭包表达式

使用尾随闭包提高可读性,

UIView.animateWithDuration(1.0) {
  self.myView.alpha = 0
}

UIView.animateWithDuration(1.0,
  animations: {
    self.myView.alpha = 0
  },
  completion: { finished in
    self.myView.removeFromSuperview()
  }
)

常见的闭包语义可以使用其缩略形式

let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.indexOf(90)

let value = numbers
   .map {$0 * 2}
   .filter {$0 > 50}
   .map {$0 + 10}

Syntactic Sugar语法糖

对于有语法糖的建议使用,提升可读性 如

var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?

而不是

var deviceModels: Array<String>
var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>

内存管理

对于class类型需要注意内存管理 普通的闭包建议使用[weak self] 或者[unowned self] 对于异步的闭包建议使用 [weak self] and guard let strongSelf = self else { return }搭配使用

weak 避免出现循环引用, strongself 避免在异步回调中 捕获列表中捕获的变量被析构

resource.request().onComplete { [weak self] response in
  guard let strongSelf = self else { return }
  let model = strongSelf.updateModel(response)
  strongSelf.updateUI(model)
}

对于顶级类型,函数,变量定义,明确的列出权限控制

对于全局变量 顶级函数,类型,永远应该有着详尽的权限控制说明符

public var whoopsGlobalState: Int
internal struct TheFez {}
private func doTheThings(things: [Thing]) {}

参考自

  1. Github
  2. LinkedIn
  3. Prolificinterative
  4. Raywenderlich
阅读更多