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}”

中古のステップワゴンでiPhoneの音楽を再生する方法

都内では長らく車は不要かと考えていましたが、中古のステップワゴンを購入し、日々の買い物など便利に使用しています。

少しずつ快適に利用するための改善を楽しんでおり、今回は安価にナビゲーション、オーディオ、Youtube再生ができる商品を選んでみました。

車のナビを最新のものにするのでは無く、スマホで代替する際に必要になるのはスマホ スタンドだと思いますが、いざスマホ スタンドを購入するとなると製品の多さに驚かされます。
ですが、購入時には色々迷っても、購入後はあまり気にならないのではないでしょうか?さっさと購入して便利に利用した方が精神衛生上にも良いと思います。

デザイン、取り付け方式は無数にありとても迷いますが、まずは利用してみないと判断ポイントも分からないと思いますので、今回はデザイン的にもすっきりした、【2023年 超軽い】iPhone スマホホルダーを選びました。

スマホナビを利用して気づく点は、やはり充電しながらの利用が必須だということ。
車の配線を変更し、電源を取り出して…とも考えましたが、素人には手間暇がかかること、コストを考えて安価に解決するため、シガーソケットを拡張できて、USB端子が複数ある商品を選ぶこととしました。

シガーソケットを拡張する商品を選んだ理由は、既にドライブレコーダーの電源を利用していたためです。
こちらも商品点数がとても多いですが、USB端子がリバーシブル、2連セパレートの2連セパレートソケット 3リバーシブルUSB自動判定 7.2A カシムラ KX-212を選びました。

シガーソケットを拡張すれば、ドライブレコーダー用電源とFMトランスミッターを利用できるようになり、USB端子からはiPhoneの充電ができます。

FMトランスミッターも商品が沢山ありますが、選ぶポイントは、以下のようです。
ノイズがない
音量が適切

Amazonでは中国製品が多く販売されているようですが、日本製の製品で評価が高いものがあったので、ジャパンアベニュー FMトランスミッター Bluetooth 5.0 ベースサウンド機能を選びました。(3,500円程度)

FMトランスミッター自体は、ノイズは無く、音量も問題がありませんでしたが、
iOSアプリは動作に問題があるようでした。(22年12月時点)

購入に際してはメーカーHP( https://japan-ave.com )で商品ラインナップを把握したかったのですが、ラインナップが多く、またモデルごとの違いが分からず大変困りましたが、製品型番から後発モデルと判断できるものを選びました。

費用は、総額7,000円程度で快適にスマホナビ、オーディオが利用できるようになったので、良い投資であったと思います。

いざ購入に際してはあまりの商品点数の多さに辟易しましたが、購入の一助になれば幸いです。

今回購入した商品は以下のものです。
ジャパンアベニュー FMトランスミッター Bluetooth 5.0 ベースサウンド機能
【2023年 超軽い】iPhone スマホホルダー
2連セパレートソケット 3リバーシブルUSB自動判定 7.2A カシムラ KX-212

エキスプレスカードが反応せず、メインカードが反応する場合、確認すること

iPhoneのウォレットに楽天カード(JCB)、Suica(定期券)、dCARD(mastercard)、VIEW CARD、MERCARI(mastercard)を登録し、支払いのメインカードは、楽天カードを利用して、エキスプレスカードは、Suica(定期券)を利用しています。

Suica(定期券)は、定期券有効期間外のものにチャージを行い、Suica支払いを利用しています。
ですが、JRの改札を通過する際にエキスプレスカードに設定しているSuica(定期券)が支払いに利用されず、支払いのメインカードである楽天カードが反応してしまい、改札で支払いが出来ない事象が頻発して困っていました。

この事象は回避できないものと諦めていたのですが、最近、以下の設定で様子を見ています。
検証期間は約一ヶ月程度ですが、JR改札でSuica(定期券)が反応しないという問題は起きていないので、改善があったのかもしれません。
同様の事象でお困りの方は、一度試されてはいかがでしょうか?

【設定内容】
「定期券有効期間外のSF利用」設定で、「SFを利用する」に設定する
使用期間後の定期券情報が残るSuicaは設定が必要とのこと。

【設定手順】

  1. Suicaアプリケーションを起動し、ログインする
  2. Suica一覧画面で「チケット購入・Suica管理」タブを選択する
  3. 「その他の設定」を選択する
  4. 「定期券有効期間外のSF利用」を「SFを利用する」

JRの改札で自動で支払いをSuicaにするには、エキスプレスカード設定を行っておくと言うのは理解できるのですが、上記の「定期券有効期間外のSF利用」設定は、誰を救うための機能なのでしょう?

また、このような事象を回避する情報をお持ちの方は是非お知らせ下さい。
では!

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環境値を取得する。
@FocusStateiOS15で導入。表示要素のフォーカス制御を行う。
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を取り入れる