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 で直列に非同期処理

プロキシ環境下で cocoapods インストールエラーが発生する場合の対処方法

プロキシ環境下での環境構築時に経験した問題の対処方法をメモしておきます.

プロキシ環境下で cocoapods のインストール時に以下のエラーが発生した.

$ sudo gem install -n /usr/local/bin cocoapods

ERROR:  Could not find a valid gem 'rails' (>= 0), here is why:
Unable to download data from https://rubygems.org/ - no such name (https://rubygems.org/latest_specs.4.8.gz)

理由は,sudo コマンドに http_proxy 環境変数が適用されないため.
-E オプションを付加するとログインアカウントの環境変数を引き継いでくれる.

$ sudo -E gem install -n /usr/local/bin cocoapods

無事,cocoapods のインストールが行えました.

Apple WATCH 音楽再生中のコントロール画面を非表示にする方法

普段通勤途中に iPhone, Apple WATCH, AirPods Pro で音楽,YouTube を楽しんでいます.
その際,Apple WATCH がこのようなコントロールが表示されることがずっと不満でした.
WATCH 側で操作を行うことがないのに,音楽再生中はコントロール画面が表示され,通勤中はずーとお気に入り壁紙がキャンセルされることになります.

iOS14 で確認したところ,音楽再生中のコントロール画面を非表示にするには,以下の項目をオフにすれば良いようです.
WATCH アプリの [一般] – [画面のスリープ解除] – [オーディオAppを自動起動]


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

iOSシミュレータでVPNできない場合に確認する項目

最近,諸事情によりテレワークしています.
そのため自宅からお客様の環境へアクセスする際にVPNを使用しているのですが,iOSシミュレータがVPNを介して通信できないことがありました.
その際に確認した点をメモしておきます.

VPN接続ができたとしても,終日作業していると何らかの原因でVPN接続が切れることがありました.
通信ができない場合は,まずVPNの接続確認,再接続など確認した方がよいかもしれません.
また,VPNネットワーク 詳細… オプション 「すべてのトラフィックをVPN接続経由で送信」をチェックして通信状態が改善するか?確認すると解決できることがあるようです.(私はこれで改善できました.)

参考:Run app in iOS simulator with VPN connection

Charles のセットアップ メモ

久しぶりに iOS アプリ開発で Charles を使用したら,セットアップ方法に手間取ったので,メモしておきます.
iOS 10.3 から証明書を信頼する必要があるとのことですが,この手順をきれいさっぱり忘れていて,手間取ったので,メモしておきます.
実機で通信をモニタリングしようとしてうまくいかない方はこの点を確認されることをお勧めします.

▼ Charles でやったこと
・アプリの API リクエスト,レスポンスを確認した
・特定の API リクエストを失敗させて,適切なエラーハンドリングがされていることを確認した(Rewrite 機能)

▼ 設定手順
1. MacにCharlesをインストール
ダウンロードページ
https://www.charlesproxy.com/download/

2. CharlesのSLL Proxyを設定する
Proxy -> SSL Proxy Setting…
Host, Port (443)を設定する

3. MacのIPアドレスを確認する
iPhone に設定するため Charles の Help -> Local IP Address で MacのIPアドレスを確認する
(クラスAの方を適用する?)

4. 実機にSSL証明書をインストール
iPhone に SSL証明書をインストールするため,以下のURLにアクセスする
http://www.charlesproxy.com/getssl

5. 実機のHTTPプロキシを設定
設定 -> Wi-Fi (接続中 Wi-Fi の iマークをタップ) -> (画面最下段の)HTTP プロキシを構成
[手動]を選択し,IPアドレスとポート番号(8888)を入力する

6. 実機で証明書を信頼する
iOS 10.3から証明書の設定項目が追加されている
設定 -> 一般 -> 情報の最下部にある証明書信頼設定で,証明書を信頼する
Charles Proxy CA

ここまで設定すれば,MacのCharlesでiPhoneの通信がモニタリングできるはずです.
(iPhoneでVPNを使用している場合,VPNをオフ)

参考:
最速で作るCharlesのMac環境設定
【開発支援ツール】Charlesの使い方【神ツール】

Xcodeビルド環境を切り替える方法

先日,改修を担当したiOSアプリのXcodeプロジェクト設定が非常にわかりずらいものでした.(PROJECT – Configurations と TARGETS が複数存在し,それらを切り替える Scheme が定義されていなかった)

改めてアプリの参照サーバを切り替える方法など,どのようなプロジェクト設定にしておけば,わかりやすいのか?考えてみたのですが,その際とても参考になった資料をメモしておきます.

参考: [Xcode] ビルド環境を切り替えるためにSchemeを追加する

参考: [Xcode] Xcode 8.0から「Active Compilation Conditions」が追加されて#if, #elseif, #else, #endifによる分岐設定がちょっと楽になってます

プロジェクトの設定項目

PROJECT
Build Settings
Apple Clang - Preprocessing
Preprocessor Macros

設定例:MYDEBUG=1
Swift Compiler - Custom Flags
Active Compilation Conditions

設定例:MYDEBUG
TARGETS
Build Settings
Swift Compiler - Custom Flags
Other Swift Flags

設定例:-D MYDEBUG

ソースコードでは以下のように切り替えることが可能です.

1
2
3
4
5
6
7
8
9
        #if DEBUG
            print("デバッグ環境")
        #elseif STUB
            print("スタブ環境")
        #elseif MYDEBUG
            print("MYDebug環境")
        #else
            print("その他環境")
        #endif