- 投稿日:2021-04-03T19:41:56+09:00
【Swift】RxSwiftまとめ
- 投稿日:2021-04-03T19:36:19+09:00
【Swift】RxSwift勉強してみたPart4
はじめに
前回
今回は、今まで出てきた用語をもう少し深ぼって学習していきたいと思います。解説
Observable(観測可能)
イベントを検知するためのクラス
ストリームと言われたりする
Observableが通知するイベントは以下のようなものがある。
・onNext
デフォルトのイベントをながす
イベント内に値を格納でき、何度でも呼び出せる
・onError
エラーイベント
一度だけ呼ばれ、その時点で終了
購読を破棄
・onCompleted
完了イベント
一度だけ呼ばれ、その時点で終了
購読を破棄ObservableとObserver
Observable: イベント発生元
Observer: イベント処理
です。例えば、この以下のような感じです。hogeObservable // Observable .map { $0 * 10 } // Observable .subscribe(onNext: { // Observer }) .disposed(by: disposeBag)disposed
購読を良きタイミングで破棄してメモリリークを回避するための仕組み
SubjectとRelay
イベントの検知に加えてイベント発生もできる便利なクラス
良い使われるもの
流せるイベント バッファ PublishSubject onNext, onError, onComplete 持たない BehaviorSubject onNext, onError, onComplete 持つ PublishRelay onNext 持たない BehaviorRelay onNext 持つ バッファ
BehaviorSubject/Relayは、subscribe時に1つ過去のイベントを受け取ることができる。
最初にsubcribeするときは、宣言時に設定した初期値を受け取る。SubjectとRelay使い分け
・Subject
通信処理やDB処理等のエラーが発生した時にその内容によって処理を分岐させたい
・Relay
UIに値をBindするbind
Observable/Observerに対してbindメソッドを使うと指定したものにイベントストリームを接続できる
単方向のデータバインディング
subscribeして値をセットしているだけfinal class HogeViewController: UIViewController { @IBOutlet private weak var nameTextField: UITextField! @IBOutlet private weak var nameLabel: UILabel! private let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() //bind利用(subscribeして値をセット) nameTextField.rx.text .bind(to: nameLabel.rx.text) .disposed(by: disposeBag) //subscribe利用 nameTextField.rx.text .subscribe(onNext: { [weak self] text in self?.nameLabel.text = text }) .disposed(by: disposeBag) } }Operator
Observableから流れてきた値をそのままsubscribe(またはbind)するのではなく、途中で値を加工してsubscribe(bind)をしたいときに使う。
・変換
概略 Operator 説明 変換 map 通常の高階関数と同じ動き flatMap 通常の高階関数と同じ動き reduce 通常の高階関数と同じ動き scan reduceに似ていて、途中結果もイベント発行できる debounce 指定時間イベントが発生しなかったら、最後に流されたイベントをながす 絞り込み filter 通常の高階関数と同じ動き take 指定時間の間だけイベントを通知してonCompletedする skip 指定時間の間はイベントを無視する distinct 重複イベントを除外する 組み合わせ zip 複数のObservableを組み合わせる(異なる型でも可能) merge 複数のObservableを組み合わせる(異なる型では不可能) combineLatest 複数のObservableの最新値を組み合わせる(異なる型でも可能) sample 引数にわたしたObservableのイベントが発生されたら、元のObservableの最新イベントを通知 concat 複数のObservableのイベントを順番に組み合わせる(異なる型では不可能) map
//nameTextFieldのテキスト文字数を数えてnameLabelのテキストに反映 nameTextField.rx.text .map { text -> String? in guard let text = text else { return nil } return "あと\(text.count)文字" } .bind(to: nameLabel.rx.text) .disposed(by: disposeBag)//ボタンをタップしたときにnameLabelにユーザーの名前を表示する let user = User(name: "REON") showUserNameButton.rx.tap .map { [weak self] in return self?.user.name } .bind(to: nameLabel.rx.text) .disposed(by: disposeBag)filter
//整数が流れるObservableから偶数のイベントのみに絞り込んでevenObservableに流す numberSubject .filter{ $0 * 2 == 0 } .bind(to: evenSubject) .disposed(by: disposeBag)zip
//複数のAPIにリクエストして同時に反映したい場合に使える Observable.zip(firstApiObservable, secondApiObservable) .subscribe(onNext: { (firstApi, secondApi) in // ... }) .disposed(by: disposeBag)HotとColdなObservable
HotなObservable
・subscribeされなくても動作する
・複数の箇所でsubscribeした時に全てのObservableで同じイベントが同時に流れるColdなObservable
・subscribeした時に動作する
・単体では意味がない
・複数の箇所でsubscribeしたとき、それぞれのObservableでそれぞれのイベントが流れる
・使い所は非同期通信処理おわりに
RxSwift楽しい!
- 投稿日:2021-04-03T17:22:05+09:00
iOS/macCatalystアプリで、指定されたフォルダの中にファイルを書き出す
概要
- iOS 13以降では、
UIDocumentPickerViewControllerでdocumentTypesにkUTTypeFolderを指定すると、フォルダを選択させることが可能になっている。- 選択されたフォルダに対して
startAccessSecurityScopedResourceを呼び出すと、そのフォルダの下にファイルを書き込むことができる。Permissionの設定
App Sandboxで「User Selected File」を「Read/Write」に設定する。
UIDocumentPickerViewControllerで書き込み先フォルダを選ぶ
iOS 13以降では
UIDocumentPickerViewControllerでdocumentTypesにkUTTypeFolderを指定することで、フォルダの選択が可能となっている。import MobileCoreServices // kUTTypeFolderを参照するために必要 ... let documentPicker = UIDocumentPickerViewController(documentTypes: [kUTTypeFolder as String], in: .open) documentPicker.delegate = self present(documentPicker, animated: true, completion: nil)選択されたディレクトリの下にファイルを書き込む
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let directoryURL = urls.first else { return } debugPrint(directoryURL) guard directoryURL.startAccessingSecurityScopedResource() else { // Handle the failure here. return } defer { directoryURL.stopAccessingSecurityScopedResource() } do { try "Hello World".write(to: directoryURL.appendingPathComponent("hello.txt"), atomically: true, encoding: .utf8) try "Good Morning".write(to: directoryURL.appendingPathComponent("morning.txt"), atomically: true, encoding: .utf8) } catch let error { debugPrint(error) } }
startAccessingSecurityScopedResource()を呼び出さないと、ファイルの書き込みができないので注意。Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file “hello.txt” in the folder “com~apple~CloudDocs”."参考
- 投稿日:2021-04-03T13:52:33+09:00
iCloudにログインしているユーザーのユーザーIDを取得する
CloudKitを利用すると、iCloudでログインしているユーザーのIDを取得することができます。
以下のように、
CKContainerに対してfetchUserRecordIDを実行すると、ユーザーIDがコールバックで取得できます。let container = CKContainer(identifier: "iCloud.xxx.xxx.xxx") // 作成したコンテナのID container.fetchUserRecordID { [weak self] (id, error) in DispatchQueue.main.async { if let error = error as? CKError { switch error.errorCode { case CKError.notAuthenticated.rawValue: // 認証していない場合の処理 case CKError.managedAccountRestricted.rawValue: // 利用制限をかけている場合の処理 default: // その他のエラー処理 } } else { // IDが取得できた時の処理 } } }
- 投稿日:2021-04-03T13:44:42+09:00
【Swift】RxSwift勉強してみたPart3
はじめに
前回に引き続き、RxSwiftを勉強したので、その学習アウトプットです。
RxSwiftでHello World的なものを書いてみます。GitHub
以下のHelloRxSwiftフォルダに今回のプロジェクトはあります。
実装
流れは以下の通りです。
1.HelloWorldSubjectというSubjectを定義
2.Subjectを購読
3.値が流れてきたら
4.定義したクラスが破棄されたら購読も自動的に破棄させる
5.N回イベントをながす
6.定義したクロージャがN回実行される
viewDidLoadでこのように書いてみます。import UIKit import RxSwift class HelloRxViewController: UIViewController { private let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() let helloWorldSubject = PublishSubject<String>() helloWorldSubject.subscribe(onNext: { message in print("onNext: \(message)") }, onCompleted: { print("onCompleted") }, onDisposed: { print("onDisposed") }) .disposed(by: disposeBag) helloWorldSubject.onNext("HelloWorld!") helloWorldSubject.onCompleted() } }出力結果//onNext: HelloWorld! //onCompleted //onDisposedこのような書き方は
ViewControllerとViewModelや遷移元と遷移先のViewControllerのデータの受け渡しで使われます。
次はViewControllerとViewModelに分けて書いてみます。HelloRxViewControllerimport UIKit import RxSwift class HelloRxViewController: UIViewController { private let disposeBag = DisposeBag() private let viewModel = HelloRxViewModel() override func viewDidLoad() { super.viewDidLoad() viewModel.helloWorldObservable .subscribe(onNext: { message in print("onNext: \(message)") }, onCompleted: { print("onCompleted") }, onDisposed: { print("onDisposed") }) .disposed(by: disposeBag) viewModel.updateItem() } }HelloRxViewModelclass HelloRxViewModel { var helloWorldObservable: Observable<String> { return helloWorldSubject.asObservable() } private let helloWorldSubject = PublishSubject<String>() func updateItem() { helloWorldSubject.onNext("Hello World!") helloWorldSubject.onCompleted() } }出力結果//onNext: HelloWorld! //onCompleted //onDisposedおわりに
RxSwiftまだまだわからないことだらけ、、、
次回
- 投稿日:2021-04-03T11:24:37+09:00
[速報] [iOS] Adjust SDKをバージョンアップしないと審査リジェクトの危険性アリ
表題の通り、Adjust SDKをアップデートしないと、アプリ審査でリジェクトを食らう可能性があるようです。
ご存知の通り、Appleから告知されていた「App Tracking Transparency」が背景にあり、こちらのWebニュースなどが参考になると思います。
https://iphone-mania.jp/news-358057/Adjust SDKはVersion 4.28.0で対応されている模様です。
該当issueはこちらです。
https://github.com/adjust/ios_sdk/pull/5262021年4月1日の投稿より引用:
We received reports that some app updates using our SDK v4.27.x and lower have been rejected by Apple. To address this problem, we have released v4.28.0. We recommend that all clients upgrade to SDK v4.28.0 before submitting their next app update.
(Google翻訳)
SDKv4.27.x以下を使用した一部のアプリのアップデートがAppleによって拒否されたという報告を受けました。この問題に対処するために、v4.28.0をリリースしました。次のアプリの更新を送信する前に、すべてのクライアントをSDKv4.28.0にアップグレードすることをお勧めします。
- 投稿日:2021-04-03T02:10:45+09:00
【Swift】RxSwift勉強してみたPart2
はじめに
GitHub
今回のリポジトリは以下のGitHubのExampleRxSwiftです。
実装
以下のように、テキストフィールドの文字数に応じて残りの入力可能文字数がわかるというものをつくってみましょう。
RxSwiftを使わない場合は簡単ですね。
import UIKit final class ExampleViewController: UIViewController { @IBOutlet private weak var nameTextField: UITextField! @IBOutlet private weak var nameLabel: UILabel! @IBOutlet private weak var addressTextField: UITextField! @IBOutlet private weak var addressLabel: UILabel! private let maxNameTextFieldSize = 10 private let maxAddressTextFieldSize = 50 private let limitText: (Int) -> String = { return "あと\($0)文字" } override func viewDidLoad() { super.viewDidLoad() nameTextField.addTarget(self, action: #selector(nameTextFieldEditingChanged(sender:)), for: .editingChanged) addressTextField.addTarget(self, action: #selector(addressTextFieldEditingChanged(sender:)), for: .editingChanged) } @objc func nameTextFieldEditingChanged(sender: UITextField) { guard let changedText = sender.text else { return } let limitCount = maxNameTextFieldSize - changedText.count nameLabel.text = limitText(limitCount) } @objc func addressTextFieldEditingChanged(sender: UITextField) { guard let changedText = sender.text else { return } let limitCount = maxAddressTextFieldSize - changedText.count addressLabel.text = limitText(limitCount) } }RxSwiftを使うとこのようになります。
import UIKit import RxSwift import RxCocoa import RxOptional final class RxExampleViewController: UIViewController { @IBOutlet private weak var nameTextField: UITextField! @IBOutlet private weak var nameLabel: UILabel! @IBOutlet private weak var addressTextFiled: UITextField! @IBOutlet private weak var addressLabel: UILabel! private let maxNameTextFieldSize = 10 private let maxAddressTextFieldSize = 50 private let limitText: (Int) -> String = { return "あと\($0)文字" } private let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() nameTextField.rx.text .map { [weak self] text -> String? in guard let text = text else { return nil } guard let maxNameTextFieldSize = self?.maxNameTextFieldSize else { return nil } let limitCount = maxNameTextFieldSize - text.count return self?.limitText(limitCount) } .filterNil() .bind(to: nameLabel.rx.text) .disposed(by: disposeBag) addressTextFiled.rx.text .map { [weak self] text -> String? in guard let text = text else { return nil } guard let maxAddressTextFieldSize = self?.maxAddressTextFieldSize else { return nil } let limitCount = maxAddressTextFieldSize - text.count return self?.limitText(limitCount) } .filterNil() .bind(to: addressLabel.rx.text) .disposed(by: disposeBag) } }おわりに
まだまだ続きます!
次回
