計測フレームワークのSwift Package Manager導入への道のり

計測フレームワークのSwift Package Manager導入への道のり

はじめに

こんにちは、計測プラットフォーム開発本部iOSブロックの中岡です。普段はZOZOMAT/ZOZOGLASSの運用・保守や計測技術を使った新規事業の開発をしています。

目次

計測フレームワークとは

私たちのチームは、ZOZOMAT/ZOZOGLASSの機能を開発し、それらをライブラリとしてZOZOTOWN iOSチームに提供しています。このライブラリのことを私たちは計測フレームワークと呼んでいます。そしてこのライブラリの提供方法として今まではCocoaPodsを利用していました。元々はCarthageを利用していたのですが、Apple silicon導入に伴いCocoaPodsへ移行しています。そちらの経緯は以下の記事とMeetupのアーカイブをご参照ください。

techblog.zozo.com

youtu.be

speakerdeck.com

Swift Package Managerへの移行の経緯

基本的にCocoaPodsに移行してからは開発体験などに問題はありませんでした。しかし、Appleから発表されたPrivacy Manifestに対応するため、依存ライブラリのいくつかを最新に更新する必要があり、そこでいくつかの問題に直面しました。以下はZOZOTOWN iOSの依存関係の一部です。

ZOZOTOWN iOSの依存関係

この中で以下のライブラリがPrivacy Manifestの対象1でした。

Lottieに関しては特に問題なくCocoaPodsで最新版に更新できました。しかし、BoringSSLとnanopbはCocoaPodsで最新版にするには一筋縄ではいきませんでした。まずBoringSSLに関しては、依存しているgrpc-swiftの最新版がCocoaPodsでのサポートはされておらず、Swift Package Manager(以下SPM)のみとなっていました。

github.com

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に移行するだけです。以下の公式ドキュメントを参考に作業を進めました。

github.com

developer.apple.com

移行前の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つのハマりポイントがありました。

  1. Objective-Cが含まれているSwift Packageでのバンドルリソースへのアクセス方法
  2. 同じソースコードを複数のターゲットで使用できない

まず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では、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。

corp.zozo.com


  1. https://developer.apple.com/support/third-party-SDK-requirements/
  2. 5/1現在、最新のARCoreが依存しているnanopbはPrivacy Manifest対応されていないが将来的に対応される予定(issue
  3. https://techblog.zozo.com/entry/zozomat-cross-platform-3d
  4. https://developer.apple.com/videos/play/wwdc2020/10169/
カテゴリー