デリゲートメソッドをRxSwift対応させる方法
この例ではUIWebViewDelegateメソッドをRxSwift対応させる。
環境
準備
RxCocoa、RxSwiftをインポート。
import RxCocoa import RxSwift
今のところ以下のキャスト時に使用する関数はパブリックになっていないので、RxCocoaからコピーしてくる。
@noreturn func rxFatalError(lastMessage: String) { fatalError(lastMessage) } func castOptionalOrFatalError<T>(value: AnyObject?) -> T? { if value == nil { return nil } let v: T = castOrFatalError(value) return v } func castOrThrow<T>(resultType: T.Type, _ object: AnyObject) throws -> T { guard let returnValue = object as? T else { throw RxCocoaError.CastingError(object: object, targetType: resultType) } return returnValue } func castOptionalOrThrow<T>(resultType: T.Type, _ object: AnyObject) throws -> T? { if NSNull().isEqual(object) { return nil } guard let returnValue = object as? T else { throw RxCocoaError.CastingError(object: object, targetType: resultType) } return returnValue } func castOrFatalError<T>(value: AnyObject!, message: String) -> T { let maybeResult: T? = value as? T guard let result = maybeResult else { rxFatalError(message) } return result } func castOrFatalError<T>(value: Any!) -> T { let maybeResult: T? = value as? T guard let result = maybeResult else { rxFatalError("Failure converting from \(value) to \(T.self)") } return result }
DelegateProxyクラスを作成
対応させるデリゲート(UIWebViewDelegate)のプロキシークラスを作成する。
class RxUIWebViewDelegateProxy: DelegateProxy, UIWebViewDelegate, DelegateProxyType { class func currentDelegateFor(object: AnyObject) -> AnyObject? { let webView: UIWebView = castOrFatalError(object) return webView.delegate } class func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) { let webView: UIWebView = castOrFatalError(object) webView.delegate = castOptionalOrFatalError(delegate) } }
Reactive ラッパー
UIWebViewのエクステンションとして、Reactiveラッパーを用意する。今回は以下のUIWebViewDelegateメソッドをラップする。
- webViewDidStartLoad(webView: UIWebView)
- webViewDidFinishLoad(webView: UIWebView)
- webView(webView: UIWebView, didFailLoadWithError error: NSError?)
extension UIWebView { // Reactive wrapper for `delegate`. public var rx_delegate: DelegateProxy { return proxyForObject(RxUIWebViewDelegateProxy.self, self) } // Reactive wrapper for webViewDidStartLoad(_:) public var rx_webViewDidStartLoad: Observable<UIWebView> { let sel = #selector(UIWebViewDelegate.webViewDidStartLoad(_:)) let o = rx_delegate .observe(sel) .map { a in return try castOrThrow(UIWebView.self, a[0]) } return o } // Reactive wrapper for webViewDidFinishLoad(_:) public var rx_webViewDidFinishLoad: Observable<UIWebView> { let sel = #selector(UIWebViewDelegate.webViewDidFinishLoad(_:)) let o = rx_delegate .observe(sel) .map { a in return try castOrThrow(UIWebView.self, a[0]) } return o } // Reactive wrapper for webView(_:didFailLoadWithError:) public var rx_webViewDidFailLoadError: Observable<(UIWebView, NSError?)> { let sel = #selector(UIWebViewDelegate.webView(_:didFailLoadWithError:)) let o = rx_delegate .observe(sel) .map { a -> (UIWebView, NSError?) in let webview = try castOrThrow(UIWebView.self, a[0]) let error = try castOptionalOrThrow(NSError.self, a[1]) return (webview, error) } return o } }
戻り値のあるデリゲートメソッドの場合
DelegateProxy.setForwardToDelegate(_:retainDelegate:) を使用して通常と同じように扱う。
使用例
class ExampleViewController: UIViewController, UIWebViewDelegate { @IBOutlet weak var webView: UIWebView! let disposeBag = DisposeBag() override func viewDidLoad() { webView .rx_webViewDidStartLoad .subscribeNext { webview in print("webViewDidStartLoad", webview) }.addDisposableTo(disposeBag) webView .rx_webViewDidFinishLoad .subscribeNext { webview in print("webViewDidFinishLoad", webview) }.addDisposableTo(disposeBag) webView .rx_webViewDidFailLoadError .subscribeNext { (webview, error) in print("webViewDidFailLoadError", webview, error) }.addDisposableTo(disposeBag) webView.rx_delegate.setForwardToDelegate(self, retainDelegate: false) let url = NSURL(string: "https://xxx")! let request = NSURLRequest(URL:url) webView.loadRequest(request) } func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool { print(request.URL?.absoluteString,) return true } }
参考URL
Swift でヒマワリを描く
に触発されて、試しにSwiftで書いてみた。
準備
16進数カラーコードを UIColor に変換する必要があるので、 SwiftでHexColor(#34495eみたいなやつ) - Qiita を参考に、UIColorに簡易イニシャライザを追加する。
extension UIColor { convenience init(hexString: String, alpha: CGFloat) { let hex = hexString.stringByReplacingOccurrencesOfString("#", withString: "") let scanner = NSScanner(string: hex) var rgbValue: UInt32 = 0 if scanner.scanHexInt(&rgbValue) { self.init(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, green: CGFloat((rgbValue & 0xFF00) >> 8) / 255.0, blue: CGFloat(rgbValue & 0xFF) / 255.0, alpha: alpha) } else { self.init(red: 0.0, green: 0.0, blue: 0.0, alpha: alpha) } } convenience init(hexString: String) { self.init(hexString: hexString, alpha: 1.0) } }
mkCoords 関数
配置する円の座標を求める関数。
func mkCoords(n: Int) -> [CGPoint] { // Helper 関数 func coord(n: Int) -> CGPoint { return fromPolar(sqrt(CGFloat(n)), theta: 2.4 * CGFloat(n)) } func fromPolar(r: CGFloat, theta: CGFloat) -> CGPoint { return CGPointMake(r * cos(theta), r * sin(theta)) } return (1...n).map(coord) }
floret 関数
円を作成する関数。とりあえずCALayerにしておく。ここで使用しているカラーセットの定義はここからコピーしてきた。
func floret(r: CGFloat) -> CALayer { let layer = CAShapeLayer() let path = UIBezierPath(arcCenter: CGPointZero, radius: 0.6, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true).CGPath layer.path = path let n = floor(1.4 * sqrt(r)) % 9 let colors = ["#ffffe5","#fff7bc","#fee391","#fec44f", "#fe9929","#ec7014","#cc4c02","#993404", "#662506","#000000"].map(UIColor.init).reverse() as [UIColor] layer.fillColor = colors[Int(n)].CGColor return layer }
sunflower 関数
ひまわりのレイヤーを返す関数。
func sunflower(n: Int) -> [CALayer] { func florets(n: Int) -> [CALayer] { return (1...n).map { floret(sqrt(CGFloat($0))) } } return zip(mkCoords(n), florets(n)).map { (position, layer) in // 円の位置を変更 layer.position = CGPointMake(position.x + 100, position.y + 100) return layer } }
SunflowerView クラス
ひまわりを描画するView
class SunflowerView: UIView { override func drawRect(rect: CGRect) { let baseLayer = CALayer() baseLayer.frame = CGRectMake(0, 0, 200, 200) sunflower(2000).forEach { baseLayer.addSublayer($0) } // ちょっと拡大 baseLayer.transform = CATransform3DMakeScale(3.0, 3.0, 0.0) layer.addSublayer(baseLayer) } }
完成
SunflowerView(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200.0))
をaddSubview等すれば完成。以下のようなひまわりが描画される。
注意
試す場合は、Playgroundでは時間がかかるので、Single View Application で行う方が良い(?)
参考URL
Swift 型消去 (Type Erasure)
try! Swiftで発表のあった型消去の話。
この例ではSwiftにおける型消去技法を使用して、結城さんの 増補改訂版Java言語で学ぶデザインパターン入門 Iteratorパターンを愚直にSwiftで実装する。
前提知識
- [増補改訂版Java言語で学ぶデザインパターン入門] Iteratorパターン
- Swift ジェネリクス・プロトコルの基礎
環境
Iteratorインターフェース
要素の数え上げをするインターフェース。
protocol Iterator { typealias Element func next() -> Element? }
Aggregateインターフェース
SwiftではJava版のように、以下のようにAggregateを定義することはできない。
protocol Aggregate { func iterator() -> Iterator }
この場合、下のようなエラーになる。
error: protocol 'Iterator' can only be used as a generic constraint because it has Self or associated type requirements
SwiftではAssociated Typeを使って定義されたプロトコル(抽象型)をそのまま使うことはできないので、AnyIteratorを返すようにする。
protocol Aggregate { typealias Element func iterator() -> AnyIterator<Element> }
AnyIteratorクラス(型消去法)
IteratorクラスのAssociated Typeを消去するためのラッパークラス。
Associated Typeからジェネリクス型に変換する。
class AnyIterator<Element>: Iterator { private let _next: () -> Element? init<Base: Iterator where Base.Element == Element>(_ base: Base) { _next = base.next } func next() -> Element? { return _next() } }
Bookクラス
本を表すクラス。
class Book { let name: String init(name: String) { self.name = name } func getName() -> String { return name } }
BookShelfクラス
本棚を表すクラス。
class BookShelf: Aggregate { var books: [Book] = [] func getBookAt(index: Int) -> Book? { return index < books.count ? books[index] : nil } func appendBook(book: Book) { books.append(book) } // Aggregateの実装 func iterator() -> AnyIterator<Book> { let bookShelfIterator = BookShelfIterator(bookShelf: self) return AnyIterator(bookShelfIterator) } }
BookShelfIterator
BookShelfをイテレートするクラス。
class BookShelfIterator: Iterator { let bookShelf: BookShelf var index: Int = 0 init(bookShelf: BookShelf) { self.bookShelf = bookShelf } // Iterator next func next() -> Book? { let book = bookShelf.getBookAt(index) index++ return book } }
実行例
let bookShelf = BookShelf() // 本棚(BookShelf)に本(Book)を入れる bookShelf.appendBook(Book(name: "Book A")) bookShelf.appendBook(Book(name: "Book B")) bookShelf.appendBook(Book(name: "Book C")) bookShelf.appendBook(Book(name: "Book D")) // bookをイテレートするAnyIterator<Book>を生成 let bookIterator = bookShelf.iterator() // イテレート while let book = bookIterator.next() { print(book.getName()) }
この出力は、
Book A Book B Book C Book D
参考URL
Note
RxMoya (RxSwift + Moya)
Swift Moyaでモヤっとしているネットワークレイヤーを解決 - タコさんブログ のRx編。
RxMoyaProviderでは、MoyaProviderにコールバッククロージャを与える代わりに、オブザーバブルなレスポンスを使用する。
今回の例もiTunes Apple APIを使用して音楽のアルバムを検索する。
環境
Moya & RxMoya をPodでインストール
use_frameworks! pod 'Moya/RxSwift' pod 'SwiftyJSON' # JSONに変換するのに使用
RxMoya 準備
API ターゲットを設定、ターゲットをTargetTypeプロトコルに準拠させる 方法は 前記事のMoya とほぼ同じ。違いは import Moya としている箇所を
import RxMoya
RxMoyaに変更し、enumの値に対応したHTTPメソッドを指定 している箇所のMoya.Methodを
internal var method: RxMoya.Method { // 略 }
RxMoyaに変更する。
リクエスト方法
MoyaProviderのリクエストをサブスクライブするとリクストが開始される。
注意として、リクエストが完了する前にオブザーバブルを破棄するとキャンセルされる。キャンセルされないようにMoyaProviderをローカルに保持する必要がある。
// providerを生成 let provider = RxMoyaProvider<iTunes>() let disposeBag = DisposeBag()
実際のリクエスト。
func requestWithRxMoya() { // 検索ターム"swift"を指定して、Search APIをリクエストする // responseの型は Observable<Response> let response = provider.request(.Search("swift")) let disposable = response .map { (response: Response) -> JSON in // SwiftyJSONを使用して、response.date (NSData型) を // JSONに変換 return JSON(data: response.data) } .subscribeNext { // results配列(アルバム情報)を出力 if let results = $0["results"].array { print(results) } else { print("Error") } } // メモリ管理 disposeBag.addDisposable(disposable) }
以下のようなアルバム情報の配列が出力される。
[{ "trackCount" : 14, "collectionId" : 907242701, "collectionName" : "1989", "artistName" : "Taylor Swift", ... },{ ... }, ...
参考URL
RxSwift 入門 その7
RxSwift 入門 その6 - タコさんブログ の続き。 今回は、RxSwiftプレイグラウンドの Mathematical and Aggregate Operators 、Connectable Observable Operators の項。プレイグラウンドの内容としてはこれで最後。
Mathematical and Aggregate Operators
この項では、Observableによって送信される全てのアイテムのシーケンス(ストリーム)に作用するオペレータに関して説明してある。
以下、プレイグラウンドに説明してあるMathematical and Aggregate Operatorsに関するオペレータ。
- concat
- reduce
concat
2つ、または、それ以上のObservablesから送信されるアイテム(イベント)を割り込みすることなく送信する。
let s1 = [0, 1, 2].toObservable() let s2 = [5, 6, 7, 8].toObservable() _ = s1.concat(s2) .subscribe { print($0) }
この出力は、
Next(0) Next(1) Next(2) Next(5) Next(6) Next(7) Next(8) Completed
このマーブルダイアグラムは以下のように表せられる。
s1 -0-1-2| s2 -5-6-7-8| ↓ concat s2 r -0-1-2-5-6-7-8|
reduce
Observableが送信する各アイテム(イベント)に関数を順次適用し、結果を送信する。reduceは、完了するまでシーケンス(ストリーム)の各要素に関数を実行し、蓄積された値をメッセージと共に送信する。これはシーケンス上のSwiftのreduce関数と同じような働きをする。
let stream = (0..<10).toObservable() _ = stream .reduce(0, accumulator: +) // 初期値 0 .subscribe { print($0) }
この出力は、
Next(45) Completed
Connectable Observable Operators
この項では、Connectable Observable に関して説明してある。Connectable Observableは、サブスクライブした時ではなく、connect()メソッドが呼ばれた時にアイテムの送信が開始されること以外は、通常のObservableに似ている。このようにして、Observableがアイテムの送信を開始する前に、Observableをサブスクライブするために、すべての期待するサブスクライバーを待つことができる。
以下、プレイグラウンドに説明してあるConnectable Observable Operatorsに関するオペレータ。
- multicast
- replay
- publish
この項を実行するにあたっての注意
この項のサンプルは、//sampleWithoutConnectableOperators() のようにコメントアウトされているので、実行を確認する場合はコメントを外す必要がある
intervalオペレータを使用して、整数のシーケンス(ストリーム)を生成している。intervalオペレータは与えられた時間間隔ごとに無限に増加する整数のアイテムを送信するシーケンスを生成する
intervalオペレータを使用しているため、 XCPlaygroundPage.currentPage.needsIndefiniteExecution = true とする必要がある。また、遅延させる必要があるため、サポート関数として以下のdelay関数を利用している
// delay秒後に、メインスレッドでclosureを実行する public func delay(delay:Double, closure:()->()) { dispatch_after( dispatch_time(DISPATCH_TIME_NOW,Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), closure) }
Without Connect
connectを使用しない場合の、挙動確認。
// 1秒間隔で整数を送信するシーケンスを生成する let s = Observable<Int>.interval(1, scheduler: MainScheduler.instance) // 1つ目のサブスクライブ _ = s .subscribe { print("first subscription \($0)") } // 2秒後に、さらにサブスクライブする delay(2) { print("-- after delay(2) --") _ = s .subscribe { print("second subscription \($0)") } }
この出力は、
first subscription Next(0) first subscription Next(1) -- after delay(2) -- first subscription Next(2) second subscription Next(0) first subscription Next(3) second subscription Next(1) first subscription Next(4) // ...
after delay(2) 後の first subscription の値が 2 で、second subscription の値は 0 になっていることが分かる。
multicast
let subject = PublishSubject<Int>() _ = subject .subscribe { print("Subject \($0)") } // multicast を使用して Connectable Observable を取得する let s = Observable<Int>.interval(1, scheduler: MainScheduler.instance) .multicast(subject) _ = s .subscribe { print("first subscription \($0)") } delay(2) { print("-- connect 呼びだし --") s.connect() } delay(4) { print("-- after delay(4) --") _ = s .subscribe { print("second subscription \($0)") } } delay(6) { print("-- after delay(6) --") _ = s .subscribe { print("third subscription \($0)") } }
この出力は、
-- connect 呼びだし -- Subject Next(0) first subscription Next(0) -- after delay(4) -- Subject Next(1) first subscription Next(1) second subscription Next(1) Subject Next(2) first subscription Next(2) second subscription Next(2) -- after delay(6) -- Subject Next(3) first subscription Next(3) second subscription Next(3) third subscription Next(3) Subject Next(4) first subscription Next(4) second subscription Next(4) third subscription Next(4) // ...
connect呼びだし後にアイテム(イベント)が送信され、first・second・third subscriptionの値が同じになっていることが分かる。
replay
replayオペレータは、Observableがアイテムの送信を開始した後に、オブザーバがサブスクライブしても、全てのオブザーバが同じ送信されたアイテム(イベント)のシーケンス(ストリーム)を受け取ることを保証する。
publish = multicast + replay subject
let s = Observable<Int>.interval(1, scheduler: MainScheduler.instance) .replay(1) // Buffer Size:1 _ = s .subscribe { print("first subscription \($0)") } delay(2) { print("-- connect 呼びだし --") s.connect() } delay(4) { print("-- after delay(4) --") _ = s .subscribe { print("second subscription \($0)") } } delay(6) { print("-- after delay(6) --") _ = s .subscribe { print("third subscription \($0)") } }
この出力は、
-- connect 呼びだし -- first subscription Next(0) -- after delay(4) -- second subscription Next(0) first subscription Next(1) second subscription Next(1) first subscription Next(2) second subscription Next(2) -- after delay(6) -- third subscription Next(2) first subscription Next(3) second subscription Next(3) third subscription Next(3) first subscription Next(4) second subscription Next(4) third subscription Next(4) // ...
publish
通常のObservableをConnectable Observableへ変換する。
publish = multicast + publish subject
つまり、publishは基本的にはreplay(0)と同じ。
let s = Observable<Int>.interval(1, scheduler: MainScheduler.instance) .publish() _ = s .subscribe { print("first subscription \($0)") } delay(2) { print("-- connect 呼びだし --") s.connect() } delay(4) { print("-- after delay(4) --") _ = s .subscribe { print("second subscription \($0)") } } delay(6) { print("-- after delay(5) --") _ = s .subscribe { print("third subscription \($0)") } }
この出力は、
-- connect 呼びだし -- first subscription Next(0) -- after delay(4) -- first subscription Next(1) second subscription Next(1) first subscription Next(2) second subscription Next(2) -- after delay(5) -- first subscription Next(3) second subscription Next(3) third subscription Next(3) first subscription Next(4) second subscription Next(4) third subscription Next(4) // ...
参考URL
Mathematical and Aggregate Operators
Connectable Observable Operators
RxSwift 入門 その6
RxSwift 入門 その5 - タコさんブログ の続き。
今回は、RxSwiftプレイグラウンドの Error Handling Operators 、Observable Utility Operators 、 Conditional and Boolean Operators の項。
Error Handling Operators
この項では、Observableからのエラー通知から復帰するのに役立つオペレータに関して説明してある。
以下、プレイグラウンドに説明してあるError Handlingに関するオペレータ。
- catchError
- retry
catchError
catchErrorオペレータは、エラーなしでシーケンスを継続することによってエラー通知から復帰する。
let s = PublishSubject<Int>() _ = s .catchError { error in return [10,11,12].toObservable() } .subscribe { print($0) } s.on(.Next(1)) s.on(.Next(2)) s.on(.Next(3)) s.on(.Error(NSError(domain: "Test", code: 0, userInfo: nil)))
この出力は、
Next(1) Next(2) Next(3) Next(10) Next(11) Next(12) Completed
このマーブルダイアグラムは以下のように表せられる。
-1-2-3-x -10-11-12| ↓ 結果 -1-2-3-1-1-1| 0-1-2|
retry
retryオペレータは、ソースObservableがエラーを送信したら、再度サブスクライブする。
以下の例は、bad practice とコメントしてある通り、例を示すためだけのもの。
var counter = 1 let stream = Observable<Int>.create { observer in observer.on(.Next(0)) observer.on(.Next(1)) observer.on(.Next(2)) // エラーを故意に起こす if counter < 2 { let error = NSError(domain: "Test", code: 0, userInfo: nil) observer.on(.Error(error)) counter += 1 } observer.on(.Next(3)) observer.on(.Next(4)) observer.on(.Next(5)) observer.on(.Completed) return NopDisposable.instance } _ = stream .retry() .subscribe { print($0) }
この出力は、
Next(0) Next(1) Next(2) Next(0) Next(1) Next(2) Next(3) Next(4) Next(5) Completed
このマーブルダイアグラムは以下のように表せられる。
-0-1-2-x -0-1-2-3-4-5| ↓ 結果 -0-1-2-0-1-2-3-4-5|
Observable Utility Operators
この項では、Observableを操作するのに役立つオペレータ、subscribeオペレータとわずかに異なるオペレータに関して説明してある。
以下、この項で説明してあるオペレータ。
- subscribe
- subscribeNext
- subscribeCompleted
- subscribeError
- doOn
subscribe
おなじみのsubscribeオペレータ。
subscribeオペレータはobserverとobservableを結びつけるのりとなるオペレータ。
おなじみなので例は省略する。
subscribeNext, subscribeCompleted
subscribeNextは、Observableにエレメントハンドラをサブスクライブするオペレータ。Observableがアイテム(イベント)を送信するときに呼ばれる。
subscribeCompletedは、Observableにコンプリーションハンドラをサブスクライブするオペレータ。エラーが起こっていない場合、Observableが最後のアイテムを送信した後にコンプリーションハンドラが呼ばれる。
let stream = PublishSubject<Int>() // Nextをサブスクライブ _ = stream .subscribeNext { print($0) } // Completedをサブスクライブ _ = stream .subscribeCompleted { print("Completed") } stream.on(.Next(1)) stream.on(.Completed)
この出力は、
1 Completed
subscribeError
subscribeErrorは、Observableにエラーハンドラをサブスクライブするオペレータ。
let stream = PublishSubject<Int>() _ = stream .subscribeError { error in print("Error:", error) } let error = NSError(domain: "Examples", code: -1, userInfo: nil) stream.on(.Error(error))
この出力は、
Error: Error Domain=Examples Code=-1 "(null)"
doOn
doOnオペレータは、いろいろなObservableのライフサイクルイベント(Next, Complete, Error)に応じるためにアクションを登録する。
let stream = (0...6).toObservable() _ = stream .doOn { print("インターセプトされたイベント \($0)") }.filter { $0 % 2 == 0 }.subscribe { print($0) }
この出力は、
インターセプトされたイベント Next(0) Next(0) インターセプトされたイベント Next(1) インターセプトされたイベント Next(2) Next(2) インターセプトされたイベント Next(3) インターセプトされたイベント Next(4) Next(4) インターセプトされたイベント Next(5) インターセプトされたイベント Next(6) Next(6) インターセプトされたイベント Completed Completed
Conditional and Boolean Operators
この項では、いくつかのObservablesによって送信される1つ以上のObservables、または、アイテム(イベント)を評価するオペレータに関して説明してある。
以下、この項で説明してあるオペレータ。
- takeUntil
- takeWhile
takeUntil
takeUntilは、2番目のObservableがアイテムを送信、または終了した後に、一番目のObservableから送信されたアイテムを破棄する。
let s1 = PublishSubject<Int>() let s2 = PublishSubject<Int>() _ = s1 .takeUntil(s2) .subscribe { print($0) } s1.on(.Next(1)) s1.on(.Next(2)) s1.on(.Next(3)) s1.on(.Next(4)) s2.on(.Next(1)) s1.on(.Next(5))
この出力は、
Next(1) Next(2) Next(3) Next(4) Completed
このマーブルダイアグラムは以下のように表せられる。
s1 -1-2-3-4---5- s2 -1-- ↓ takeUntil s2 r -1-2-3-4-|
takeWhile
takeWhileは、指定された条件が偽となるまでObservableから送信されたアイテム(イベント)を反映する。
let stream = (0..<10).toObservable() _ = stream .takeWhile { x in x < 4 } .subscribe { print($0) }
この出力は、
Next(0) Next(1) Next(2) Next(3) Completed
このマーブルダイアグラムは以下のように表せられる。
-0-1-2-3-4-5-6-7-8-9| ↓ takeWhile { x < 4 } -0-1-2-3|
参考URL
Error Handling operators
Observable Utility Operators
Conditional and Boolean Operators
RxSwift 入門 その5
Combination operators
RxSwift 入門 その4 - タコさんブログ の続き。
今回は、RxSwiftプレイグラウンドの Combination operators の項。
この項では、単一のObservableを生成するために、多数のソースObservablesと連携するオペレータに関して説明してある。
以下、プレイグラウンドに説明してあるCombinationに関するオペレータ。
- startWith
- combineLatest
- zip
- merge
- switchLatest
startWith
startWith オペレータは、ソースObservableからアイテム(イベント)を送信する前に、特定のアイテムのシーケンス(ストリーム)を送信する。
let stream = [2,3].toObservable() _ = stream .startWith(1) .startWith(0) .subscribe { print($0) }
この出力は、
Next(0) Next(1) Next(2) Next(3) Completed
このマーブルダイアグラムは以下のように表せられる。
-----2-3| ↓ startWith(1) ---1| ↓ startWith(0) -0| ↓ 結果 -0-1-2-3|
combineLatest
combineLatestオペレータは、2つのObsrvablesの内どちらかから1つのアイテムが送信される時、指定された関数によって、各Observableから送信される最新のアイテムを結合し、この関数の評価に基づいたアイテムを送信する。
let s = PublishSubject<Int>() let t = PublishSubject<String>() // s,t を結合 _ = Observable.combineLatest(s, t) { "\($0)\($1)" } .subscribe { print($0) } s.onNext(1) t.onNext("a") s.onNext(2) t.onNext("b")
この出力は、
Next(1a) Next(2a) Next(2b)
このマーブルダイアグラムは以下のように表せられる。
s -1-2-- t --a-b- ↓ combineLatest(s, t) { "\($0)\($1)" } r --122- aab // r:resultの意味
zip
多数のObservablesが送信するアイテムを指定した関数によって結合し、その関数の結果に基づき、各組み合わせの単一のアイテムを送信する。
let s = (0...4).toObservable() let t = ["a", "b", "c"].toObservable() // zip _ = Observable.zip(s, t) { "\($0)\($1)" } .subscribe { print($0) }
この出力は、
Next(0a) Next(1b) Next(2c) Completed
このマーブルダイアグラムは以下のように表せられる。
s -0-1-2-3-4| t --a-b-c| ↓ zip(s, t) { "\($0)\($1)" } r --0-1-2| a-b-c|
merge
mergeオペレータは、多数のObservablesから送信されるアイテムをマージすることによって、1つのObservableに結合する。
let s = PublishSubject<Int>() let t = PublishSubject<Int>() _ = Observable.of(s, t) .merge() .subscribe { print($0) } s.on(.Next(1)) t.on(.Next(2)) s.on(.Next(1)) s.on(.Next(1)) t.on(.Next(2))
この出力は、
Next(1) Next(2) Next(1) Next(1) Next(2)
このマーブルダイアグラムは以下のように表せられる。
s -1--1--1-- t --2-----2- ↓ merge r -12-1--12-
switchLatest
switchLatestオペレータは、Observablesを送信するObservableを、それらのObservablesの直前に送信されたアイテムを送信する単一のObservableへ変換する。
let s = Variable(0) let t = Variable(10) // u は Observable<Observable<Int>> のようなもの let u = Variable(s.asObservable()) _ = u.asObservable() .switchLatest() .subscribe { print($0) } s.value = 1 s.value = 2 u.value = t.asObservable() t.value = 11 t.value = 12 s.value = 3 s.value = 4
この出力は、
Next(0) Next(1) Next(2) Next(10) Next(11) Next(12) Completed
このマーブルダイアグラムは以下のように表せられる。
s -0-1-2----3-4| t -------1-1-1| 0 1 2| ↓ switchLatest r -0-1-2-1-1-1| 0 1 2|