var rac3 = reactivecocoa + swift @ reactivecocoa tokyo #rac_tokyo 10/18
DESCRIPTION
2014-10-18(土)に開催された「ReactiveCocoa Tokyo #rac_tokyo」の発表資料です。 http://connpass.com/event/8680/TRANSCRIPT
var$RAC3$=$Reac,veCocoa$+$Swi2@ikesyo#rac_tokyo
Reac%veCocoa)Tokyo)2014110118)Sat)@freee株式会社
@ikesyo
いけしょー/池田翔京都でフリーランスのiOS/Androidエンジニアしています
甘いもの大好き!!!!お仕事待ってます!!!
現在フリュー㈱にて一部RAC3を使ってお仕事中!
Reac%veCocoaのコミッター(Contributor)やってます
今日はSwi$ベースになったReac%veCocoa)3.0のご紹介
※!注意2014%10%16,(Xcode(6.1(GM(seed(2時点,(
swi4%api%reduxブランチ(#1532)の内容に基づきます
APIやクラス名など今後変更される可能性は大いにあります
※!実際に7/26の!#rac_kansai!から大きく変わっています……!※
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
• ColdSignal)!)Producer)!
• Subscriber)!)Consumer)!
• HotSignal)!)Signal)!
• ObservableProperty)!)SignalingProperty)!
• !)Promise)!
• Action
Generics(Support型パラメータは当然サポートpublic struct ColdSignal<T> {}public final class Subscriber<T>: SinkType {}extension NSNotificationCenter { public func rac_notifications (name: String? = nil, object: AnyObject? = nil) -> HotSignal<NSNotification>}extension NSURLSession { public func rac_dataWithRequest (request: NSURLRequest) -> ColdSignal<(NSData, NSURLResponse)>}
ColdSignal• Cold&RACSignal:&Event&Stream
• Subscriberをアタッチしてイベントを購読する
• Subscriber毎に受け取るイベントやタイミングは異なる
• SubscriberがDisposableを持っていて、ColdSignal生成時のクロージャではDisposableを返さない
• subscriber.disposable.addDisposable(...)
ColdSignal.swi-
public struct ColdSignal<T> { public init(generator: Subscriber<T> -> ()) // +[RACSignal createSignal:]
public static func empty() -> ColdSignal // +[RACSignal empty] public static func single(value: T) -> ColdSignal // +[RACSignal return:] public static func error(error: NSError) -> ColdSignal // +[RACSignal error:] public static func never() -> ColdSignal // +[RACSignal never]
// -[RACSignal subscribe:] public func subscribe(subscriber: Subscriber<T>) -> Disposable // -[RACSignal subscribeNext:error:completed:] public func subscribe( next: T -> () = doNothing, error: NSError -> () = doNothing, completed: () -> () = doNothing ) -> Disposable}
ColdSignalの生成例extension RACSignal { public func asColdSignal() -> ColdSignal<AnyObject?> { return ColdSignal { subscriber in let next = { (obj: AnyObject?) -> () in subscriber.put(.Next(Box(obj))) }
let error = { (maybeError: NSError?) -> () in let nsError = maybeError.orDefault(RACError.Empty.error) subscriber.put(.Error(nsError)) }
let completed = { subscriber.put(.Completed) }
let disposable: RACDisposable? = self.subscribeNext(next, error: error, completed: completed) subscriber.disposable.addDisposable(disposable) } }}
Subscriber• like:'id<RACSubscriber> subscriber
• SinkTypeプロトコルのputにEvent<T>でラップした値を渡す
• public func put(event: Event<T>) {}
• 従来の'-sendNext:'などの代わり
• Event<T> -> ()'のクロージャをハンドラー本体としてSubscriberを初期化し、ColdSignalのsubscribeに渡せる
Subscriber.swi+
public final class Subscriber<T>: SinkType { public typealias Element = Event<T>
public let disposable = CompositeDisposable()
public init<S: SinkType where S.Element == Event<T>>(_ sink: S) public convenience init(handler: Event<T> -> ()) public convenience init( next: T -> (), error: NSError -> (), completed: () -> ())
public func put(event: Event<T>)}
Subscriberの使用例extension ColdSignal { public func asDeferredRACSignal<U: AnyObject> (evidence: ColdSignal -> ColdSignal<U?>) -> RACSignal { return RACSignal.createSignal { subscriber in let selfDisposable = evidence(self).subscribe(next: { value in subscriber.sendNext(value) }, error: { error in subscriber.sendError(error) }, completed: { subscriber.sendCompleted() })
return RACDisposable { selfDisposable.dispose() } } }}
補足1• evidenceという型チェックにより、特定の型パラメータを持つColdSignalでのみメソッドを呼べるように制限
• 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
HotSignal• Hot%RACSignal:%Push+driven%stream
• CompletedやErrorによる終了はない
• T -> ()%のクロージャがオブサーバー%SinkOf<T>%となってobserveする
• ColdSignalのSubscriberと異なり、全てのオブザーバーが同じイベントを同じタイミングで受け取る
HotSignal.swi-
public final class HotSignal<T> { public init(_ generator: SinkOf<T> -> ()) public class func never() -> HotSignal
public func observe<S: SinkType where S.Element == T> (observer: S) -> Disposable public func observe(next: T -> ()) -> Disposable}
HotSignalの生成例extension NSNotificationCenter { public func rac_notifications (name: String? = nil, object: AnyObject? = nil) -> HotSignal<NSNotification> { let disposable = ScopedDisposable(SerialDisposable())
return HotSignal { sink in let observer = self.addObserverForName(name, object: object, queue: nil) { notification in sink.put(notification) }
disposable.innerDisposable.innerDisposable = ActionDisposable { self.removeObserver(observer) }
return () } }}
HotSignal/Observer(SinkOf<T>)の使用例extension HotSignal { /// Creates a 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 forward all values from the /// underlying HotSignal. The returned RACSignal will never complete or /// error, so it must be disposed manually. public func asInfiniteRACSignal<U: AnyObject> (evidence: HotSignal -> HotSignal<U?>) -> RACSignal { return RACSignal.createSignal { subscriber in evidence(self).observe { subscriber.sendNext($0) } return nil } }}
ObservableProperty• 値の変更をColdSignalとして通知することができるプロパティ用オブジェクト
• KVOの代替手段:&Swi*のクラスにはKVOがなく、監視される側が自分から通知できるように公開するため?
• !&func __conversion()&によりラップされた値を透過的に使用することができる&!&Swi*&1.0でなくなりました!
ObservableProperty.swi1
public final class ObservableProperty<T> { public func values() -> ColdSignal<T>
// setするとvalues()の戻り値のColdSignalのsubscriberに通知される public var value: T
// not public: ミス or 他からの変換? init(_ value: T)
/** public func __conversion() -> T public func __conversion() -> Signal<T> */}
extension ObservableProperty: SinkType { public func put(value: T)}
ObservablePropertyの使用例public class Hoge { let name: ObservableProperty<String> = ObservableProperty("nanashi")}
public func printName(name: String) { println(name)}
let hoge = Hoge()
printName(hoge.name.value)// => nanashi
hoge.name.values().subscribe(next: { name in println("\(name): name was changed!")})// => nanashi: name was changed! : 最初に現在値が通知される
hoge.name.value = "ikesyo"// orhoge.name.put("ikesyo") // by SinkType// => ikesyo: name was changed!
!!Promise!!• 単一の値を生成(resolve)する遅延タスクを表現する
• ProducerのようなEvent-Streamではないので複数の値を通知したりはしない
• 値は-public let signal: Signal<T?>-でSignalとして参照できる
• resolve前:-nil,-resolve後:-生成された値-を通知
Ac#on• RACCommandの置き換え
• インプットからアウトプットを返すアクション(主にUI用:&メインスレッドで動作)の実行・結果を提供する
• 個別のアクション実行の結果はColdSignal<T>で返す
• 結果のチェックは戻り値がColdSignalなのでsubscribeで
Ac#on.swi*
public final class Action<Input, Output> { // RACCommand.executionSignals相当は現時点でなし
public let executing: ColdSignal<Bool> // 実行中かどうか public let enabled: ColdSignal<Bool> // 有効かどうか public let values: HotSignal<Output> // 成功結果 public let errors: HotSignal<NSError> // 失敗結果
// アクションとなるクロージャを渡す public init(enabledIf: HotSignal<Bool>, execute: Input -> ColdSignal<Output>)
public func execute(input: Input) -> ColdSignal<Output> // アクションの実行}
おまけ
Xcode&6.1,&iOS&7/8両対応でSwi$版のReac)veCocoaを組み込む
方法を共有します。
1. git submodule add https://github.com/ReactiveCocoa/ReactiveCocoa.git External/ReactiveCocoa
2. (LlamaKitの微修正……)
3. LlamaKit.framework5/5Reac9veCocoa.framework5をビルドする
4.ビルドしたFrameworkをリンクする
5. Frameworkをアプリにコピーする
6. importして使うだけ!!
https://github.com/ikesyo/rac_tokyo_rac3_installation_sample
まとめ
!!RAC!3.0はまだ早い……!!
Q&A?
ありがとうございました!