SwiftUIのサンプルで気づいた点をメモ

新しい技術については、必要に迫れられることがない限り、体系立てた知識の整理ができていません。あまり利用しない技術についてはすぐ忘れてしまうのです。(笑)
SwiftUIについても数年前から、調べては忘れてを繰り返しているのですが、再度、調べる際にメモがあると早く思い出せるようなので、今回もメモしておきます。

【PropertyWrapperの内部変数にアクセス】

PropertyWrapperを定義したプロパティに「_」をつけることで、PropertyWrapperの内部変数にアクセスする。
(ObjCのメンバ変数の表記に似ているので抵抗がありますが、間違いみたいではないのでメモしておきます。)

@Binding var isPresented: Bool

init(isPresented: Binding) {
    self._isPresented = isPresented
}

【$ と projectedValue】

「$」をつけると射影プロパティにアクセスできる。また、内部変数(_をつけた変数)から projectedValue というプロパティへアクセスするのと等価になる。

_isPresented.projectedValue

$isPresented

【SwiftUI Non-constant range 警告】

Non-constant range: argument must be an integer literal」という warning が出力される場合は、以下の様に対処できる。

ForEach(0 ..< myArray.count) { index in
    ...
}
	↓
ForEach(0 ..< myArray.count, id: \.self) { index in
    ...
}

参考:SwiftUI ForEachのNon-constant range警告

【KeyPath の利用】

List など作成する場合に表示データを以下の様な DataModel で表現した場合、初期化パラメータに KeyPath を利用した記述ができる。

struct DataModel: Identifiable {
    let id = UUID()
    let name: String
    var subItems: [DataModel]?
}

  init(
    data: Data,
    children: KeyPath<Data.Element, Data?>
	:
	省略
	:

// パラメータ指定時には以下の様に KeyPath を用いて記述できる
children: \.subItems

マキタ 充電空気入れ 商品選定で混乱した件

以前より「電動空気入れ」というものの存在は知っていましたが、まだまだ一般的ではないのだろうと勝手に解釈していました。
ですが、信頼されているメーカ マキタにも製品があり、価格もこなれているようだったので、購入を検討してみました。
その際、初心者には製品構成が理解できず、商品選定にとても時間を要したので、商品選びのポイントをメモしておきます。

私が購入を検討した2022年時点で、マキタの充電式空気入れは、新機種2モデルと従来機種2モデルの合計4機種があるようでした。
そこで型番を基にネットで平均価格を調べてみたのですが、値段にばらつきがあること、製品型番の意味が分からず、比較検討に時間がかかってしまいました。
例えば、今回購入したのは、「MP100DSH」という充電空気入れなのですが、同じ製品で「MP100DZ」という型番も存在しています。
なぜ同じ製品で商品型番が異なるのか?
これが混乱した点なのですが、要は本体のみの商品と本体と付属品のセット商品かの違いです。

MP100DSH バッテリ、充電器、ケース
MP100DZ 本体のみ

もう一つ商品選定時にとても困った点として、充電空気入れがなぜ4機種も存在するのか?という点です。この点についてメーカの商品情報から、明確な理由が簡単には見つけられず、商品選定にとても時間がかかりました。

いろいろ調べてみると、マキタの商品はバッテリーが数種類あり、それぞれに対応するモデルがあり、複数の機種が存在しているようでした。
今回はバイクのトランポに積むことを考慮し、サイズが一番小さいモデルを選んだのですが、納得して商品を選ぼうとすると、とても苦労しました。
(その他のモデルは、大型バッテリーを利用しており性能は高いが、2.8Kgあり、サイズが大きい。)

商品自体は、値段、性能を考えると大変満足して愛用しています。
ですが、マーケティング的にはどうかと感じます。
こういったプロ向け商品の業界では、一般にはサポートが行われないということも多々感じるので、本来、一般消費者向けへの販売はあまり考慮されていないのかもしれません。

使用して一点気になる点としては、バルブ部分に英式バルブ用のアダプタが取り外せないチェーンで取り付けられている点です。
これではバイク用途には向いていないと思います。
最近のオートバイの前輪部分には、大型のブレーキディスク、サスペンション部品などがあり、タイヤのバルブ周りにはあまりスペースがないため、空気入れ本体のバルブをセットするのに少し苦労を要します。
そのため、英式バルブがチェーンでついていると作業に際しては邪魔になるので、チェーンを取り外して使用しています。

■ MP100DSH [充電式空気入れ 充電器・バッテリー付]
10.8Vリチウムイオンスライドバッテリー対応モデル
最高圧力は830kPa(121PSI、8.3bar)
吐出量は10L/分(200kPa時)
1.1Kg

Mac 起動用USBを作成する方法

iMac 27K (Late 2014) を売却するため、ストレージをディスクユーティリティで初期化しようとして、以前は選択することができたデータの完全削除を行う「セキュリティーオプション」がなくなっていることに気がつきました。

詳細は分かりかねますが、SSDの特性からセキュリティーオプションの提供が行われないように変更されたのかもしれません。SSDについては寿命があるようなので、セキュリティーオプションのような処理を行った場合、SSDの寿命を縮めることになり、そのため、セキュリティーオプションが用意されていないのかもしれません。

SSDの寿命はどれくらい?寿命を縮める原因と対策方法をご紹介

SSDストレージの初期化について調べたところ、以下のポイントが分かりました。

【SSD初期化の問題点】
1) APFSでは、完全削除ができない
2) ディスクユーティリティのセキュリティーオプションがなくなっている

上記の問題に対する対処としては、以下の方法があることが分かりました。

1) HFS+にすることで対処
2) 起動用USBで起動し、ターミナルからコマンドで対処

まず、起動USBを作成し、SSDのフォーマット、セキュリティーオプション相当のコマンド処理を行うことにしました。

対処に際して、いくつか問題があったため、メモしておきます。

【USBの準備】

USBフラッシュドライブは、以下の条件を満たす必要があるようです。

Mac OS 拡張でフォーマットされ、空き容量が 14 GB 以上あるものを用意してください。

私の場合、手持ちのUSBメモリの容量が小さく、上記の条件を満たさなかったので、新しいUSBメモリを買いました。
ノジマでUSBメモリ 16GBを約800円程度でした。

【起動用USB作成手順】

起動用USBを作成するには、まず、macOSをダウンロードし、インストーラーをUSBにコピーします。
今回は、High Sierraをダウンロードし、作業を進めました。

macOS をダウンロードする方法
macOS の起動可能なインストーラを作成する

【問題1: 空白のエスケープ指定】

最初、ダウンロードしたmacOSの createinstallmedia コマンドでUSBにファイルをコピーを試みましたが、参照した例が、空白のエスケープをダブルクォーテションで行っており、認識できていないようだったので、バックスラッシュでエスケープし、対処しました。

(High Sierra で、USBボリューム名が、MyVolumeの場合)

$ sudo /Applications/Install\ macOS\ High\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume --applicationpath /Applications/Install\ macOS\ High\ Sierra.app --nointeraction

【問題2: USBフォーマット問題】

パラメータが適切に認識され、コマンドを発行したところ、以下のエラーが出力されたので、USBをMacOSのファイルフォーマットでフォーマットすることで対処しました。

「このボリュームのフォーマットには大きすぎるため、コピーできません」

対処法:「このボリュームのフォーマットには大きすぎるため、コピーできません」

【問題3: Info.plistのバージョン指定問題】

パラメータ指定、USBのフォーマットに対処しましたが、次は以下のエラーが出力されました。
Info.plistの情報に不適切な指定があるようなので、訂正します。

「USBメモリ is not a valid volume mount point.」

macOS Sierraのインストール 用 USBメモリ作成方法の落とし穴

$ sudo plutil -replace CFBundleShortVersionString -string "12.6.03" /Applications/Install\ macOS\ Sierra.app/Contents/Info.plist

【問題4: disk error number (-69888, 0)問題】

上記の対処を行い、再度コマンドを入力したところ以下のエラーメッセージが出力されました。
このエラーは、Macを再起動し、再度コマンドを入力することで対処できました。

「Error erasing disk error number (-69888, 0)」

【SSDの初期化】

ようやく起動用USBが作成できたので、USB起動し、ターミナルからコマンドでSSDを初期化します。

起動用USBをスロットに差したまま電源を入れれば、USB起動できます。
マウント状態を確認し、必要であればHPS+フォーマットを行い、ストレージを初期化します。

$ diskutil list # マウントされているディスク一覧を表示

Macにマウントされているディスクの状態を確認し、APFSフォーマットされたディスクがあればHPS+にフォーマットします。
その後、diskutil secureErase コマンドでSSDを初期化すれば作業終了です。

1) Apple_APFS を削除、HPS+(Hierarchical File System Plus) にフォーマット

diskutil apfs deleteContainer /dev/disk0s2 # disk0s2 など指定は環境により異なることに注意
diskutil eraseDisk JHFS+ "Untitled" /dev/disk0

2) SSDを初期化

diskutil secureErase 2 /dev/disk0 # 2: 米国国防総省標準の7回の上書き

OSX El Capitan でのHDD完全消去方法

初期化処理は、消去方法によっては数時間を要します。
処理中にMacがスリープすることで処理が停止しているようであれば、以下のコマンドでスリープを抑止します。

$ sudo pmset -a disablesleep 1

【参考】
Mac を売却、譲渡、下取りに出す前にやっておくべきこと

SwiftUI、UIKit の相互利用について

現行のアプリにSwiftUIを用いる場合、UIKitとの相互利用について考慮する必要があるため、調べてみました。

【UIKit → SwiftUI】

UIHostingController クラス

【SwiftUI → UIKit】

UIViewRepresentable プロトコル
UIViewControllerRepresentable プロトコル

SwiftUIでUIKitのUIButtonを利用するサンプルは以下の通り

import SwiftUI

struct CounterView: View {
    @State var text: String = "Button_title"
    
    var body: some View {
        VStack(alignment: .leading) {
            CustomButton(text: $text)
        }
    }
}

struct CustomButton: UIViewRepresentable {
    @Binding var text: String

    func makeUIView(context: Context) -> UIButton {
        let button = UIButton(frame: CGRect(x: 0, y: 0, width: 200, height: 44))
        button.setTitle(self.text, for: .normal)
        button.setTitleColor(.black, for: .normal)
        button.addTarget(context.coordinator, action: #selector(Coordinator.didTapCustomButton(sender:)), for: .touchUpInside)
        return button
    }

    // SwiftUI → UIkit
    // ビューの状態が更新されるたび呼び出される
    func updateUIView(_ uiView: UIButton, context: Context) {
        print("\(#function)")
    }

    static func dismantleUIView(_ uiView: UIButton, coordinator: ()) {
        print("\(#function)")
    }
    
    // UIKit → SwiftUI
    func makeCoordinator() -> Coordinator {
        return Coordinator(button: self)
    }

    class Coordinator {
        var button: CustomButton

        init(button: CustomButton) {
            self.button = button
        }

        @objc func didTapCustomButton(sender: UIButton) {
            print("\(#function)")
        }
    }
}

【参考】

一部の画面だけSwiftUIを使いたいとき
UIViewRepresentable を理解して SwiftUI の足りないところを UIKit で補う

Gutenbergで画像のリンクを削除する方法

WordPressで投稿記事に画像を用いた場合、デフォルトではリンクが作成されますが、リンク情報の削除が分からず、調べたのでメモしておきます。

Gutenberg(バージョン 15.5.1)では、以下の場所に「リンクの削除」があるようです。
リンクの削除を押下し、投稿を更新すれば、リンク情報が削除できます。

iOS 開発で用いられるアーキテクチャまとめ

少し時間がとれたので、iOS開発におけるアーキテクチャについて整理してみることにしました。
最近では、iOS開発におけるアーキテクチャについて語られる際には、MVVMとCleanアーキテクチャについて触れられることが多いように感じています。

そのため、MVVMとCleanアーキテクチャについて比較しながら理解を深めようとするのですが、用語が異なるため毎回混乱しています。
理由の一因として、MVVMのオブジェクトは役割分担が比較的明確に理解することができるのに対し、Cleanアーキテクチャの資料の多くは、概念に終始しているため実装例は様々な解釈が存在し、単純に比較することができないという点があると感じています。

個人的には、Cleanアーキテクチャの用語について馴染みがあまりない部分があるので、改めて用語についてまとめてみることにしました。

MVC、MVP、MVVM アーキテクチャ

このアーキテクチャは、それぞれ3階層構造で似ています。
View ←→ Controller ←→ Model
View ←→ Presenter ←→ Model
View ←→ ViewModel ←→ Model

Clean アーキテクチャ

【メリット】

  • ビジネスロジックが明確になる
  • FW、UI、DataStoreに依存しない
  • テストが導入しやすい

【デメリット】

  • 各層にインターフェイスが用意されるため、コード量が多くなる

ドメイン駆動開発(DDD)を意識して、ビジネスロジックをUIやFWから分離し、それぞれの階層ごとに役割と責務を分けたアーキテクチャ。

大別するとPresentation、Domain、Data Layer の3つの階層
Presentation Layer ←→ Domain Layer ←→ Data Layer

View ←→ Presenter ←→ UseCase(Translater、Model) ←→ Repository ←→ DataStore(Entity)

Presentation Layer

UIの表示やイベント ハンドリングを行う
ビジネスロジックは含まない
例えば、UseCase から取得したデータを成功、失敗に応じて処理し、View に通知する

Domain Layer

ビジネス ロジック、ビジネスルールを表す責務を負う

UseCase

どのようにデータを取得、生成するか実装する
例えば、Repository から取得したデータを加工し、ストリームに流す

Translater

UseCase で取得した Entity から View の表示に最適な Model を生成する

Repository

UseCase で取得したいデータの CRUD I/F を用意する
例えば、DataStore からデータを取得し、ストリームに流すなど

Data Layer

通信、永続化データを扱う

Data Store

複数の DataStore を扱う場合は、Factory パターンを用いて Repository がデータ種別を意識しない設計にすること
例えば、UserDefaults からデータを取得するなど

Entity

DataStore で扱うことができる性的なモデル

個人のまとめ

  • VC、Modelが肥大化するのはプログラマが対処すべき
  • Translaterの実装例がイメージしづらいのでサンプルを探したい
  • 依存関係を一方向のみとし、相互依存を排除する
  • 外部ライブラリを用いてアーキテクチャを実装することは避けたい
    (DI、Repository を SwiftTask (Promiseライブラリ) でラップなど)

参考資料について

アーキテクチャの種類について効率よく知るには、「iOSアプリ設計パターン」が参考になりました。

クリーンアーキテクチャ完全に理解した
個人的には学術的な理解は不要で、まずはなんとなく使えるレベルでOKだと考えているので、少しボリュームがあると感じましたが、用語、概念についてざっと知りたい場合に良いドキュメントだと思います。

CleanArchitectureRxSwift
RxSwiftを用いたサンプルですが、Cleanアーキテクチャの概念を理解するには良いサンプルだと思います。

SwiftUI + Combine で MVVM & Clean Architecture な設計を考えてみた
Cleanアーキテクチャに、SwiftUI、Combineなどをどのように用いるのか?という点について、とても参考になりました。ボリュームも抑えられているので、サクッと理解できると思います。

pod install が失敗する場合の対処方法

久しぶりにプライベートなマシンで技術検証しようとpodコマンドを入力したところ、エラーが出力されたので原因と対処方法を調べたメモ

エラー内容

pod update コマンドを入力した際に、以下のエラーが出力された。

-bash: /usr/local/bin/pod: /System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/bin/ruby: bad interpreter: No such file or directory

原因

CocoaPods が参照している Ruby のバージョンが 2.3 なのに対し、MacOS では Ruby のバージョンがデフォルトで 2.6 だったため。

環境

macOS Big Sur バージョン 11.7.5

対処方法

cocoapods を再インストールすることで対処することができた。

$ sudo gem uninstall cocoapods
$ sudo gem install cocoapods

ruby の指定バージョンを利用する方法

rbenv を利用することで、複数のバージョンの ruby を切り替えることが可能。

参考資料

rbenvの使い方と仕組みについて
rbenv rehashをちゃんと理解する
rbenv rehashは何をやっているのか?

コマンド

# Homebrewをアップデート
$ brew update

# rbenvをインストール
$ brew install rbenv
# rbenvでインストール可能なrubyのバージョンを確認
$ rbenv --version
$ brew upgrade rbenv
$ rbenv install --list
# インストールされているrubyのバージョンを確認
$ rbenv versions
# インストールした ruby(”2.5.0”)に切り替える
$ rbenv global 2.5.0
# 必ず実行しないといけないコマンドではないが、問題がある場合は以下のコマンドを試す
$ rbenv rehash

# 指定したバージョンに切り替える
$ rbenv shell --unset コマンド

~/.bash_profile 設定ファイル

eval “$(rbenv init -)” を ~/.bash_profile の末尾に追加する

環境変数(PATH)

export PATH=”/Users/(ユーザ名)/.rbenv/shims:${PATH}”