はじめに
こんにちは、計測プラットフォーム開発本部iOSブロックの中岡です。普段はZOZOMAT/ZOZOGLASSの運用・保守や計測技術を使った新規事業の開発をしています。
目次
計測フレームワークとは
私たちのチームは、ZOZOMAT/ZOZOGLASSの機能を開発し、それらをライブラリとしてZOZOTOWN iOSチームに提供しています。このライブラリのことを私たちは計測フレームワークと呼んでいます。そしてこのライブラリの提供方法として今まではCocoaPodsを利用していました。元々はCarthageを利用していたのですが、Apple silicon導入に伴いCocoaPodsへ移行しています。そちらの経緯は以下の記事とMeetupのアーカイブをご参照ください。
Swift Package Managerへの移行の経緯
基本的にCocoaPodsに移行してからは開発体験などに問題はありませんでした。しかし、Appleから発表されたPrivacy Manifestに対応するため、依存ライブラリのいくつかを最新に更新する必要があり、そこでいくつかの問題に直面しました。以下はZOZOTOWN iOSの依存関係の一部です。
この中で以下のライブラリがPrivacy Manifestの対象1でした。
Lottieに関しては特に問題なくCocoaPodsで最新版に更新できました。しかし、BoringSSLとnanopbはCocoaPodsで最新版にするには一筋縄ではいきませんでした。まずBoringSSLに関しては、依存しているgrpc-swiftの最新版がCocoaPodsでのサポートはされておらず、Swift Package Manager(以下SPM)のみとなっていました。
nanopbに関しては、依存しているARCoreはCocoaPodsをサポートしています。しかし、CocoaPodsを使用してARCoreの最新版を導入すると、問題が発生しました。本来インストールしているOpenCVではなく、ARCoreが内部で使用しているOpenCVとZOZOGLASSがリンクし、クラッシュすることがわかりました。この問題は、SPMを利用してARCoreを導入することで解消されたことが確認されており、現在はその詳しい原因を調査中です。
これらの問題から、計測フレームワークをSPMへ移行することにしました。また合わせて、ZOZOTOWN iOSは共有の依存としてLottieやnanopb(Firebaseの依存)を持っているので、これらも同時にSPMへ移行しました。
Swift Package Managerへの移行
基本的には、podspecに記載されているdependencyやsource_fileをPackage.swiftに移行するだけです。以下の公式ドキュメントを参考に作業を進めました。
移行前のpodspecの一部
Pod::Spec.new do |spec| spec.name = "ZOZOMAT" # 省略 spec.source_files = "Sources/ZOZOMAT/**/*.swift" spec.resource_bundles = { "ZozomatResources" => [ "Sources/ZOZOMAT/Resources/**/*.xib", # 省略 ] } spec.dependency "SwiftGRPC", "0.11.0" spec.dependency "lottie-ios", "3.2.3" # 省略 } end
移行後のPackage.swiftの一部
import PackageDescription let package = Package( name: "ZOZOMAT", defaultLocalization: "ja", platforms: [ .iOS("15.5.0") ], products: [ .library( name: "ZOZOMAT", targets: ["ZOZOMAT"] ), ], dependencies: [ .package(url: "https://github.com/grpc/grpc-swift.git", .upToNextMajor(from: "1.21.1")), .package(url: "https://github.com/airbnb/lottie-spm", .upToNextMajor(from: "4.4.0")), // 省略 ], targets: [ .target( name: "ZOZOMAT", dependencies: [ .product(name: "Lottie", package: "lottie-spm"), .product(name: "GRPC", package: "grpc-swift"), // 省略 ], path: "Sources/ZOZOMAT", // source_fileに対応するディレクトリを指定 resources: [ .process("Resources") ], ) ] )
移行作業でハマったこと
そして作業を進めていく中で以下の2つのハマりポイントがありました。
- Objective-Cが含まれているSwift Packageでのバンドルリソースへのアクセス方法
- 同じソースコードを複数のターゲットで使用できない
まず1に関して、ZOZOMATの3D表示の機能はC++とObjective-Cで書かれており3、それらは別のライブラリとして管理されています。そしてこのライブラリはシェーダーコードなどのリソースを持っています。Objective-CからSwift PackageのバンドルにアクセスするにはSWIFTPM_MODULE_BUNDLE
というマクロを使用します4。基本的にはSwiftのBundle.module
と同じように動作します。以下のPackage.swiftの場合、リソースバンドルはSampleObjc-Package_SampleObjc-Target.bundle
という名前になります。
import PackageDescription let package = Package( name: "SampleObjc-Package", products: [ .library( name: "SampleObjc-Target", targets: ["SampleObjc-Target"]), ], targets: [ .target( name: "SampleObjc-Target", path: "Sources/SampleObjc", resources: [ .process("Resources") ] ), ] )
しかし、Objective-CからSWIFTPM_MODULE_BUNDLE
を使いアクセスしようとすると、以下のエラーのようなエラーが出て、クラッシュします。これは実際のバンドル名がSampleObjc-Package_SampleObjc-Target.bundle
なのに、SampleObjc_Package_SampleObjc_Target.bundle
にアクセスしているために発生します。これを避けるためにObjective-CのSwift Packageでは名前に-
を含めないようにする必要があります。
*** Terminating app due to uncaught exception 'SwiftPMResourcesAccessor', reason: 'unable to find bundle named SampleObjc_Package_SampleObjc_Target'
次に2の「同じソースコードを複数のターゲットで使用できない」に関してです。ZOZOMATは一部の機能をZOZOTOWN iOSとZOZOMATの計測機能のみを持つデモアプリで処理を分岐させるためにActive Compilation Conditionsを使っています。podspecに以下のようにフラグを設定しZOZOTOWNに提供しており、デモアプリの場合はpod install
時にpost_install
でそのフラグを書き換えていました。
ZOZOMATのpodspec
# ZOZOTOWN用フラグを設定 spec.pod_target_xcconfig = { "SWIFT_ACTIVE_COMPILATION_CONDITIONS" => "ZOZOMAT_RELEASE", }
ZOZOMATのローカルにあるデモアプリ用のPodfile
# デモアプリ用のフラグを設定 def set_demo_mode(pi) pi.pods_project.targets.each do |t| t.build_configurations.each do |bc| if t.name == "ZOZOMAT" bc.build_settings["SWIFT_ACTIVE_COMPILATION_CONDITIONS"] = "ZOZOMAT_DEMO" end end end end post_install do |pi| set_demo_mode(pi) end
これは元々Carthageを使っていた時に、デモアプリ用とZOZOTOWN用の2つのXcodeターゲットを用意し、フラグで処理を分岐させるという方法をとっていたためです。これと同じことを実現するために以下のように共通のソースコードを持つ2つのターゲットを定義し、それぞれに別のフラグを設定しようと考えましたが、SPMでは同じソースコードを複数のターゲットに含めることができませんでした。以下のようにPackage.swiftを定義しそれぞれのターゲットでswiftSetting
を設定しようとするとエラーが出ます。
そのため、現在は一時的な対応としてデモアプリを動かす際はswiftSetting
を書き換えて作業を行うという方法をとっています。将来的にはライブラリ内にこのような分岐を持つことは本来望ましくないので、リファクタリングをする予定です。
target 'ZOZOMATDemo' has overlapping sources: /Users/xxxxxx/SamplePackage/Sources/ZOZOMAT/Sample.swift
import PackageDescription let package = Package( name: "ZOZOMAT", products: [ .library( name: "ZOZOMAT", targets: ["ZOZOMAT"]), .library( name: "ZOZOMATDemo", targets: ["ZOZOMATDemo"]), ], targets: [ .target( name: "ZOZOMAT", path: "Sources/ZOZOMAT", swiftSettings: [ .define("ZOZOMAT_RELEASE") ] ), // 以下のようにすることはできない .target( name: "ZOZOMATDemo", path: "Sources/ZOZOMAT", swiftSettings: [ .define("ZOZOMAT_DEMO") ] ), ] )
まとめ
本記事では、計測フレームワークをSPMへ移行する際の経緯と移行作業でハマったポイントについて紹介しました。SPMへの移行により、依存ライブラリの更新ができPrivacyManifestの対応ができましたが、移行によって新たに発生した問題もあります。
- ブランチ切り替えの際に毎回Package resolveが走り時間がかかる
- ZOZOTOWN iOSはCocoaPodsとSPMの混在状態となっている
- ZOZOMATのデモアプリを動かす際に手動で
swiftSetting
を書き換える必要がある
今後はこれらの問題を解決し、より良い開発体験になるように改善していきたいと考えています。
ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。
- https://developer.apple.com/support/third-party-SDK-requirements/↩
- 5/1現在、最新のARCoreが依存しているnanopbはPrivacy Manifest対応されていないが将来的に対応される予定(issue)↩
- https://techblog.zozo.com/entry/zozomat-cross-platform-3d↩
- https://developer.apple.com/videos/play/wwdc2020/10169/↩