タコさんブログ

プログラミングメモと小言

デリゲートメソッドを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 でヒマワリを描く

bitterharvest.hatenablog.com

に触発されて、試しに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等すれば完成。以下のようなひまわりが描画される。

f:id:tiny_wing:20160331190347p:plain

注意

試す場合は、Playgroundでは時間がかかるので、Single View Application で行う方が良い(?)

参考URL

Swift 型消去 (Type Erasure)

try! Swiftで発表のあった型消去の話。
この例では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 OperatorsConnectable 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

この項を実行するにあたっての注意

  1. この項のサンプルは、//sampleWithoutConnectableOperators() のようにコメントアウトされているので、実行を確認する場合はコメントを外す必要がある

  2. intervalオペレータを使用して、整数のシーケンス(ストリーム)を生成している。intervalオペレータは与えられた時間間隔ごとに無限に増加する整数のアイテムを送信するシーケンスを生成する

  3. 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 OperatorsObservable Utility OperatorsConditional 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|

参考URL