SwiftUIの個人的なメモ

SwiftUIもそろそろ業務で利用されるかと思い、少しずつ情報を整理しています。
というわけで個人的なメモを以下に記します。

2022年秋には、iOS16が提供されることにより市場のアプリのサポートバージョンは、iOS14、15、16が一般的になると思います。結果、SwiftUIもそろそろ積極的に採用されるとの判断です。
実際調べてみると、SwiftUIはiOSバージョンにより挙動の差異があるようです。
この点は、以下のページで詳しく報告されています。

iOS バージョン による SwiftUI の機能差分・制限まとめ

SwiftUIを採用するということは、UIKitからの移行ということだと改めて認識しました。現行のままでは、移行しきれないUIデザインというのもあるのでしょうね。
UIKitのあのコントロールは、SwiftUIではどのコントロールに該当するのかというのは、以下のページが参考になりました。

[Swift] SwiftUIのチートシート

SwiftUIサンプルコードを見ていて、新しく見つけたProperty Wrapperは以下の通り。

@mainエントリポイント
@PublishedCombineフレームワークが提供。Publisherを生成する。
$を付加して、Publisherにアクセスできる。
@ObservedObjectSwiftUIの監視対象となり、参照しているViewが自動的に再描画される。
@State変数の変更が監視され、変更時にViewが再描画される。
アクセスは宣言されたView内のみとなり、変更時には$を付与する必要がある。
@StateObject再描画されても値は保持される。
@EnvironmentObjectアプリ内でデータを共有する。
@Environment環境値を取得する。
SwiftUI Property Wrapper

リアクティブプログラミング手法では、UIとモデルのバインディングがどのように行うのか?ということが一つのテーマであったと思うが、SwiftUIはProperty Wrapperという形で提供している。その点も踏まえて、今後はSwiftUIと相性の良いアーキテクチャについても考えたい。

リアクティブプログラミング、Combine の個人的なメモ

時間があるとリアクティブプログラミングの調査は行っているのですが、毎回まとめる前に忘れてしまうということを繰り返していたので、今回はまとめてみた。
業務でいつも最新の手法のみを使っているわけではないので、習得してもまとめておかないと忘れちゃうんですよね。(笑)

リアクティブプログラミングの定義

非同期イベントの処理を宣言的な記述で書くプログラミング。

もう少しイメージしやすい表現だと、データ流れをイベントのストリームとして表現し、ストリームに応じた振る舞いをさせる考え方。

Combine概要

Combine が登場したのは 2019 年 6 月の WWDC。
iOS 13.0以上で使用できる。
オブジェクト間でイベントを伝える仕組みを提供する。

主要な要素と役割は、以下の通り。

  • Publisher – 時間の経過に沿ってイベントを伝達する役割
  • Operator – イベントを作成、もしくは処理する役割
  • Subscriber – Publisher から送信されたイベントを処理する役割
  • Cancellable – イベントをキャンセルできることを表現する役割
  • Subscription – Publisher と Subscriber の購読を表現する役割

subjectに対して、複数のsubscribeを行うことができる。
複数のsubscriptionを扱う場合、storeメソッドでまとめて保持する。
Publisherは、sendメソッドでイベントをpublishする。

参考: Apple Developer Documentation | Combine

Publisher イベントを発行する

Publisher を subscribe することでイベントを受信できる。
PassthroughSubject クラス
CurrentValueSubject クラス

Justとは 値を1回だけ出力するPublisher。
Futureは、通信処理などで利用していたコールバック処理をCombineで実装する場合に使用する。

Subscriber イベントを購読する

イベントを受信する(購読する)subscribe処理
assignメソッド
sinkメソッド

var subscriptions = Set()

Operator 流れてくる値を加工する

map
filter
compactMap   
combineLatest など

APIリクエストを行い、結果集合をメンバ変数に格納するサンプル

import Foundation
import Combine

final class SearchUserViewModel: ObservableObject {
    @Published var text = ""
    @Published var users = [User]()
    private lazy var subject = PassthroughSubject<Void, Never>()
    private var cancellables = Set<AnyCancellable>()
    private let searchUserModel: SearchUserModelProtocol
    
    init(searchUserModel: SearchUserModelProtocol) {
        self.searchUserModel = searchUserModel
        
        subject.sink(receiveValue: { _ in
            searchUserModel.fetchUser(query: self.text)
                .receive(on: RunLoop.main)
                .sink(receiveCompletion: { completion in
                    switch completion {
                    case .finished:
                        break
                    case .failure(let error):
                        print(error)
                    }
                }, receiveValue: {
                    self.users = $0
                })
                .store(in: &self.cancellables)
            })
            .store(in: &self.cancellables)
    }
    
    func searchButtonTapped() {
        subject.send()
    }
}

参考:【Combine】APIとの通信処理にCombineを取り入れる

非同期処理で待ち合わせ制御を行っている場合、自由にキャンセル処理を行うにはNSOperationを選択した方がよい

APIリクエスト処理の不具合に対処していて、非同期処理について調べたのでメモしておきます。

iOSアプリをSwiftで実装している場合、いくつかの実装方法があり、お手軽に対処するためにGCD(Grand Central Dispatch)、DispatchGroup、DispatchSemaphoreを選択したのですが、以下のような【非同期処理の直列化】を行おうとした場合、処理途中で自由に処理をキャンセルできず(割り込めない)、結局、Foundation.Operation(NSOperation)で実装することとなりました。
キャンセル処理が出来ないことなんてあるのか?と思いますが、実際にキャンセル処理は処理完了時にしか出来ないようでした。

【非同期処理】
タスク1 --->
タスク2 --->
タスク3 --->
後処理  --->

【非同期処理の直列化】
タスク1 --->
タスク2     --->
タスク3         --->
後処理              --->

※ 後処理は全タスクの終了を待ち合わせてから処理する

参考にしたURLは以下の通り。

【参考URL】
Foundation.Operationの並列オペレーションがよくわからない人向けの説明
【Swift】非同期処理「Operation」について
swift で直列に非同期処理

Swift5 で文字列検索

久しぶりに Swift で文字列検索処理を書こうとして、 index(of:) を使おうとしたら、思うような結果が得られず、ネットを検索しても新旧の情報が入り交じっていて、よく分からなかったので、まとめた。

配列の最初に一致したインデックス番号を取得

index(of:) → firstIndex(of:)
メソッド名が変更されているようだが、結果を利用することができなかった。
文字列から文字を検索して、単純にインデックス番号が取得したかったのだが、そういうものではないらしい。 😂
Index とか Range とかの意味を整理する必要があって、数分で内容を理解して使用することができなかった。

文字列から文字を検索できるけど…

let str = "abcabc"
let firstIndex = str.firstIndex(of: "c")
print(type(of: firstIndex), firstIndex ?? 0)

出力結果 :
Optional Index(_rawBits: 131329)

Index型は、文字列の範囲指定に使用するものらしい…

文字列から文字を検索して、出現するインデックスを取得

以下の方法であれば、インデックス番号が取得できる。

let str = "abcabc"
let indexes = str.enumerated().compactMap { $0.element == "c" ? $0.offset : nil }
print(type(of: indexes), indexes)
// compactMap は配列のデータから nil を取り除きたい場合に使用する関数

出力結果 :
Array [2, 5]

NSRange で文字列中で指定した文字列が最初に出現する範囲を検索して取得

NSRange で取得するのが便利そう。

var str = "abcdef"
let range = NSString(string: str).range(of: "cde")
print("location:", range.location, "length:", range.length)

出力結果 :
location: 2 length: 3

文字の出現回数を正規表現で取得

こちらも便利です。

let string = "abcdefgabcdefg"
let pattern = NSRegularExpression.escapedPattern(for: "a")
let regex = try! NSRegularExpression(pattern: pattern, options: .caseInsensitive)
print(regex.numberOfMatches(in: string, range: NSRange(0..<string.utf16.count)))

出力結果 :
2

Swiftの範囲についてもよく知らなかったので、まとめた。

これだけ知っていれば良いと思う。

Range0..<30 ~ 2
ClosedRange0…30 ~ 3
let range1 = 0..<3
print("type: \(type(of: range1))")
print("lower:\(range1.lowerBound) upper:\(range1.upperBound) count:\(range1.count)")
print("range1.contains(0) \(range1.contains(0))")
print("range1.contains(1) \(range1.contains(1))")
print("range1.contains(2) \(range1.contains(2))")
print("range1.contains(3) \(range1.contains(3))")
print("range1.contains(4) \(range1.contains(4))")

出力結果 :
type: Range<Int>
lower:0 upper:3 count:3
range1.contains(0) true
range1.contains(1) true
range1.contains(2) true
range1.contains(3) false
range1.contains(4) false
let range2 = 0...3
print("type: \(type(of: range2))")
print("lower:\(range2.lowerBound) upper:\(range2.upperBound) count:\(range2.count)")
print("range2.contains(0) \(range2.contains(0))")
print("range2.contains(1) \(range2.contains(1))")
print("range2.contains(2) \(range2.contains(2))")
print("range2.contains(3) \(range2.contains(3))")
print("range2.contains(4) \(range2.contains(4))")

出力結果 :
type: ClosedRange<Int>
lower:0 upper:3 count:4
range2.contains(0) true
range2.contains(1) true
range2.contains(2) true
range2.contains(3) true
range2.contains(4) false

Swift のキーワードを今更ながら調べる

最近ずっと避けてきた Swift のキーワードを勉強しています.(^_^;
正直ジェネリクスを利用したヘッダーはノリでは分かる,もしくは,分かった気になれますが,完全に理解するのは私にはとても忍耐がいる作業です.

参考になったページをメモしておきます.

typealias
associatedtype
ジェネリクスの<>の記述が分からない場合に参考になると思います.

【Swift】associatedtypeの使いどころ
typealiasというSwiftの仕様を把握する
→ 言語仕様が変更されて一部,associatedtype にする必要がある.
【Swift】ジェネリクスが使われているメソッドを理解する

RxSwift 個人メモ

iOS アプリの Reactive プログラミングは,最終的には Apple 謹製のフレームワークになるかと思いますが,世の中には既に RxSwift を利用した iOS アプリが多数存在しながらも,学習コストが比較的高い分野のため,開発フェーズ後はメンテできる人がおらず,手つかずになっているアプリも沢山あると想像しています.

最初にRxSwiftに入門した際には,概念を把握するのにとても苦労しました.
理由は,体系的に説明された資料が見つけられず,ネットの断片的な資料を探すことから始めたため,概要の把握が思いのほか大変であったためです.

個人的なメモですが,最初に知っておくべき概念,用語,何度も見返したい参考ページをまとめます.

▼ Rx とは?
Rx(Reactive X)とは,「オブザーバパターン」「イテレータパターン」「関数型プログラミング」の概念を実装している拡張ライブラリ.
Rxの導入メリットは,「値の変化を検知できる」「非同期の処理を簡潔に書ける」こと.
値の変化には,変数値の変化やUIの変化も含まれる.

▼ Rxの三要素
– Observable
– Operator
– Scheduler

▼ Reactive Programming 概要
SwiftでReactive Programming

▼ RxSwift で参考になった資料
RxSwiftの機能カタログ
俺はこれでRxSwiftを学んだ!リファレンス集
[RxSwift] shareReplayをちゃんと書いてお行儀良くストリームを購読しよう
[Rx入門] Subject詳解 / Hotな、ColdなObservableのこと
RxSwiftのobserveOnとsubscribeOnを理解する // Speaker Deck
よく使いそうなRxSwiftのオペレーターの勉強をしてみた(flatMap, flatMapFirst, flatMapLatest 等)
RxSwift のスケジューラ
→ observeOn,subscribeOn の違いについて参考になった.
RxSwift4で廃止になった Variable のリプレイス

▼ サンプル コード
RxExample MVVM のその先へ(Fat ViewModel の倒し方)
RxSwiftとUIライブラリの表現を組み合わせたサンプル紹介
リバース・エンジニアリング – RxSwift

▼ 用語 について
Observable イベント発生元
Observer イベント処理

▼ Observable の破棄
– disposeBagで破棄する.
– subscribeメソッドの戻り値Disposableインスタンスに対してdispose()メソッドを呼び出す方法.

▼ Driver/Signal
Driver
subscribe()後,イベントを1つ流してほしい場合に使用する.
(UITextField の入力文字列,UIButton の有効/無効状態など)
Signal
subscribe()後,イベントが発生するまで,イベントを流さないで欲しい場合に使用する.
(UIButton のタップイベント,アニメーションの完了イベントなど)

▼ Hot/Cold
Subject から送出される値が「Hot」,それ以外が「Cold」.
Cold を Hot に変換するには,以下のメソッドを使用する.
Publish()メソッドは,Hot 変化オペレータ.Connect()メソッドは,ストリーム開始.

– share(replay: 1)は,Cold Observable を Hot Observable へ変換するためのオペレータ.

▼ Hot な Observable の特徴
– subscribe されなくても動作する
– 複数の箇所で subscribe したとき、全ての Observable で同じイベントが同時に流れる.

▼ Cold な Observable の特徴
– subscribe したときに動作する
– 単体では意味がない
– 複数の箇所で subscribe した時,それぞれの Observable でそれぞれのイベントが流れる.

▼ Subject/Relay
Subject「通信処理や DB 処理等」エラーが発生したときにその内容によって処理を分岐させたい
Relay UI に値を Bind する
– PublishSubject
– BehaviorSubject
– PublishRelay
– BehaviorRelay

Swift の associatedtype を最短で理解できたページ

associatedtype キーワードがよくわからなかったので調べたのだが,ジェネリックがどうとか難しいページが多かった.
キーワードの意味だけをわかった気になりたかったのだが…
以下のページを見ると2分くらいで理解できました.

associatedtype とは,protocol を定義する時には型が決まっていないものに使用する.
protocol 実装側で型を決められるようになる.

swiftの謎キーワードだったassociatedtype, mutating, subscriptについて

SwiftUI 個人メモ

SwiftUIの情報をまとめました.
正直キャッチアップはめんどくさいですが,切り捨てられない程度に時代について行きます.
ある程度時代の方向性が定まってから,最短ルートでついて行くつもりですが,SwiftUIに関する仕様の変更点は,まだまだあるのかもしれません.

– 概要の把握には以下の記事が参考になります.
SwiftUIの考察

– コードのサンプルには以下の記事が参考になります.
Apple Developer – SwiftUI Tutorials
Introducing SwiftUI
Fucking SwiftUI
カピ通信 SwiftUI

– SwiftUIサンプルコードを見ていると見慣れない some というキーワードが散見されますが,Swift 5.1 で導入される Opaque Result Type は以下の記事が参考になります.
Swift 5.1 に導入される Opaque Result Type とは何か

– 実際のアプリにSwiftUIを用いる際には,一部のビューから導入するというのが現実だと思われます.
一部の画面だけSwiftUIを使いたいとき

– Xcode11.4を触っていて見慣れない SceneDelegate.swift というのがあったので,調べます.
SwiftUIを触って分かったこと:①初期画面の設定方法

その他,見たことがなかった属性についてもまとめました.
@State
– SwiftUIのViewはstructのため,通常の値を更新することができない.@Stateを宣言することで,メモリの管理がSwiftUIフレームワークに委譲されて値を更新することができる.
– @Stateで宣言されたプロパティを子Viewに渡す時は、プロパティ名の頭に$をつける.プロパティの値そのものではなく、プロパティへの参照を渡すイメージ.
– @Stateは,値をコピーする.

@ObservedObject
– データクラスに対しては,@ObservedObjectを使用する.
– @ObsevedObjectは,View の外部からデータを取得する際に利用する.
– @ObsevedObject に@Published 属性を持つプロパティを用意する場合、そのプロパティに変化があればそのプロパティを利用する View が更新される.

@Binding
【SwiftUI】@Stateとか@Bindingて何

– @BindingをつけてやるとView間での双方向のデータ共有が可能となる.
– 子View側では、@Stateではなく@Bindingでプロパティを宣言する.
– 自らは保有せずに、親のプロパティを参照する.
– @Binding は実体を保持せず、参照してデータソースの値を変更する.
– 利用するデータソースを複数にせず、一つの正しいデータソースのみを参照し変更することで不具合を避けることができる.

@Environment
– @EnvironmentObjectを付与したプロパティは複数のViewで共通のインスタンスを参照する.(アプリ全体で共通のプロパティ)

@ViewBuilder
SwiftUIのViewBuilderについて調べてみる