var$RAC3$=$Reac,veCocoa$+$Swi2
@ikesyo
#rac_kansai
Reac%veCocoa勉強会関西)2014.07.26)Sat)@はてな京都オフィス
@ikesyo
いけしょー/池田翔
京都でフリーランスのiOS/Android
エンジニアしています
甘いもの大好き!!
お仕事待ってます!
Reac%veCocoaのコミッター(Contributor)やってます
今日はSwi$ベースになった
Reac%veCocoa)3.0のご紹介
※!注意
2014%07%25,)Xcode)6)beta)4時点の内容に基づきます
APIやクラス名など今後変更される可能性は大いにあります
RAC$3.0
元々はこれ
h"ps://github.com/Reac3veCocoa/
Reac3veCocoa/pull/966
_人人人人人人人_
> 突然のSwi$ <
 ̄Y^Y^Y^Y^Y^Y ̄
だけではなく
RACDC%2014:%June%3rd@Github
• The$Future$Of$Reac.veCocoa$by$Jus.n$Spahr9Summers$•$
GitHub$Reac.ve$Cocoa$Developer$Conference
• h#p://vimeo.com/98100163
• h#ps://github.com/jspahrsummers/the<future<of<
reac>vecocoa
h"ps://github.com/jspahrsummers/
RxSwi8
を経て
The$Great$Swi,ening$(a.k.a.$the$
new$3.0)
h"ps://github.com/Reac3veCocoa/
Reac3veCocoa/pull/1382
New$Concepts
• Generics)Support
• Producer
• Consumer
• Signal
• SignalingProperty
• Promise
• Action
Generics(Support
型パラメータは当然サポート
public struct Producer<T> {}
public final class Consumer<T>: Sink {}
extension NSNotificationCenter {
public func rac_notifications
(name: String? = nil, object: AnyObject? = nil)
-> Signal<NSNotification?>
}
extension NSURLSession {
public func rac_dataProducerWithRequest
(request: NSURLRequest)
-> Producer<(NSData, NSURLResponse)>
}
Producer
• Cold&RACSignal:&Event&Stream
• Consumerをアタッチしてイベントをproduceする
• Consumer毎に受け取るイベントやタイミングは異なる
• ConsumerがDisposableを持っていて、Producer生成時の
クロージャではDisposableを返さない
• consumer.disposable.addDisposable(...)
Producer.swi,
public struct Producer<T> {
public init(produce: Consumer<T> -> ()) // +[RACSignal createSignal:]
public static func empty() -> Producer<T> // +[RACSignal empty]
public static func single(value: T) -> Producer<T> // +[RACSignal return:]
public static func error(error: NSError) -> Producer<T> // +[RACSignal error:]
public static func never() -> Producer<T> // +[RACSignal never]
// -[RACSignal subscribe:]
public func produce(consumer: Consumer<T>) -> Disposable
public func produce(consumer: Event<T> -> ()) -> Disposable
// -[RACSignal subscribeNext:error:completed:]
public func produce(
next: T -> (),
error: NSError -> (),
completed: () -> ()
) -> Disposable
}
Producerの生成例
extension RACSignal {
public func asProducer() -> Producer<AnyObject?> {
return Producer { consumer in
let next = { (obj: AnyObject?) -> () in
consumer.put(.Next(Box(obj)))
}
let error = { (maybeError: NSError?) -> () in
let nsError = maybeError.orDefault(RACError.Empty.error)
consumer.put(.Error(nsError))
}
let completed = {
consumer.put(.Completed)
}
let disposable: RACDisposable? = self.subscribeNext(next, error: error, completed: completed)
consumer.disposable.addDisposable(disposable)
}
}
}
Consumer
• like:'id<RACSubscriber> subscriber
• SinkプロトコルのputにEvent<T>でラップした値を渡す
• public func put(event: Event<T>) {}
• 従来の'-sendNext:'などの代わり
• Producerのproduceメソッドに'Event<T> -> ()'のクロー
ジャをConsumerとして渡せる
Consumer.swi,
public final class Consumer<T>: Sink {
public typealias Element = Event<T>
public let disposable = CompositeDisposable()
public init<S: Sink where S.Element == Event<T>>(_ sink: S)
public convenience init(put: Event<T> -> ())
public convenience init(
next: T -> () = emptyNext,
error: NSError -> () = emptyError,
completed: () -> () = emptyCompleted)
public func put(event: Event<T>)
}
Consumerの使用例
extension Producer {
public func asDeferredRACSignal<U: AnyObject>(evidence: Producer<T> -> Producer<U?>) -> RACSignal {
return RACSignal.createSignal { subscriber in
let selfDisposable = evidence(self).produce { event in
switch event {
case let .Next(obj):
subscriber.sendNext(obj)
case let .Error(error):
subscriber.sendError(error)
case let .Completed:
subscriber.sendCompleted()
}
}
return RACDisposable {
selfDisposable.dispose()
}
}
}
}
補足1
• evidenceという型チェックにより、特定の型パラメータを持
つProducerでのみメソッドを呼べるように制限
• Objec've)CとのブリッジなのでStructやEnumは渡せず、
AnyObjectでクラスオブジェクトだけに制限
• 使い方:,引数に,identityという関数を渡すだけ
Iden%ty.swi,
/// The identity function, which returns its argument.
///
/// This can be used to prove to the typechecker that a given type A is
/// equivalent to a given type B.
///
/// For example, the following global function is normally impossible to bring
/// into the `Signal<T>` class:
///
/// func merge<U>(signal: Signal<Signal<U>>) -> Signal<U>
///
/// However, you can work around this restriction using an instance method with
/// an “evidence” parameter:
///
/// func merge<U>(evidence: Signal<T> -> Signal<Signal<U>>) -> Signal<U>
///
/// Which would then be invoked with the identity function, like this:
///
/// signal.merge(identity)
///
/// This will verify that `signal`, which is nominally `Signal<T>`, is logically
/// equivalent to `Signal<Signal<U>>`. If that's not actually the case, a type
/// error will result.
public func identity<T>(id: T) -> T {
return id
}
補足2
• 例えばScalaではGeneralized-Type-Constraintsという言語機能で
同様の制限が可能
• h6p://yuroyoro.hatenablog.com/entry/
20100914/1284471301
• h6p://www.ne.jp/asahi/hishidama/home/tech/scala/
generics.html#hgeneralizedtype_constraints
Signal
• Hot%RACSignal:%Behaviour%in%other%FRP
• CompletedやErrorによる終了はなく、常に現在の値を持つ
• T -> ()%のクロージャがオブサーバー%SinkOf<T>%となって
observeする
• ProducerのConsumerと異なり、全てのオブザーバーが同
じイベントを同じタイミングで受け取る
• observeを開始した時点で現在値が通知される
Signal.swi*
public final class Signal<T> {
public var current: T
public init(initialValue: T, generator: SinkOf<T> -> ())
public class func constant(value: T) -> Signal<T>
public func observe<S: Sink where S.Element == T>
(observer: S) -> Disposable
public func observe(observer: T -> ()) -> Disposable
}
Signalの生成例
extension NSNotificationCenter {
public func rac_notifications
(name: String? = nil, object: AnyObject? = nil)
-> Signal<NSNotification?>
{
let disposable = ScopedDisposable(SerialDisposable())
return Signal(initialValue: nil) { sink in
let observer = self.addObserverForName(name, object: object, queue: nil) { notification in
sink.put(notification)
}
disposable.innerDisposable.innerDisposable = ActionDisposable {
self.removeObserver(observer)
}
return ()
}
}
}
Signal/Observer(SinkOf<T>)の使用例
extension Signal {
/// Creates a "hot" RACSignal that will forward values from the receiver.
///
/// evidence - Used to prove to the typechecker that the receiver is
/// a signal of objects. Simply pass in the `identity` function.
///
/// Returns an infinite signal that will send the observable's current
/// value, then all changes thereafter. The signal will never complete or
/// error, so it must be disposed manually.
public func asInfiniteRACSignal<U: AnyObject>(evidence: Signal<T> -> Signal<U?>) -> RACSignal {
return RACSignal.createSignal { subscriber in
evidence(self).observe { value in
subscriber.sendNext(value)
}
return nil
}
}
}
SignalingProperty
• 値の変更をSignalとして通知することができるプロパティ用
オブジェクト
• KVOの代替手段:&Swi*のクラスにはKVOがなく、監視される側
が自分から通知できるように公開する
• func __conversion()&によりラップされた値を透過的に使
用することができる
SignalingProperty.swi1
public final class SignalingProperty<T>: Sink {
public typealias Element = T
public let signal: Signal<T>
// setするとsignalのオブザーバーに通知される
public var value: T
public init(_ defaultValue: T)
public func __conversion() -> T
public func __conversion() -> Signal<T>
public func put(value: T)
}
SignalingPropertyの使用例
public class Hoge {
var name: SignalingProperty<String> = SignalingProperty("")
}
public func printName(name: String) {
println(name)
}
let hoge = Hoge()
// Stringが必要なのでSignalingProperty<String>から現在の値が取り出される
printName(hoge.name)
hoge.name.signal.observe { name in
println("(name): name was changed!")
}
hoge.name.put("ikesyo")
Promise
• 単一の値を生成(resolve)する遅延タスクを表現する
• ProducerのようなEvent-Streamではないので複数の値を通知
したりはしない
• 値は-public let signal: Signal<T?>-でSignalとして参
照できる
• resolve前:-nil,-resolve後:-生成された値-を通知
Promise.swi*
public final class Promise<T> {
public let signal: Signal<T?>
// 生成時のクロージャの引数の`sink`に`put`することでresolveされる。
// 二度目以降の`put`は無視される。
public init(action: SinkOf<T> -> ())
public func start() -> Promise<T>
public func await() -> T
// Promiseのチェーン
public func then<U>(action: T -> Promise<U>) -> Promise<U>
}
Promiseの使用例
public final class Signal<T> {
public func firstPassingTest(pred: T -> Bool) -> Promise<T> {
return Promise { sink in
self.take(1).observe { value in
if pred(value) {
sink.put(value)
}
}
return ()
}
}
}
Ac#on
• RACCommandの置き換え
• インプットからアウトプットを返すアクション(主にUI用:&デフ
ォルトでメインスレッドで動作)の実行・結果を提供する
• 個別のアクション実行の結果はPromise<Result<T>で返す
• 結果のチェックはSignal<Result<T>?>なのでobserveで
• 実行結果の値があるのでアクションの合成(チェーン)が可能
Ac#on.swi*
public final class Action<Input, Output> {
public typealias ExecutionSignal = Signal<Result<Output>?>
// RACCommand.executionSignalsと同様
public let executions: Signal<ExecutionSignal?>
public var results: Signal<Result<Output>?> // 実行結果
public var executing: Signal<Bool> // 実行中かどうか
public let enabled: Signal<Bool> // 有効かどうか
public var values: Signal<Output?> // 成功結果
public var errors: Signal<NSError?> // 失敗結果
// アクションとなるクロージャを渡す
public init(enabledIf: Signal<Bool>,
scheduler: Scheduler = MainScheduler(),
execute: Input -> Promise<Result<Output>>)
public func execute(input: Input) -> ExecutionSignal // アクションの実行
public func then<NewOutput> // アクションの合成
(action: Action<Output, NewOutput>)
-> Action<Input, NewOutput>
}
Q&A?
ありがとうございました!

var RAC3 = ReactiveCocoa + Swift