- 投稿日:2020-11-24T18:50:50+09:00
Swift Decodable(Codable) 色んなJSONデータパターンのまとめ 【備忘録】
自分がAPIから取得したJSONのレスポンスパラメータで、色々な構造パターンがあったので備忘録も兼ねてまとめてみたいと思います。
そのままプロパティに適用できるパターン
一番手間がない形式でシンプルにデコード用クラスを定義できる
var data = """ { "id": 1, "name": "Taro", } """.data(using: .utf8)! struct User: Decodable { let id: Int let name: String } let user: User = try JSONDecoder().decode(User.self, from: data) print(user)キーとプロパティが一致しないパターン
Swiftの変数名の命名規則はローワーキャメルケースなので、CodingKeyを使って揃えることができる
var data = """ { "ID": 1, "Name": "Taro", } """.data(using: .utf8)! struct User: Decodable { let id: Int let name: String enum CodingKeys: String, CodingKey { case id = "ID" case name = "Name" } } let user: User = try JSONDecoder().decode(User.self, from: data) print(user)データ構造に配列があるパターン
デコードメソッドの引数に配列で渡す
var data = """ [ { "id": 0, "name": "Mike" }, { "id": 1, "name": "John" }, { "id": 2, "name": "Mary" } ] """.data(using: .utf8)! struct User: Decodable { let id: Int let name: String } let user: [User] = try JSONDecoder().decode([User].self, from: data) print(user)ネストが深いパターン(自動デコード)
別のデコード用のstructを作る(あんまり自動で済むパターンないですが)
var data = """ { "id": 0, "name": { "last": "Suzuki", "first": "Taro" } } """.data(using: .utf8)! struct User: Decodable { let id: Int let name:Name struct Name: Decodable { let last: String let first: String } } let user: User = try JSONDecoder().decode(User.self, from: data) print(user)ネストが深いパターン(手動デコード)
おそらくこっちのがよくあるパターン 別のstructを作らずに済む
取得しない値(キー用意していない変数)は 辞書型ならnestedContainer
、 配列ならnestedUnkeyedContainer
で取得できる階層ごとに別のCodingKeyを用意してあげる
var data = """ { "id": 0, "name": { "last": "Suzuki", "first": "Taro" } } """.data(using: .utf8)! struct User: Decodable { let id: Int let lastName: String let firstName: String enum CodingKeys: String, CodingKey { case id case name } enum NameKeys: String, CodingKey { case lastName = "last" case firstName = "first" } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decode(Int.self, forKey: .id) let nameValues = try values.nestedContainer(keyedBy: NameKeys.self, forKey: .name) lastName = try nameValues.decode(String.self, forKey: .lastName) firstName = try nameValues.decode(String.self, forKey: .firstName) } } let user: User = try JSONDecoder().decode(User.self, from: data) print(user)キーが存在しない時があるパターン (自動デコード)
プロパティをオプショナル型にする
var data = """ { "id": 0 } """.data(using: .utf8)! struct User: Decodable { let id: Int let name: String? } let user: User = try JSONDecoder().decode(User.self, from: data) print(user)キーが存在しない時があるパターン (手動デコード)
decodeIfPresent
やif let (変数) = try? decode~~
などを使って、もしキーが存在すればのデコードをするvar data = """ { "id": 0 } """.data(using: .utf8)! struct User: Decodable { let id: Int let name: String enum CodingKeys: String, CodingKey { case id case name } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decode(Int.self, forKey: .id) name = try values.decodeIfPresent(String.self, forKey: .name) ?? "" } } let user: User = try JSONDecoder().decode(User.self, from: data) print(user)取得しない値が配列になっているパターン (手動デコード)
あんまりQiitaで見かけないのでこのパターンも記載(JSONデータの例が雑ですみません )
var data = """ { "id": 0, "names": [ { "name": "太郎(日本名)" }, { "name": "テンプレマン(ニックネーム)" }, { "name": "Taro(アメリカネーム)" } ] } """.data(using: .utf8)! struct User: Decodable { let id: Int let name: [String] enum CodingKeys: String, CodingKey { case id case names } enum NameKeys: String, CodingKey { case name } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decode(Int.self, forKey: .id) var names = try values.nestedUnkeyedContainer(forKey: .names) var nameArray: [String] = [] while !names.isAtEnd { let nameDic = try names.nestedContainer(keyedBy: NameKeys.self) let nameValue = try nameDic.decode(String.self, forKey: .name) nameArray.append(nameValue) } name = nameArray } } let user: User = try JSONDecoder().decode(User.self, from: data) print(user)どっちかのキーで返ってくるパターン
EnumにDecodableを準拠させたプロパティを用意して、それを使って分岐で両方デコードする
var data1 = """ { "id": 0, "name": "Taro" } """.data(using: .utf8)! var data2 = """ { "id": 1, "name": { "last": "Suzuki", "first": "Ichiro" } } """.data(using: .utf8)! struct User: Decodable { let id: Int let name: NameType enum CodingKeys: String, CodingKey { case id case name } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decode(Int.self, forKey: .id) name = try values.decode(NameType.self, forKey: .name) } enum NameType: Decodable { case firstNameOnly(String) case fullName(Name) init(from decoder: Decoder) throws { let nameType = try decoder.singleValueContainer() if let nameValue = try? nameType.decode(Name.self) { self = .fullName(nameValue) } else { let nameValue = try nameType.decode(String.self) self = .firstNameOnly(nameValue) } } } } struct Name: Decodable { let last: String let first: String private enum NameKeys: String, CodingKey { case last case first } init(from decoder: Decoder) throws { let nameValues = try decoder.container(keyedBy: NameKeys.self) last = try nameValues.decode(String.self, forKey: .last) first = try nameValues.decode(String.self, forKey: .first) } } let user1: User = try JSONDecoder().decode(User.self, from: data1) print(user1) let user2: User = try JSONDecoder().decode(User.self, from: data2) print(user2)感想
もし記載ミス等あればご指摘お願いします (JSONデータ例が雑なのは悪しからず。。)
パースって大変ですよね(白目)追記
仕事時に参考にしていた記事
【Swift】Codableについて備忘録
レスポンスのJSONが異なるAPIにオプショナルを使わず対応する方法(Swift)
Codableについて色々まとめた[Swift4.x]
- 投稿日:2020-11-24T18:36:12+09:00
【Swift】 TableViewCell に画像を入れる【初心者】
はじめに
TableViewCellに画像を入れる方法について調べたので、メモとしてまとめる事にしました。
storyboardにTableViewとTableViewCellを準備
cellにIdentifierを付ける
Assets.xcassetsに画像ファイルを入れる
TableViewをdatasouceとdelegateに繋ぐ
UITableViewDelegate,UITableViewDatasourceを追加
ViewController.swiftclass ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {配列を準備
ViewController.swiftlet animalArray = ["虎", "猫","犬"] //画像用 let imageArray = ["lion", "cat", "dog"]セルの設定
ViewController.swift//セルの個数を指定する func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return animalArray.count } //セルに値を設定する func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //セルを取得 let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) //セルに表示する値を設定 //テキストラベル cell.textLabel?.text = animalArray[indexPath.row] return cell }①1つの画像を全てのcellに入れる
ViewController.swift//①画像(1つの画像を全てのcellに入れる) cell.imageView?.image = UIImage(named: "lion")②cellの1つずつに別の画像を入れる
ViewController.swift//②画像を複数入れる cell.imageView?.image = UIImage(named: imageArray[indexPath.row])③cellの1つずつに別の画像を入れる(同じサイズにリサイズ)
CGSizeのwidth:50,height:50を変更する事で画像サイズを変更できます。
ViewController.swift//③画像を同じサイズにリサイズ cell.imageView?.image = UIImage(named: imageArray[indexPath.row])?.resize(size: CGSize(width: 50, height: 50))リサイズメソッド
ViewController.swiftextension UIImage { func resize(size: CGSize) -> UIImage { let widthRatio = size.width / self.size.width let heightRatio = size.height / self.size.height let ratio = (widthRatio < heightRatio) ? widthRatio : heightRatio let resizedSize = CGSize(width: (self.size.width * ratio), height: (self.size.height * ratio)) // 画質を落とさないように以下を修正 UIGraphicsBeginImageContextWithOptions(resizedSize, false, 0.0) draw(in: CGRect(x: 0, y: 0, width: resizedSize.width, height: resizedSize.height)) let resizedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return resizedImage! } }全てのコード
①②のコードはコメントアウトしてあります。
ViewController.swiftimport UIKit class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource { let animalArray = ["虎", "猫","犬"] //画像用 let imageArray = ["lion", "cat", "dog"] override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } //セルの個数を指定する func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return animalArray.count } //セルに値を設定する func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //セルを取得 let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) //セルに表示する値を設定 //テキストラベル cell.textLabel?.text = animalArray[indexPath.row] //①画像(1つの画像を全てのcellに入れる) // cell.imageView?.image = UIImage(named: "lion") //②画像を複数入れる // cell.imageView?.image = UIImage(named: imageArray[indexPath.row]) //③画像を同じサイズにリサイズ cell.imageView?.image = UIImage(named: imageArray[indexPath.row])?.resize(size: CGSize(width: 50, height: 50)) return cell } } //リサイズメソッド extension UIImage { func resize(size: CGSize) -> UIImage { let widthRatio = size.width / self.size.width let heightRatio = size.height / self.size.height let ratio = (widthRatio < heightRatio) ? widthRatio : heightRatio let resizedSize = CGSize(width: (self.size.width * ratio), height: (self.size.height * ratio)) // 画質を落とさないように以下を修正 UIGraphicsBeginImageContextWithOptions(resizedSize, false, 0.0) draw(in: CGRect(x: 0, y: 0, width: resizedSize.width, height: resizedSize.height)) let resizedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return resizedImage! } }終わりに
初学者のため、もし間違いがあれば教えていただければ幸いです。
参考サイト
- 投稿日:2020-11-24T17:45:05+09:00
[iOS] [Swift] ローカル通知とアプリのライフサイクルについて
はじめに
これはiOS アプリでプッシュ通知についての記事です。
これから学ぶ知識
- 通知
- AppDelegate / SceneDelegate
- UNNotificationContent
- UNNotificationTrigger
- UNNotificationRequest
通知
概要
スマートフォンを使っていると 1 日に 1 度はみることのある通知は、ユーザーがアプリを開いていなくてもアプリに関する情報を伝えられるというメリットが強力です。
例えば、アラームアプリはユーザーがアプリを開いていなくてもその時刻が来たことを適切に伝えてくれます。
また、通知にはいくつかの種類がありますが、よく使われるのは以下のものです。
アラート(リスト) アラート(バナー) バッジ サウンド No Image
アプリケーションのライフサイクル
プログラム上では通知を受け取った際のアプリの状態によって処理が変わってきます。
状態というのはアプリが今起動しているかやアプリが画面上に表示されているかといったもののことです。そこで、通知と紐付けて、iOS アプリケーションにおけるライフサイクルを確認しましょう。
ライフサイクルとは生まれてから消えるまでの過程のことです。フォアグラウンド(Foreground)
フォアグラウンドとはアプリケーションが画面上に表示されている状態のことを指します。
Active
/In Active
の 2 種類に細かく分けられます。バックグラウンド(Background)
バックグランドとはアプリケーションが画面上に表示されていない状態のことを指します。
Backgroundの状態で一定時間起動が続くと、システムが自動的に
Suspended(停止状態)
に切り替えてくれます。
アプリケーションのライフサイクル
種類 説明 例 Active
アプリが現在操作中である 使用中 InActive
アプリの操作から一時的に離れている状態であるが、アプリは表示されている コントロールセンター・通知センターを表示している状態 Foreground
Active
とInActive
の状態の総称Background
別のアプリケーションが表示されている状態。アプリは起動していてバックグランドで特定のタスクが可能 Suspended
一定時間の Background
状態が続くと自動的にこの状態になるNot Running
アプリが完全に停止している状態 細かく分けられるものの、アプリケーションにはフォアグラウンドとバックグラウンドの状態があることが重要です。
Suspended
とNot Running
に関してはシステムが自動で管理する部分もありあまり開発者でも意識することはありません。AppDelegate について
AppDelegate とは
@UIApplicationMain
の属性をもつクラスで、アプリごとに 1 つのみ存在します。SceneDelegateについて
SceneDelegateとはAppDelegateとは異なり、アプリごとに複数存在することがあります。iOS13から導入された
Multiple Windows
によって一つのアプリを複数のウィンドで操作できることがあるため、アプリケーションのライフサイクルはAppDelegateよりもSceneDelegateの方が優先されるようになりました。(UIApplicationDelegate#applicationDidBecomeActive
はSceneDelegateが設定されている場合は呼ばれません。)例えば、アプリがActiveなのかそうでないのかを判断する場合は以下の2つのメソッドを実装する必要があります。
func sceneDidBecomeActive(_ scene: UIScene)
- シーンのライフサイクルがアクティブになった際に呼ばれます。
func sceneWillResignActive(_ scene: UIScene)
- シーンのライフサイクルがアクティブでなくなった際に呼ばれます。
許可について
通知はアプリをインストールしてくれた全てのユーザーに対して送ることができるわけではありません。
実際に通知を送るには下の画像の様に予めユーザーに許可をとる必要があります。
通知許可アラート 通知の許可を送るコード
以下のコードで
アラート
バッジ
サウンド
の3つに対して許可のリクエストをすることができます。// プッシュ通知の許可を依頼する際のコード UNUserNotificationCenter.current().requestAuthorization([.alert, .badge, .sound]) { (granted, error) in // [.alert, .badge, .sound]と指定されているので、「アラート、バッジ、サウンド」の3つに対しての許可をリクエストした if granted { // 「許可」が押された場合 } else { // 「許可しない」が押された場合 } }通知を送信する
UNMutableNotificationContent
通知を送信する際に
UNMutableNotificationContent
というクラスを作成します。下の例は、その通知によって付与されるバッジの数・通知のタイトル・通知を受信した際のサウンドを決定しています。
// MARK: 通知の中身を設定 let content: UNMutableNotificationContent = UNMutableNotificationContent() content.title = notificationTitle content.sound = UNNotificationSound.default content.badge = 1UNNotificationTrigger
次に
UNNotificationTrigger
というクラスを作成します。
UNNotificationTrigger
はトリガーという言葉が含まれていることから分かる様に、いつ通知を発行するのかを設定するためのものです。
実際には以下の4つのクラスを使用してトリガーを作成します。
UNCalendarNotificationTrigger
- カレンダーを元にするトリガー
DateComponents
を用いて特定の時間帯になると通知が発行される- 次の朝9時
- 毎朝9時
UNLocationNotificationTrigger
- 位置を元にするトリガー
- ある特定の地域に位置していると通知が発行される
UNTimeIntervalNotificationTrigger
- 時間差を元にするトリガー
- 60秒後
- 1週間後
UNPushNotificationTrigger
- リモート通知におけるトリガー
作成例
ある日付データ
notificationDate
に対して年/月/日/時間/分が同じ際に一回のみ通知される際のトリガーは以下のように書けます。// MARK: 通知をいつ発動するかを設定 // カレンダークラスを作成 let calendar: Calendar = Calendar.current let trigger: UNCalendarNotificationTrigger = UNCalendarNotificationTrigger(dateMatching: calendar.dateComponents([.year, .month, .day, .hour, .minute], from: notificationDate), repeats: false)
UNNotificationRequest
次に
UNNotificationRequest
というクラスを作成します。
UNNotificationRequest
は先ほどのUNNotificationContent
とUNNotificationTrigger
の二つを用いて作成することができます。作成例
// MARK: 通知のリクエストを作成 let request: UNNotificationRequest = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
identifier
にはUUID().uuidString
という一意の(重複して生成されることのない)文字列を使用します。通知ごとにユニークで他と被ることがない必要があります。通知を登録(送信)する
最後に作成した通知リクエストを通知センターに登録して、通知を送信します。
コールバック引数の中で、実際にエラーが返ってきたかどうかをerror: Error?
引数によって判断することができます。// MARK: 通知のリクエストを実際に登録する UNUserNotificationCenter.current().add(request) { (error: Error?) in // エラーが存在しているかをif文で確認している if error != nil { // MARK: エラーが存在しているので、エラー内容をprintする } else { // MARK: エラーがないので、うまく通知を追加できた } }通知を受け取った際の処理を実装する
設定した時刻になり、ユーザーが通知を受信したときの処理をする必要があります。
UNUserNotificationCenterDelegate
に準拠した上で、以下の 2 つのメソッドを実装する必要があります。class ClassA: UNUserNotificationCenterDelegate { // フォアグラウンドの状態でプッシュ通知を受信した際に呼ばれるメソッド func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.banner, .list]) } // バックグランドの状態でプッシュ通知を受信した際に呼ばれるメソッド func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { completionHandler() } }通知を受け取った際に、アプリが画面に表示されている(フォアグラウンド)か、そうではない(バックグランド)かで、別のメソッドを実装する必要があることが分かると思います。
頭にある
ClassA
は例で、実際には以下のようにAppDelegate
がUNUserNotificationCenterDelegate
を準拠して 2 つのメソッドを実装します。extension AppDelegate: UNUserNotificationCenterDelegate { // フォアグラウンドの状態でプッシュ通知を受信した際に呼ばれるメソッド func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.banner, .list]) } // バックグランドの状態でプッシュ通知を受信した際に呼ばれるメソッド func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { completionHandler() } }
具体的なサンプル
+ボタンから作成したプッシュ通知を一覧画面で確認できるアプリです
- 投稿日:2020-11-24T15:35:02+09:00
RxSwift Transforming Operators
Transforming Operatorsまとめ
RxSwiftでコードを書く必要があり、学習中です。
参考文献を元に学んだ内容をすぐ振り返れるようにまとめていきます。
今回はTransforming Operatorsについてまとめました。
完全に自分の解釈でコメントを載せていますが、解釈に誤りがありましたら、コメント欄より教えてください。toArray()
Observableから発火されたelementをArrayに詰め込んでSingleで返す。
import Foundation import RxSwift let disposeBag = DisposeBag() // 1 Observable.of("A", "B", "C") // 2 .toArray() .subscribe(onSuccess: { print($0) }) .disposed(by: disposeBag)map()
標準と一緒。各値に処理を加えて返す。
let disposeBag = DisposeBag() // 1 let formatter = NumberFormatter() formatter.numberStyle = .spellOut // 2 Observable<Int>.of(123, 4, 56) // 3 .map { formatter.string(for: $0) ?? "" } .subscribe(onNext: { print($0) }) .disposed(by: disposeBag)enimerated()
Observableから受け取った値にindexを振り管理できる。
let disposeBag = DisposeBag() // 1 Observable.of(1, 2, 3, 4, 5, 6) // 2 .enumerated() // 3 .map { index, integer in index > 2 ? integer * 2 : integer } // 4 .subscribe(onNext: { print($0) }) .disposed(by: disposeBag)compactMap()
map+nilを取り除くfilter
let disposeBag = DisposeBag() // 1 Observable.of("To", "be", nil, "or", "not", "to", "be", nil) // 2 .compactMap { $0 } // 3 .toArray() // 4 .map { $0.joined(separator: " ") } // 5 .subscribe(onSuccess: { print($0) }) .disposed(by: disposeBag)flatMap()
Observableが変わっても、継続して以前のObservableから値を受け取り続ける。
struct Student { let score: BehaviorSubject<Int> } let disposeBag = DisposeBag() // 1 let laura = Student(score: BehaviorSubject(value: 80)) let charlotte = Student(score: BehaviorSubject(value: 90)) // 2 let student = PublishSubject<Student>() // 3 student .flatMap { $0.score } // 4 .subscribe(onNext: { print($0) }) .disposed(by: disposeBag) student.onNext(laura) laura.score.onNext(85) student.onNext(charlotte) laura.score.onNext(95) charlotte.score.onNext(100)flatMapLatest()
Map + switchLatest: Observableが変わったら最新のObservableからのみ値を受け取り、前のObservableからは値を受け取らない。
let disposeBag = DisposeBag() let laura = Student(score: BehaviorSubject(value: 80)) let charlotte = Student(score: BehaviorSubject(value: 90)) let student = PublishSubject<Student>() student .flatMapLatest { $0.score } .subscribe(onNext: { print($0) }) .disposed(by: disposeBag) student.onNext(laura) laura.score.onNext(85) student.onNext(charlotte) // 1 laura.score.onNext(95) charlotte.score.onNext(100)materialize() && dematerialize()
materialize()しない場合、エラーが起こったとき、そこでdisposeされてしまうが、materialize()することでelementをeventに変換し、.dematerialize()でelementに戻すことで、エラーが起こったObservableはUnsubscribeされるが、別のObservableは継続して使用することができる。
// 1 enum MyError: Error { case anError } let disposeBag = DisposeBag() // 2 let laura = Student(score: BehaviorSubject(value: 80)) let charlotte = Student(score: BehaviorSubject(value: 100)) let student = BehaviorSubject(value: laura) // 1 let studentScore = student .flatMapLatest { $0.score.materialize() } // 2 studentScore // 1 .filter { guard $0.error == nil else { print($0.error!) return false } return true } // 2 .dematerialize() .subscribe(onNext: { print($0) }) .disposed(by: disposeBag) // 3 laura.score.onNext(85) laura.score.onError(MyError.anError) laura.score.onNext(90) // 4 student.onNext(charlotte)参考文献
RxSwift: Reactive Programming with Swift
Chapter7 Transforming Operators Written by Scott Gardner
詳細が気になる方はこちらをご参照ください!
https://www.raywenderlich.com/13285844-rxswift-reactive-programming-with-swift-update-now-available
- 投稿日:2020-11-24T15:03:40+09:00
RxSwift FilterOperators チートシート
FilterOperators
RxSwiftでコードを書く必要があり、学習中です。
参考文献を元に学んだ内容をすぐ振り返れるようにまとめていきます。
今回はFilter Operatersについてまとめました。
完全に自分の解釈でコメントを載せていますが、解釈に誤りがありましたら、コメント欄より教えてください。elementAt()
指定したインデックスの場合のみ、インベントは発火
import Foundation import RxSwift // 1 let strikes = PublishSubject<String>() let disposeBag = DisposeBag() // 2 strikes .elementAt(2) .subscribe(onNext: { _ in print("You're out!") }) .disposed(by: disposeBag) strikes.onNext("X") strikes.onNext("X") strikes.onNext("X")filter()
この場合、2で割り切れる値のみ表示する。条件に当てはまる場合のみ、イベントを発火。
let disposeBag = DisposeBag() // 1 Observable.of(1, 2, 3, 4, 5, 6) // 2 .filter { $0.isMultiple(of: 2) } // 3 .subscribe(onNext: { print($0) }) .disposed(by: disposeBag)Skip()
先頭か指定された数まで、イベント発火をスキップする。
let disposeBag = DisposeBag() // 1 Observable.of("A", "B", "C", "D", "E", "F") // 2 .skip(3) .subscribe(onNext: { print($0) }) .disposed(by: disposeBag)SkipWhile()
ある条件が成立するまで、イベントを通さない。一度通ったあとは、全部通す。
// 1 Observable.of(2, 2, 3, 4, 4) // 2 .skipWhile { $0.isMultiple(of: 2) } .subscribe(onNext: { print($0) }) .disposed(by: disposeBag)SkipUntil()
動的にイベントを発火させる。トリガーが起動された後のイベントを全て発火する。
let disposeBag = DisposeBag() // 1 let subject = PublishSubject<String>() let trigger = PublishSubject<String>() // 2 subject .skipUntil(trigger) .subscribe(onNext: { print($0) }) .disposed(by: disposeBag) subject.onNext("A") subject.onNext("B") trigger.onNext("X") subject.onNext("C") subject.onNext("D")take()
Skipオべレーターの逆、指定した数までイベントを発火、それ以降はイベントを止める。
let disposeBag = DisposeBag() // 1 Observable.of(1, 2, 3, 4, 5, 6) // 2 .take(3) .subscribe(onNext: { print($0) }) .disposed(by: disposeBag)takeWhile()
条件が満たされる間は、イベントを発火
let disposeBag = DisposeBag() // 1 Observable.of(2, 2, 4, 4, 6, 6) // 2 .enumerated() // 3 .takeWhile { index, integer in // 4 integer.isMultiple(of: 2) && index < 3 } // 5 .map(\.element) // 6 .subscribe(onNext: { print($0) }) .disposed(by: disposeBag)takeUntil()
条件が満たされるまでイベントを発火。最期のイベントを発火するかどうか選択できる。
// 1 Observable.of(1, 2, 3, 4, 5) // 2 .takeUntil(.exclusive) { $0.isMultiple(of: 4) } .subscribe(onNext: { print($0) }) .disposed(by: disposeBag)takeUntil() Trigger
トリガーが軌道するまで、イベントを発火。それ以降は発火しない。
let disposeBag = DisposeBag() // 1 let subject = PublishSubject<String>() let trigger = PublishSubject<String>() // 2 subject .takeUntil(trigger) .subscribe(onNext: { print($0) }) .disposed(by: disposeBag) // 3 subject.onNext("1") subject.onNext("2") trigger.onNext("X") subject.onNext("3")distinctUntilChanged()
エレメントが重複する場合、一度目のイベントのみを発火、それ以降はエレメントが変わるまでイベントを発火しない。
// 1 Observable.of("A", "A", "B", "B", "A") // 2 .distinctUntilChanged() .subscribe(onNext: { print($0) }) .disposed(by: disposeBag)distinctUntilChanged() 条件付き
後の数字の文字列に同じ要素が含まれないか確認、含まれる場合はスキップ、含まれない場合はイベントを発火!
// 1 Observable.of("A", "A", "B", "B", "A") // 2 .distinctUntilChanged() .subscribe(onNext: { print($0) }) .disposed(by: disposeBag) } let disposeBag = DisposeBag() // 1 let formatter = NumberFormatter() formatter.numberStyle = .spellOut // 2: フォーマッターで変換しなくていいように、はじめからNSNumberに!! Observable<NSNumber>.of(10, 110, 20, 200, 210, 310) // 3 .distinctUntilChanged { a, b in // 4 guard let aWords = formatter .string(from: a)? .components(separatedBy: " "), let bWords = formatter .string(from: b)? .components(separatedBy: " ") else { return false } var containsMatch = false // 5 for aWord in aWords where bWords.contains(aWord) { print(aWord) containsMatch = true break } return containsMatch } // 6 .subscribe(onNext: { print($0) }) .disposed(by: disposeBag)Filter応用
import Foundation import RxSwift let disposeBag = DisposeBag() let contacts = [ "603-555-1212": "Florent", "212-555-1212": "Shai", "408-555-1212": "Marin", "617-555-1212": "Scott" ] func phoneNumber(from inputs: [Int]) -> String { var phone = inputs.map(String.init).joined() phone.insert("-", at: phone.index( phone.startIndex, offsetBy: 3) ) phone.insert("-", at: phone.index( phone.startIndex, offsetBy: 7) ) return phone } let input = PublishSubject<Int>() input .skipWhile {return $0 == 0} .filter {return $0 < 10 } .take(10) .toArray() .subscribe(onSuccess: { let phone = phoneNumber(from: $0) if let contact = contacts[phone] { print("Dialing \(contact) (\(phone))...") } else { print("Contact not found") } }) .disposed(by: disposeBag) //0 is rejected input.onNext(0) //Only figure less than 10 can be passed input.onNext(603) input.onNext(6) input.onNext(0) input.onNext(3) "5551212".forEach { if let number = (Int("\($0)")) { input.onNext(number) } } input.onNext(9)参考文献
RxSwift: Reactive Programming with Swift
Filtering Operators Written by Scott Gardner
詳細が気になる方はこちらをご参照ください!
https://www.raywenderlich.com/13285844-rxswift-reactive-programming-with-swift-update-now-available
- 投稿日:2020-11-24T08:57:05+09:00
[Swift] APIレスポンス値はそのまま判定せずenumに置き換えた方が良い
前提環境:
・Xcode:12.1
・Swift:5.3私はコードレビュー等でしばしば以下のような実装を見かけるのですが、Swiftの良さを活かしていないなと感じます…
改善前// APIレスポンスの値と仮定 let memberType = "1" func foo() { switch memberType { case "0": print("通常会員の処理を行う") case "1": print("プレミアム会員の処理を行う") case "2": print("お試し会員の処理を行う") default: break } } foo() // "プレミアム会員の処理を行う"以下が改善したコードです。
改善後// APIレスポンスの値と仮定 let memberTypeRaw = "1" enum MemberType: String { case normal = "0" case premium = "1" case trial = "2" } func foo() { guard let memberType = MemberType(rawValue: memberTypeRaw) else { preconditionFailure("定義されていない値") } switch memberType { case .normal: print("通常会員の処理を行う") case .premium: print("プレミアム会員の処理を行う") case .trial: print("お試し会員の処理を行う") } } foo() // "プレミアム会員の処理を行う"改善後のコードには以下の利点があります。
- enumを定義することによって、APIレスポンスとして返却されうる値に対して型と名前が付与されることで、その仕様が読み取りやすくなる。
- guard節によって、MemberType型に変換した結果がnilである=仕様として想定外の値である、という実装意図が明確である。
- 将来
enum MemberType
にcaseが増えた際に、switch memberType
にてcaseを網羅していないとコンパイルエラーとして検出でき、改修漏れを防止できる。一行でまとめると、
Swiftの「Null安全」、「型安全」、そして「コンパイラによるenumの全ケース網羅チェック」という特徴を活かしましょう、
というお話でした。