酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

UIImagePickerControllerを使って写真.appで編集済みの動画を選択しても編集前のオリジナル動画が使われてしまう

動画のアップロード機能を実装していると、iOS標準の写真.appでクリッピングやトリミングしている編集済みの動画を選択した場合に、編集しているにもかかわらず編集前のオリジナル動画がアップロードされてしまう現象に気が付いた。

動画を選択するのに UIImagePickerController を使っている。未編集の動画を選択した場合とクリッピングした動画を選択した場合で違いがあるのかについて調査した。

結論から書くとUIImagePickerControllerは関係なく、PHAssetからAVAssetを取り出す際のPHVideoRequestOptionsの指定方法に誤りがあることがわかった。

編集済みの動画の場合はPHAsset#adjustedが1になっている

UIImagePickerControllerで動画や写真を選択するとUIImagePickerControllerDelegate.imagePickerController(_:didFinishPickingMediaWithInfo:)が呼ばれる。未編集動画と編集済み動画でどんな差異があるのか、info の中身をまず調べることにした。

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
  // ここでinfoの中身を調べた
  print("\(info)")
}

調査の結果、未編集の動画と編集済みの動画との違いはPHAsset#adjustedの値の違いだけであることがわかった。少なくとも info[.editedPhAsset] などからデータを取り出さないといけないわけではない。PHAssetから「編集前のオリジナル動画」ではなく「編集済みの動画」を取得するにはどうしたらよいのか?については後述する。

あまり重要な情報ではないが、それぞれの動画を選択した場合のログを載せてく。

編集済みの動画を選択した場合には、info[.phAsset]で取り出したPHAssetのadjusted1になっている。

[
 __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaType): 
public.movie, 
 __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerPHAsset): 
<PHAsset: 0x13024f720> A6BC4CB3-E92F-4BB3-A967-5EA773F1DCDF/L0/001 mediaType=2/0, sourceType=1, (1920x1080), creationDate=2022-01-14 13:59:02 +0000, location=1, hidden=0, favorite=0, adjusted=1 , 
 __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerReferenceURL): 
assets-library://asset/asset.MOV?id=A6BC4CB3-E92F-4BB3-A967-5EA773F1DCDF&ext=MOV, 
 __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaURL): 
file:///private/var/mobile/Containers/Data/PluginKitPlugin/F543103D-63B7-4566-B24D-758E8D85CA17/tmp/trim.E02629B0-C7EE-4FA1-998E-3EFE22F9D590.MOV
]

未編集の動画を選択した場合には、info[.phAsset]で取り出したPHAssetのadjusted0になっている。

[
__C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerPHAsset): 
<PHAsset: 0x135141e30> D82F6058-C843-45E6-B82F-3D0614A6A5A5/L0/001 mediaType=2/0, sourceType=1, (1920x1080), creationDate=2022-01-14 13:59:02 +0000, location=1, hidden=0, favorite=0, adjusted=0 , 
__C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaType): 
public.movie, 
__C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerReferenceURL): 
assets-library://asset/asset.MOV?id=D82F6058-C843-45E6-B82F-3D0614A6A5A5&ext=MOV, 
__C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaURL): 
file:///private/var/mobile/Containers/Data/PluginKitPlugin/F543103D-63B7-4566-B24D-758E8D85CA17/tmp/trim.20082119-CD92-4146-A76D-3AE767B38FCC.MOV
]

問題はUIImagePickerControllerで選択した動画情報(PHAsset)の取り出し方にあるわけではなくて、PHAssetからAVAssetを取得する際のオプションの指定の仕方が悪いことがわかった。

PHAssetからAVAssetの取得時にオプションでオリジナル動画かクリッピングした動画かを選択する

既存の実装では、PHAssetからAVAssetを取り出す際のPHVideoRequestOptionsの指定を PHVideoRequestOptionsVersion.original としていた。このため編集前のオリジナル動画がアップロードされていたことがわかった。

let options: PHVideoRequestOptions = PHVideoRequestOptions()
// 編集前のオリジナル動画がほしい場合
//options.version = PHVideoRequestOptionsVersion.original
// 編集後の動画がほしい場合
options.version = PHVideoRequestOptionsVersion.current

PHImageManager.default().requestAVAsset(forVideo: self, options: options, resultHandler: {(asset: AVAsset?, _: AVAudioMix?, _: [AnyHashable: Any]?) -> Void in
    DispatchQueue.main.async {
        if let urlAsset = asset as? AVURLAsset {
            let localVideoUrl: URL = urlAsset.url as URL
            completionHandler(localVideoUrl)
        } else {
            completionHandler(nil)
        }
    }
})

写真.appで編集したとしても同じ動画として保存した場合には、写真.appで見た場合にサムネイルが変わっていたとしても、オリジナル動画が上書きされるわけではなくて少なくともファイルパス等はそのまま残っている。編集後の動画が欲しい場合には PHVideoRequestOptions#versionの指定を PHVideoRequestOptionsVersion.current とする必要がある。

f:id:ch3cooh393:20220115101436p:plain

以上で問題は解決した。

UIImagePickerControllerを使って動画を録画しようとすると __TCCAccessRequest_block_invoke でクラッシュしてしまう

プロフィール画像などの静止画の撮影にUIImagePickerControllerを使うことはよくあり、UIImagePickerControllerを使って動画の録画ができることも知っていた。iOSアプリ開発に携わって11年ちょっと経つが、いままでUIImagePickerControllerを使って動画録画したことがなかった。

簡単に実装できると思っていたので下記のコードを書いた。

let vc = UIImagePickerController()
vc.delegate = self
vc.sourceType = .camera
vc.mediaTypes = [ "public.movie" ]
self?.present(vc, animated: true, completion: nil)

しかし、iOS 15.2の実機でこのコードを実行したところ、UIImagePickerControllerへの遷移時に謎のクラッシュが発生してしまった。スタックトレースが吐かれずに __TCCAccessRequest_block_invoke とだけ表示されている。

予期せぬTCCAccessRequest エラーに注意」によれば、以下のように記述があった。

カメラの使用、フォトライブラリの参照などを行う場合、info.plistに説明文の記述が必要。

  • カメラ : NSCameraUsageDescription
  • フォトライブラリ : NSPhotoLibraryUsageDescription

前述の通りプロフィール画像の撮影のために Info.plist にはすでに定義を追加済みである。他にもアプリ内で利用するためにPrivacy - Location When In Use Usage Description などの定義もすでに追加している。

f:id:ch3cooh393:20220111155554p:plain

まともなログが出ていないため原因がわからず、2時間ほど国内QAサイトやStack Overflowなどを検索してまわったがやはり原因がわからなかった。

ふと静止画撮影と動画録画の違いを考えたところ「音声の有無」に気付いた。結局これが当たりだったようで、クラッシュすることなく動画の録画画面に遷移できるようになった。

マイク使用の許可の定義 Privacy - Microphone Usage Description が足りていないのが原因だったようだ。

Swift Package Managerでマルチモジュール化しているアプリでCoreDataの.xcdatamodeldファイルが参照できない

注意:このプロジェクトではCoreDataの定義ファイルを TweetAlbum.xcdatamodeld としている。もし存在するかわからないが、本記事を読んで同じ現象でハマった方がいれば、TweetAlbumの部分は各々で読み替えていただきたい。

Appの外にあるCoreDataの.xcdatamodeldファイルを参照できない

Swift Package Manager (以下SwiftPMと記す)を使ったマルチモジュール構造でアプリを作っている。CoreDataの定義ファイル(.xcdatamodeld)をアプリに持たせるのではなくSwiftPMのCoreモジュールに持たせるとうまく参照できずに以下のエラーが発生していた。

[error] error:  Failed to load model named TweetAlbum
CoreData: error:  Failed to load model named TweetAlbum

TweetAlbum.xcdatamodeldをアプリ内に配置している段階では、以下のコードでNSPersistentContainerオブジェクトを取得できていた。

let container: NSPersistentContainer

container = NSPersistentContainer(name: "CoreData")

TweetAlbum.xcdatamodeldをSwiftPMのパッケージであるCoreモジュールに移動させた途端、うまく参照できなくなってしまった。ちなみにCoreモジュールは下図のように定義している。

解決編:Bundleから.xcdatamodeldファイルのパスを取得する

SwiftPMでマルチモジュール化している場合には、NSManagedObjectModelオブジェクトを指定して以下のようにNSPersistentContainer(name: String, managedObjectModel model: NSManagedObjectModel)を使って初期化する。

let container: NSPersistentContainer

let modelURL = Bundle.module.url(forResource: "TweetAlbum", withExtension: "momd")!
let model = NSManagedObjectModel(contentsOf: modelURL)!
container = NSPersistentCloudKitContainer(name: "TweetAlbum", managedObjectModel: model)

xcframeworks に対応した Rome を使って Carthage のキャッシュをS3にアップロードして Bitrise で超高速にビルドしよう! #bitrise #bitrisearticle

CarthageのキャッシュをS3で共有できる「Rome」がxcframeworksに対応して帰ってきた!!この記事はiOS Advent Calendar 2021の12日目の記事です。

Romeとは?

ライブラリを事前にビルドしておいてiOSアプリのビルド時間を短縮するプロダクトに「Carthage」がある。Carthageの成果物をローカル・またはS3のリモートでキャッシュしておいてBitriseなどのCIサービスでもビルド時間を短縮するプロダクトに「Rome」がある。どちらもポエニ戦争に由来する名称でCarthageもRomeもググラビリティが最悪である。

github.com

「Rome」のxcframework対応が長い間止まっていたので、さくさんはxcframework対応のために Carthage/Buildディレクトリをzipで固めてS3に置いておき、Bitriseでビルドするたびにzipファイルをダウンロードする方法で対応していた。

2021年11月26日に「Rome」がxcframeworkに対応して帰ってきた。本記事ではさくさんが個人開発しているアプリにRomeを再導入するまでの手順を書き残している。過去に Carthage および Rome を使ったことない方には不親切な内容となっているがご了承いただきたい。

はじめに

2021年11月にさくさんはM1チップ(Apple Silicon)搭載のMacBook Pro (16-inch, 2021)に移行した。このタイミングで前々より計画していたxcframework対応を一気に進めることになった。

Rosetta上でXcodeとiOSシミュレータを使えばxcframework対応をする必要はなかったが、全社的にRosettaを使わない方針でいくことになった。四苦八苦試行錯誤しながら会社で開発しているアプリのM1対応を進めた。Twitterでうめきながら愚痴りながらなんとか環境を整えることができた。副産物的に「Four Cropper」が macOS でも動くようになった。

xcframework対応にあたって可能な限りCocoaPodsの利用をやめてCarthageに移行させることになった。Carthageでxcframeworkでビルドしたい場合には以下のコマンドで実行すればよい。

carthage update --use-xcframeworks --platform ios --cache-builds

Carthageでビルドできなかったライブラリは SwiftPM か CocoaPods を使ってインストールすることで回避できた。Carthage での xcframework対応の話はこれで終わりだ。

Carthage/Build ディレクトリを zip でまとめて Bitrise でダウンロードしていたが……

さくさんは会社でも個人でもアプリのビルドには Bitrise を使っている。git にプッシュすると Bitrise でビルドして TestFlight にアップロード・配信する流れができている。xcframework 対応にあたり問題になったのが、Carthage の Carthage/Build ディレクトリをどうやって Bitrise で扱うかだ。

アプリに Realm が含まれているとBitriseの最大ビルド時間である1時間半を大幅に超えてしまい、ビルドが強制的にabortされてしまうため、Bitrise上ではcarthage bootstrapは使えない。さくさんは Carthage/Build ディレクトリをzipで固めてS3にアップロードしておき、Bitriseでビルドするたびにzipファイルをダウンロードする方法で対応していた。

Bitriseでは resource-archive ステップを使うと、zipファイルをダウンロードして解凍の流れを実行してくれる。bitrise.ymlでは以下のように書く。指定したBuild.zipをダウンロードして、FourCropper/Carthage/以下に解凍してくれる。

workflows:
  primary:
    steps:
〜〜省略〜〜
    - cocoapods-install@2: {}
    - resource-archive@2:
        inputs:
        - extract_to_path: FourCropper/Carthage/
        - archive_url: https://example.com/XXXXXXXXXXXX/Build.zip

Build.zip は単純に以下のスクリプトで生成した Carthage/Build ディレクトリを圧縮したファイルである。s3cmdなどのコマンドを使ってBuild.zipをS3へアップロードするだけで簡単だ。

carthage update --use-xcframeworks --platform ios --cache-builds
cd /Users/ch3cooh/works/FourCropper_ios/FourCropper/Carthage/
rm -f Build.zip
zip -r Build.zip Build/

ライブラリの少ないアプリの場合はこの方法で問題ないが、前述の Realm などクソデカライブラリ容量の大きなライブラリが含まれているとBuild.zipは1GB近くになってしまう。Bitriseの resource-archive ステップで1GB近いのzipファイルをダウンロードするには10分ほど掛かってしまう。早期になんとかしたいと考えていたが代替案がなくそのままになっていた。

Rome の v0.24.0.65 で xcframeworks に対応したので、この Carthage/Build ディレクトリ丸ごと圧縮作戦をやめてRomeに切り替えていく。

Romeの初期設定

Carthageの設定に関しては本エントリでは扱わない。Romeを使うための準備方法を紹介していく。

Romeのインストール

RomeはHomebrewでインストールすることもできるが、Bitriseでの利用を前提にして取り回しの効くCocoaPods経由でインストールさせる。Podfileに追記しておく。

pod 'Rome'

CarthageのキャッシュをS3へアップロードするための準備

S3にxcframeworkをアップロードしたいので、ローカルPCからAWSへ接続するための準備をおこなう。過去にAWS SDKを使ったことがあれば設定は必要ないと思う。

AWSのIAMとバケットを作成する

AWSのIAMとバケットを作成する。

  • AmazonS3FullAccess ポリシーを持つユーザー
  • Carthage/Build のビルド済み .xcframework を格納する S3バケット

アクションを縛りたいがうまく行かなかったのでバケットポリシーは全許可にしている。

{
    "Version": "2012-10-17",
    "Id": "PolicyXXXXXXXXXX",
    "Statement": [
        {
            "Sid": "StmtXXXXXXXXX",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::XXXXXXX_S3_BUCKET_NAME"
        }
    ]
}
AWSの認証情報を入力する

aws configurateを使っても良いが、Romeの説明でコマンドラインを使ってたのでそれに倣った。

mkdir -p ~/.aws/
touch ~/.aws/credentials
touch ~/.aws/config
~/.aws/credentials
[default]
aws_access_key_id=AKIXXXXXXXXXX
aws_secret_access_key=UvPzXXXXXXXXXXXXXXXXXXXX
~/.aws/config
[default]
region=ap-northeast-1

Romefileの書き方

基本的にRomeはCartfile.resolvedを参照してよしなにしてくれる。

しかしリポジトリ名と生成されるxcframeworkの名称が異なる場合はキャッシュ対象から除外されてしまう。たとえばSentryのリポジトリ名はsentry-cocoaだが生成されるxcframeworkの名前はSentry.xcframeworkとなっている。細かいところでは-_の違いでも対象から除外されてしまう。たとえばUITextView-Placeholder なのに生成されるのが UITextView_Placeholder.xcframework の場合などである。

Romefileの書き方には難解な部分がある。さくさんが個人開発しているFour Cropperで実際に使っているCartfileとRomefileの例を紹介する。

FourCropperのCartfileは下記の通りだ。FourCropper自体が小規模なアプリなので利用しているライブラリは少ない。

github "yhirano/LicensePlistViewController"
github "bizz84/SwiftyStoreKit"
github "TimOliver/TOCropViewController"
github "takecian/SwiftRater"
github "devxoul/UITextView-Placeholder"
github "getsentry/sentry-cocoa"

Romefile では前述したようにリポジトリ名と生成されるxcframeworkの名称を関連付けする必要がある。FourCropperのRomefileは下記の通りだ。

cache:
  s3Bucket: XXXXXXX_S3_BUCKET_NAME

repositoryMap:
- TOCropViewController:
    - name: CropViewController
    - name: TOCropViewController
- sentry-cocoa:
    - name: Sentry
- UITextView-Placeholder:
    - name: UITextView_Placeholder

AWS SDKの場合リポジトリひとつに対して生成される xcframework が20以上あるので設定がややこしくなる。iOSエンジニア各位はどのライブラリをインストールしたらなんの xcframework が生成されているのか理解していると思うので、おそらくこの関連付けの作業は難なくこなせるかと思う。

以上でRomeの設定はできた。

ローカルPCでのRomeの使い方

S3へCartageのキャッシュのアップロードするにはローカルPC上で下記のコマンドを実行する。毎回すべてのライブラリをアップロードするのはS3の転送量的にも所要時間的にも勿体無いので、差分のみをアップロードする方法を採るべきだろう。

export SWIFT_VERION=`xcrun swift --version | head -1 | sed 's/.*\((.*)\).*/\1/' | tr -d "()" | tr " " "-"`

./Pods/Rome/rome download --use-xcframeworks --cache-prefix $SWIFT_VERION
carthage update --use-xcframeworks --platform ios --cache-builds
./Pods/Rome/rome list --missing --use-xcframeworks --cache-prefix $SWIFT_VERION | awk '{print $1}' | xargs -I {} ./Pods/Rome/rome upload "{}" --use-xcframeworks --cache-prefix $SWIFT_VERION

ローカルでXcodeを使う際には事前に下記のコマンドを実行する。これだけでS3から必要なxcframeworkをダウンロードできる。

export SWIFT_VERION=`xcrun swift --version | head -1 | sed 's/.*\((.*)\).*/\1/' | tr -d "()" | tr " " "-"`

./Pods/Rome/rome download --use-xcframeworks --cache-prefix $SWIFT_VERION

BitriseでのRomeの使い方

BitriseでRomeを使う際には一点注意することがある。xcframeworksに対応したRome v0.24.0.65は S3からキャッシュデータをdownloadする際にかならず失敗してしまう問題がある。すでにIssueが起票されているので修正はされると思うので、それまでの回避策を含めてBitriseでのRomeの使い方を紹介する。

Install Carthage/Build ステップ が成功しても失敗しても、次のステップから普通に実行したい場合どうするのか?

GUI上から「発生したエラーを無視してスキップする設定」ができないので、bitrise.ymlでis_skippable: trueを付与すれば良い。

    - cocoapods-install@2: {}
    - script@1:
        title: Install Carthage/Build
        is_skippable: true 
        inputs:
        - content: |-
            #!/usr/bin/env bash
            cd FourCropper
            export SWIFT_VERION=`xcrun swift --version | head -1 | sed 's/.*\((.*)\).*/\1/' | tr -d "()" | tr " " "-"`
            ./Pods/Rome/rome download --use-xcframeworks --cache-prefix $SWIFT_VERION
    - cache-push@2:
        inputs:
        - cache_paths: |-
            $BITRISE_CACHE_DIR
            FourCropper/Carthage -> FourCropper/Cartfile.resolved
            FourCropper/Pods -> FourCropper/Podfile.lock
            FourCropper/vendor -> FourCropper/Gemfile.lock

bitrise.ymlの設定が終われば、あとはBitriseから該当のAWS S3にアクセスできるようにアクセスキーを登録しておこう。AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION を忘れないで。

以上でBitriseでRomeを使う設定は完了だ。

xcframeworks対応のRomeを使ってみた結果ビルド速度が超高速になった!

Carthage/Buildまるごとzipで固める作戦から Rome に移行した。ビルド時間がどれだけ早くなったのかを確認していこう。

Four Cropperでのビルド時間は約12分から約9分へ短縮した。1ビルドの所要時間を約25%軽減することができた。

Four Cropperでのビルド時間は約12分から約9分へ短縮した

別のアプリAでのビルド時間は約16分から約13分へ短縮した。アプリAではアプリ内DBとしてRealmを使っている。1ビルドの所要時間を約20%軽減することができた。

アプリAでのビルド時間は約16分から約13分へ短縮した

別のアプリBでのビルド時間は約50分から約37分へ短縮した。このアプリではresource-archiveステップで9分〜10分かかっていた。この時間がマルッとなくなったのが嬉しい。1ビルドの所要時間を約25%軽減することができた。

アプリBでのビルド時間は約50分から約37分へ短縮した

追記:突然Romeが使えなくなることがある……

Rome v0.24.0.65 はやはり不安定なのかもしれない。とあるライブラリのインストール先を「元リポジトリ」と「フォークしたリポジトリ」とのどちらにするか実験するために、ライブラリのアップデート+Romeへのアップロードを繰り返していたところ、突然以下のエラーが発生するようになってromeが使えなくなってしまった。

rome: device /dev/random cannot be grabbed
CallStack (from HasCallStack):
  error, called at ./Crypto/Random/Entropy/Unix.hs:60:20 in cryptonite-0.26-1BsgpeCLUsqwiNhmB6AoC:Crypto.Random.Entropy.Unix

最終的には romeを一旦削除して再インストール、もう一度試したら正常に動くようになった。今回はCocoaPods経由でインストールしているためPodsディレクトリを削除して pod install で事なきを得た。

さいごに

この記事をみて Bitrise を使ってみたくなった方がもしいれば「https://app.bitrise.io/referral/a927d5dbff07cc1d」からBitriseのアカウントを作成してくれると嬉しい。

現在Bitriseの無料カウントでは1ビルドあたり30分までビルド時間を使うことができる。この紹介リンクからアカウントを作るとビルド時間が5分伸びるのでとても便利になる。GitHubのアカウントを持っていれば数クリックでBitriseのアカウントも作成できる。この機会にBitriseを知った方には是非利用して欲しい。

備忘録:SwiftUIのListでrefreshした時に前回のデータが表示されている。画面が再描画されると新しいデータが反映される

備忘録として書き残しておく。気が向いたら書き直すかもしれない。


SwiftUIのListで下図のような画面を実装している。

縦スクロールと横スクロールの混ざったリストは珍しくないが、この画面の厄介なところは横スクロールでも別途APIを叩いてデータを取得する必要がある。1画面1APIではなくて、縦スクロール用に1APIと横スクロール分x1API叩く必要がある。実装にめちゃくちゃ苦労しているが、APIの話はここではあまり関係ない。

f:id:ch3cooh393:20211203091355p:plain

実装としてはこんな感じだ。実際のコードとは全然違うのでイメージとして受け取ってください。

    var body: some View {
        List {
             ForEach(vItems) { hItems in
                 HorizontalScrollView(items: hItems)
             }
         }
    }

この画面は上から下方向へスワイプさせることで画面をrefreshさせる。ここではrefreshとは「オブジェクトは一旦削除して再度追加してListに表示させること」を指すことにする。

refreshしても画面の表示が変わらない。前回のデータが引き続き表示されてしまっているようだ。HorizontalScrollViewに触るなどするとViewの更新が掛かってAPIから取得した新しいデータが反映される。

Listが前回のデータをキャッシュしている疑いが濃厚だが、何を基準にしてキャッシュしているのかわからない。HorizontalScrollViewに渡しているObservableObjectの配列は以下の通り。最初は id を基準にしているのかと考えた。

class ItemModel: ObservableObject, Identifiable, Equatable {

    // ... (省略)

    // MARK: - Identifiable

    var id: String {
        item_id
    }

    // MARK: - Equatable

    static func == (lhs: ItemModel, rhs: ItemModel) -> Bool {
        return lhs.id == rhs.id
    }
}

なんで前回のデータが表示されているのかがわからなかったが「キャッシュされている」「おそらくidで判定している」と当たりがついたので、SwiftUIのListのキャッシュについて調べていくと「ios - SwifUI ForEach List keeps modified values when reloading a @Published array inside ObservableObject - Stack Overflow」が出てきた。

アイテム数が同じだったらどうするのなど考えていないのであまり良い例ではないが Equatable の処理を下記のように変更したところ、refreshした際にきちんと新しいデータが描画されるようになった。

class ItemModel: ObservableObject, Identifiable, Equatable {

    // ... (省略)

    // MARK: - Identifiable

    var id: String {
        item_id
    }

    // MARK: - Equatable

    static func == (lhs: ItemModel, rhs: ItemModel) -> Bool {
        return lhs.id == rhs.id && lhs.items.count == rhs.items.count
    }
}

国居さんにも教えてもらった。

R.swiftがv6.0にアップデートされたのでプロジェクト設定の変更が必要になった

R.swiftはプロジェクトに追加しているリソースをハードコーディングせず管理できるライブラリだ。2021/11/19、R.swiftがv6.0にアップデートされた。

プロジェクトをビルドすると以下のワーニングが出るようになったので対応した。

warning: [R.swift] For updating to R.swift 6.0, read our migration guide: https://github.com/mac-cain13/R.swift/blob/master/Documentation/Migration.md
warning: [R.swift] In R.swift Run Script build phase, disable "Based on dependency analysis"

公式のマイグレーション手順に従って、Based on dependency analysis のチェックを外す。

f:id:ch3cooh393:20211119205407p:plain

Input Filesに登録している $TEMP_DIR/rswift-lastrun を削除する。Output Filesはそのままでよい。

f:id:ch3cooh393:20211119205523p:plain

以上で R.swift v6.0への移行作業は完了だ。

OneSignal-iOS-SDK v3.9.0 をインストールするとarm64シミュレータ向けビルドでリンクエラーが発生する

Carthageで「OneSignal-iOS-SDK v3.9.0」をインストールすると、arm64シミュレータ向けのビルド時にリンクエラーが発生してしまう。iOSシミュレータでデバッグ実行できなくなってしまった。

この問題は2021/11/17にv3.9.1がリリースされたことで解決した。リンクエラーは発生せずにビルドも通るようになった。

OneSignal-iOS-SDK v3.9.0をインストールするとarm64シミュレータ向けビルドでリンクエラーが発生する

2021/11/14にOneSignal iOS SDKがv3.9.0に更新された。その結果、リンクエラーが発生するようになってしまった。

ld: warning: ignoring file /Users/ch3cooh/Library/Developer/Xcode/DerivedData/MY_APPLICATION-eoyslbwfrtrzwjcmqeevlxktpqgn/Build/Products/Debug-iphonesimulator/OneSignal.framework/OneSignal, missing required architecture arm64 in file /Users/ch3cooh/Library/Developer/Xcode/DerivedData/MY_APPLICATION-eoyslbwfrtrzwjcmqeevlxktpqgn/Build/Products/Debug-iphonesimulator/OneSignal.framework/OneSignal (2 slices). Undefined symbols for architecture arm64:
"OBJC_CLASS$_OneSignal", referenced from:
objc-class-ref in NotificationService.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

今までのバージョンはうまく動いてたのにどうして?

v3.8.xからv3.9.0へマイナーバージョンが上がってため「大きな変更があったのかな?」と調査した。リンクエラーが通らなくなった理由は.xcframeworkの中身を見るとわかった。

v3.8.0

これは過去バージョン。arm64シミュレータ向けにサポートされていることがわかる。

f:id:ch3cooh393:20211115133051p:plain

v3.9.0

こちらが最新バージョン。arm64向けのサポートが消えてしまっているのがわかる。

f:id:ch3cooh393:20211115133049p:plain

対応編

Cartfileで以下のように書くことでバージョンを v3.8.0 に固定できる。

github "OneSignal/OneSignal-iOS-SDK" == 3.8.0

応急処置だがこれでしばらくは乗り切れるだろう。OneSignal iOS SDKがv3.9.0でarm64向けシミュレータのサポートを正式にやめてしまったのか今後の動向を追いたいと思う。

(2021/11/17追記) v3.9.1でarm64シミュレータ向けのサポートが復活した

2021/11/17にv3.9.1がリリースされた。arm64シミュレータとmaccatalystのサポートが復活していた。v3.8.xと同じように問題なくリンクエラーが通るようになった。