RxSwift 入門 その4
Filtering Observables
RxSwift 入門 その3 - タコさんブログ の続き。
今回は、RxSwiftプレイグラウンドの Filtering Observables の項。
この項では、ソースObservableから選択的にアイテム(イベント)を送信するオペレータに関して説明してある。
以下、プレイグラウンドで説明してあるFilteringに関するオペレータ。
- filter
- distinctUntilChanged
- take
RxSwiftには、この他にも以下のオペレータが利用できる:
- debounce
- elementAt
- ignoreElements
- sample
- skip
- takeLast
filter
filterオペレータは、述語が真となるアイテムをObservableから送信する。
let stream = (1..<10).toObservable() _ = stream .filter { $0 % 2 == 0 // 偶数をフィルタ } .subscribe { print($0) }
この出力は、
Next(2) Next(4) Next(6) Next(8) Completed
このマーブルダイアグラムは以下のように表せられる。
-1-2-3- ... -8-9| ↓ filter { x % 2 == 0 } ---2--- ... -8--|
distinctUntilChanged
distinctUntilChangedオペレータは、Observableによって送信される重複するアイテムを除去する。
let stream = [1, 2, 3, 1, 1, 4].toObservable() _ = stream .distinctUntilChanged() .subscribe { print($0) }
この出力は、
Next(1) Next(2) Next(3) Next(1) Next(4)
このマーブルダイアグラムは以下のように表せられる。
-1-2-3-1-1-4| ↓ distinctUntilChanged -1-2-3-1---4|
take
takeオペレータは、Observableによって送信される最初のn個のアイテムのみを送信する。
let stream = [1, 2, 3, 1, 1, 4].toObservable() _ = stream .take(2) .subscribe { print($0) }
この出力は、
Next(1) Next(2) Completed
このマーブルダイアグラムは以下のように表せられる。
-1-2-3-1-1-4| ↓ take(2) -1-2|
参考URL
RxSwift 入門 その3
Transforming Observables
RxSwift 入門 その2 - タコさんブログの続き。
今回は、RxSwiftプレイグラウンドの Transforming Observables の項。
この項では、Observableから送信されるアイテム(イベント)を変換するオペレータについて説明してある。
変換に関するオペレータ
以下、プレイグラウンドに説明してある変換に関するオペレータ。
- map
- flatMap
- scan
この他にも、以下の変換に関するオペレータがある。
- buffer
- window
map (a.k.a. select)
mapオペレータはObservableによって送信された各アイテム(イベント)に関数を適用することによって、アイテムを変換する。
RxJavaではSelectになっている。
下の例では、map で Int -> UnicodeScalar に変換している。
// ストリームを生成 let stream = (65..<70).toObservable() _ = stream .map { x in UnicodeScalar(x) } .subscribe { print($0) }
この出力は、
Next(A) Next(B) Next(C) Next(D) Next(E) Completed
このマーブルダイアグラムは以下のように表せられる。
---6---6---6---6---6| 5 6 7 8 9| ↓ map ---A---B---C---D---E|
ここで、
---65---66---67---68---69|
の数字の二桁めを以下のように2段めに記述した。
---6---6---6---6---6| 5 6 7 8 9|
flatMap
Observableによって送信されたアイテム(イベント)をObservablesに変換し、その後、それらから送信されるアイテムを1つのObservableへ変換(フラットに)する。
下の例では、1〜29のシーケンス値をAのシーケンス、Bのシーケンス、...とZまで繰り返し、27以降は空のシーケンスに変換し、これらを1つのA〜Zのシーケンスに変換している。
let stream = (1..<30).toObservable() _ = stream .flatMap { (x: Int) -> Observable<UnicodeScalar> in if case (1..<27) = x { return Observable.just(UnicodeScalar(x + 64)) } else { return Observable.empty() } } .subscribe { print($0) }
この出力は、
Next(A) Next(B) Next(C) Next(D) ... // 略 Next(X) Next(Y) Next(Z) Completed
このマーブルダイアグラムは以下のように表せられる。
-1-2 ... -2-2-2-2| 6 7 8 9| A| B| ... Z| | | | ↓ flatMap -A-B- ... -Z----|
scan
Observableによって送信された各アイテム(イベント)に関数を逐次適用し、そして、それぞれの連続値を送信する。
下の例では、0をアキュムレータの初期値とし、0〜3までの値を足している。
let stream = Observable.of(0, 1, 2, 3) _ = stream .scan(0) { acum, elem in acum + elem } .subscribe { print($0) }
この出力は、
Next(0) Next(1) Next(3) Next(6) Completed
このマーブルダイアグラムは以下のように表せられる。
-0-1-2-3| ↓ scan -0-1-3-6|
参考URL
RxSwift 入門 その2
RxSwift 入門 その1 - タコさんブログ の続き。
今回は、RxSwiftプレイグラウンドのSubjectsの項。
Subject は Observer 、また Observable の両方として機能し、Hotなオブザーバブルである。
Erik MeijerはSubjectを好んでいないようだ(Why Does E.Meijer not like Subjects?)。
Subjects
RxSwiftでは以下のSubjectクラスが利用できる。
- PublishSubject
- ReplaySubject
- BehaviorSubject
- Variable
PublishSubject
PublishSubjectはサブスクリプションの後のソースObservable(s)が送信したアイテム(イベント)のみを送信する。
// メモリ管理 let disposeBag = DisposeBag() // PublishSubject生成 let subject = PublishSubject<String>() // サブスクライブ subject.subscribe({ event in print("Subscription: 1, event:\(event)") }).addDisposableTo(disposeBag) subject.on(.Next("a")) subject.on(.Next("b")) // "b"を送信した後に、 // 更にサブスクライブ subject.subscribe({ event in print("Subscription: 2, event:\(event)") }).addDisposableTo(disposeBag) subject.on(.Next("c")) subject.on(.Next("d"))
この出力は、
Subscription: 1, event:Next(a) Subscription: 1, event:Next(b) Subscription: 1, event:Next(c) Subscription: 2, event:Next(c) Subscription: 1, event:Next(d) Subscription: 2, event:Next(d)
ReplaySubject
ReplaySubjectはオブザーバがいつサブスクライブするかに関わらずソースObservable(s)が送信した全てのアイテム(イベント)をオブザーバに送信する。
// メモリ管理 let disposeBag = DisposeBag() // ReplaySubject生成 // bufferSizeに指定した数だけアイテムがキャッシュされる // 全てのアイテムをキャッシュする場合は、createUnbounded を使用する let subject = ReplaySubject<String>.create(bufferSize: 1) // サブスクライブ subject.subscribe({ event in print("Subscription: 1, event:\(event)") }).addDisposableTo(disposeBag) subject.on(.Next("a")) subject.on(.Next("b")) // "b"を送信した後に、 // 更にサブスクライブ subject.subscribe({ event in print("Subscription: 2, event:\(event)") }).addDisposableTo(disposeBag) subject.on(.Next("c")) subject.on(.Next("d"))
この出力は、
Subscription: 1, event:Next(a) Subscription: 1, event:Next(b) Subscription: 2, event:Next(b) Subscription: 1, event:Next(c) Subscription: 2, event:Next(c) Subscription: 1, event:Next(d) Subscription: 2, event:Next(d)
BehaviorSubject
オブザーバがBehaviorSubjectをサブスクライブした時、ソースObservableから送信された最新のアイテム(イベント)を送信し、それから、ソースObservableから送信された他のアイテムを送信し続ける。
// メモリ管理 let disposeBag = DisposeBag() // BehaviorSubject生成 // 初期値 "Initial value" を与える let subject = BehaviorSubject<String>(value: "Initial value") // サブスクライブ subject.subscribe({ event in print("Subscription: 1, event:\(event)") }).addDisposableTo(disposeBag) subject.on(.Next("a")) subject.on(.Next("b")) // "b"を送信した後に、 // 更にサブスクライブ subject.subscribe({ event in print("Subscription: 2, event:\(event)") }).addDisposableTo(disposeBag) subject.on(.Next("c")) subject.on(.Next("d")) subject.on(.Completed)
この出力は、
Subscription: 1, event:Next(Initial value) Subscription: 1, event:Next(a) Subscription: 1, event:Next(b) Subscription: 2, event:Next(b) Subscription: 1, event:Next(c) Subscription: 2, event:Next(c) Subscription: 1, event:Next(d) Subscription: 2, event:Next(d) Subscription: 1, event:Completed Subscription: 2, event:Completed
Variable
VariableはBehaviorSubjectをラップする。BehaviorSubjectに対しての利点は、Variableは明示的に complete または error になることがない。また、Variableはdeallocated時に自動的に完了する。
// Variable のdeallocate時にCompleteを見るためにdoで囲む do { // メモリ管理 let disposeBag = DisposeBag() // Variable生成 // 初期値 "Initial value" を与える let variable = Variable("Initial value") variable.asObservable().subscribe({ event in print("Subscription: 1, event:\(event)") }).addDisposableTo(disposeBag) variable.value = "a" variable.value = "b" // valueを"b"に変更した後に、 // 更にサブスクライブ variable.asObservable().subscribe({ event in print("Subscription: 2, event:\(event)") }).addDisposableTo(disposeBag) variable.value = "c" variable.value = "d" print("--- スコープを抜けて variable はdeallocateされる ---") }
この出力は、
Subscription: 1, event:Next(Initial value) Subscription: 1, event:Next(a) Subscription: 1, event:Next(b) Subscription: 2, event:Next(b) Subscription: 1, event:Next(c) Subscription: 2, event:Next(c) Subscription: 1, event:Next(d) Subscription: 2, event:Next(d) --- スコープを抜けて variable はdeallocateされる --- Subscription: 1, event:Completed Subscription: 2, event:Completed
参考URL
RxSwift 入門 その1
とりあえずRxSwiftのプレイグラウンドのIntroductionに書いてあるサンプルを見ていく。Introductionには「RxSwiftを理解する鍵は、Observablesの概念を理解することである」と書いてあるとおり、主にObservableの生成について説明してある。
環境
準備
- GitHubからReactiveX/RxSwiftをクローンする
- Rx.xcworkspace を開いて、RxSwiftをビルド
- ビルドスキームを RxSwift-OSX に変更
- ビルド (Cmd + B)
Observables
Observables は時間とともに送信されるイベントの列(ストリーム)。
Marble Diagrams (マーブルダイアグラム)はObservables(データストリーム)を視覚化する図形で、下の図のようなもの。
---◯---△---□---|-->
- 横線(-):Observableのタイムラインを表す
- 丸(◯)、三角(△)、四角(□):Observableによって送信されるアイテム(データ)を表す
- 縦線(|):Observableが正常に完了したことを表す
Observablesの生成とサブスクライブ
Observablesを生成するには、以下の関数が利用できる。
- empty
- just
- of
- toObservable
- create
- error
- deffered
Observableを監視する(observe, listen, subscribeなどの呼び方がある?)には subscribe メソッドを呼ぶ。subscribeの中で、プッシュされたイベントにリアクト(反応)する。今回は単純にコンソールに出力しているだけ。
empty 関数
empty は空ストリームを生成する。
.Completed メッセージを唯一送信する。
// ストリームを生成 let emptyStream: Observable<Int> = Observable.empty() // ストリームをサブスクライブする let subscription = emptyStream .subscribe { event in print(event) }
この出力は、
Completed
emptyのマーブルダイアグラムは、
--|-->
never 関数
never はエレメントまたは、完了を一度も送信しないストリームを生成する。
let neverStream: Observable<String> = Observable.never() let subscription = neverStream .subscribe { _ in print("このブロックは一度も呼び出されない") }
neverのマーブルダイアグラムは、
---->
just 関数
just は一つのイベントを含むストリームを生成し、サブスクライバーに2つのメッセージを送信する。
1つ目はイベントの値、2つ目は .Completed メッセージ。
let singleElementStream = Observable.just(32) let subscription = singleElementStream .subscribe { event in print(event) }
この出力は、
Next(32) Completed
just(32)のマーブルダイアグラムは、
--32--|-->
of 関数
Observable.of 関数は一定数のイベントのストリームを生成する。
let stream = Observable.of(0, 1, 2) let subscription = stream .subscribe { event in print(event) }
この出力は、
Next(0) Next(1) Next(2) Completed
Observable.of(0, 1, 2)のマーブルダイアグラムは、
--0--1--2--|-->
toObservable
toObservable はSequenceType (Array, Range とか) からシーケンスを生成する。
let streamFromArray = [0, 1, 2].toObservable() let subscription = streamFromArray .subscribe { event in print(event) }
出力、マーブルダイアグラムは Observable.of(0, 1, 2)と同様。
create 関数
create はクロージャからストリームを生成する。
この例はカスタム版のjust関数。
let myJust = { (singleElement: Int) -> Observable<Int> in return Observable.create { observer in observer.on(.Next(singleElement)) observer.on(.Completed) return NopDisposable.instance } } let singleElementStream = myJust(32) let subscription = singleElementStream .subscribe { event in print(event) }
出力、マーブルダイアグラムはjust(32)と同様。
※ NopDisposable の Nop は No Operation の略
error 関数
error はエラーで終了するストリーム(Observable)を生成する。
let error = NSError(domain: "Error Test", code: -1, userInfo: nil) let erroredStream: Observable<Int> = Observable.error(error) let subscription = erroredStream .subscribe { event in print(event) }
この出力は、
Error(Error Domain=Error Test Code=-1 "(null)")
errorのマーブルダイアグラムは、
--x-->
deffered 関数
deferred はオブザーバがサブスクライブするまで待ち、サブスクライブ後、Observableを生成する。
let deferredStream: Observable<Int> = Observable.deferred { print("creating") return Observable.create { observer in print("emmiting") observer.on(.Next(0)) observer.on(.Next(1)) return NopDisposable.instance } } print("firstSubscription") let firstSubscription = deferredStream .subscribe { event in print(event) } print("secondSubscription") let secondSubscription = deferredStream .subscribe { event in print(event) }
この出力は、
firstSubscription creating emmiting Next(0) Next(1) secondSubscription creating emmiting Next(0) Next(1)
マーブルダイアグラムは、
firstSubscriptionのストリーム --0--1--2--|--> secondSubscriptionのストリーム --0--1--2--|-->
その他
RxCocoaには、この他にもたくさんの役に立つメソッドが用意されている。例えば、
- KVOをラップする rx_observe
- @IBActionをラップする rx_tap
- NotificationCenterのイベントをラップする rx_notification
参考URL
Swift Moyaでモヤっとしているネットワークレイヤーを解決
MoyaはAlamofireを直接さわることなく、ネットワークレイヤーを抽象化してくれるライブラリ。 イメージとしては、下の図のような感じ。
Moyaの特徴として、
今回の例はiTunes Apple APIを使用して音楽のアルバムを検索する。
環境
MoyaをPodでインストール
use_frameworks! pod 'Moya'
MoyaをインストールすればAlamofireもインストールしてくれるので、AlamofireをPodfileに書く必要がない。
API ターゲットを設定
iTunesの検索には以下のURLを使用する:
https://itunes.apple.com/search?media=music&entity=album&term=検索キーワード
enumでターゲットAPIを設定する。
今回は検索するだけなので、Searchとする。
internal enum iTunes { case Search(String) // 検索キーワードを付随させる }
ターゲットをTargetTypeプロトコルに準拠させる
enumで定義したターゲット(iTunes)をTargetTypeプロトコルに準拠させ、以下の4つを実装する。
- var baseURL: NSURL { get }
- var path: String { get }
- var method: Moya.Method { get }
- var parameters: [String: AnyObject]? { get }
- var sampleData: NSData { get }
extension iTunes: TargetType { // ベースURLを文字列で定義 private var _baseURL:String { return "https://itunes.apple.com" } public var baseURL: NSURL { return NSURL(string: _baseURL)! } // enumの値に対応したパスを指定 public var path: String { switch self { case .Search: return "/search" } } // enumの値に対応したHTTPメソッドを指定 public var method: Moya.Method { switch self { case .Search: return .GET } } // enumの値に対応したパラメータを指定 public var parameters: [String: AnyObject]? { switch self { case .Search(let term): // 検索タームを付随させる return ["media" : "music", "entity" : "album", "term" : term] } } // スタブデータの設定 // 便宜のためベタに書いているが // Moyaを使ったアプリeidolonではMainBundleからロードさせている public var sampleData: NSData { switch self { case .Search: return "Stub data".dataUsingEncoding(NSUTF8StringEncoding)! } } }
簡単に使う場合、Moyaの設定はこれでOK!!
リクエスト方法
Moyaを使ってリクエストする。
// providerを生成 let provider = MoyaProvider<iTunes>() // 検索ターム"swift"を指定して、Search APIをリクエストする provider.request(.Search("swift")) { result in switch result { case .Success(let response): let json = try! response.mapJSON() print(json) case .Failure(let error): print("request error: ", error) } }
リクエストのキャンセル
リクエストのキャンセルにはリクエストした時に、Cancellableオブジェクトが戻り値として 受け取れるので、このオブジェクトを保持しておき、キャンセルする。
以下のような感じ。
var cancellable: Cancellable? let provider = MoyaProvider<iTunes>() cancellable = provider.request(...) // Cancellableオブジェクトを取得 // 後でキャンセルする場合 cancellable?.cancel()
感想
Moyaを使ったアプリ Artsy/eidolon のコードがGitHub上に公開されているので、高度な使用例が見れて良いと思う。
参考URL
- Moya/Moya
- Moya ドキュメント
- Moyaを使ったアプリのコードArtsy/eidolon
CommonCryptoのSwift製ラッパー IDZSwiftCommonCrypto を使ってみる
IDZSwiftCommonCrypto を使ってみる
IDZSwiftCommonCrypto は Appleの CommonCrypto のSwiftラッパーで、Realmで紹介されていたので、ちょっと試してみた。CryptoSwiftでAES暗号 (AES-256-CBC) - タコさんブログと同じように Swift側でIDZSwiftCommonCryptoを使って暗号化(AES-256-CBC)し、Ruby側で復号化する。
環境
IDZSwiftCommonCryptoで暗号化(AES-256-CBC)
do { // 暗号化する平文 let plainText = "Hello IDZSwiftCommonCrypto" // 用意した鍵(適当な256ビット長)を16進文字列に変換 let keyStr = "BDC171111B7285F67F035497EE9A081D".utf8ToHexString() // 16進文字列を[UInt8]に変換 let keyHex = arrayFromHexString(keyStr) // 16 byteの初期化ベクトル let iv = try Random.generateBytes(16) // Cryptorを生成 let cryptor = Cryptor(operation:.Encrypt, algorithm:.AES, options:.PKCS7Padding, key:keyHex, iv:iv) // 暗号化 if let cipheredText = cryptor.update(plainText)?.final() { // 使用したIV:Ts4lC0DdSZvufWOPb3uTbg== print(base64EncodedStringFromArray(iv)) // 暗号化された文字列:gWTKuU8SK6g9OrNR2SLdtSxM/OCYXa2ZHVFkRvn3Qgg= print(base64EncodedStringFromArray(cipheredText)) } } catch RNGStatus.ParamError { print("IV Error") } catch { print("error") }
ちょっとしたエクステンション・関数
// UTF-8文字列をHex文字列に変換するエクステンション extension String { func utf8ToHexString() -> String { return self.utf8.map { NSString(format: "%2X", $0) as String }.reduce("", combine: {$0 + $1}) } } // UInt8配列をBase64文字列に変換する関数 func base64EncodedStringFromArray(array: [UInt8]) -> String { let nsdata = dataFromByteArray(array) return nsdata.toBase64EncodedString() } // NSDataをBase64文字列に変換するエクステンション extension NSData { func toBase64EncodedString() -> String { return self.base64EncodedStringWithOptions(.Encoding64CharacterLineLength) } }
初期化ベクトルは16バイトで良いのか調べたら、Stack Overflow (Need solution for wrong IV length in AES)にあった。
Ruby側で復号化
Ruby側の復号化についてはCryptoSwiftでAES暗号 (AES-256-CBC) - タコさんブログとほぼ同じ。
require 'base64' require 'openssl' key = 'BDC171111B7285F67F035497EE9A081D' encrypted = Base64::decode64 'gWTKuU8SK6g9OrNR2SLdtSxM/OCYXa2ZHVFkRvn3Qgg=' iv = Base64::decode64 'Ts4lC0DdSZvufWOPb3uTbg==' # Cipherインスタンスの生成 cipher = OpenSSL::Cipher.new('aes-256-cbc') cipher.padding = 1 # パディングを有効にする cipher.key = key # 鍵の設定 cipher.iv = iv # IVの設定 # 復号化 decrypted = "" decrypted << cipher.update(encrypted) decrypted << cipher.final p decrypted # 出力:Hello IDZSwiftCommonCrypto
感想
IDZSwiftCommonCryptoは、Apple製のCommonCryptoを使用したラッパーなので、ある程度信はできるんじゃないでしょうか。ただあまりソースの更新はなさそう(?)
参考URL
Xcode 7 で 実機にインストールすると Security エラーで入らない時に確認すること
久しぶりに、実機にインストールしようとしたら、Security エラーが出てインストールできなかったので、その時の解決方法
環境
エラー内容
実行した時、以下のようなエラー内容が表示されました。
Could not launch "アプリ名" process launch failed: Security
解決方法
次の方法で解決できました:
- 設定 -> 一般 -> プロファイルとデバイス管理 -> デベロッパAPP にデベロッパ登録したメールアドレスが表示されているので、選択
- "デベロッパのメールアドレス"を信頼 を選択
- ポップアップでも 信頼 を選択
これでアプリはすでにインストールされている。
プロファイルとデバイス管理はiOS 9.2からでてきたのかな?