はじめに
普段は弊社サービスの、物件の撮影を補助/管理をするアプリのiOSの開発をしています。
今回は、研究開発チームが作成した、画像を台形補正する社内向けのライブラリをiOSプロジェクトに入れた時の内容をまとめます。
現在、iOSプロジェクトでは、Swift Package Manager(以下”SPM”と略)でライブラリ管理をしており、今回も他のライブラリと同様にSPMで対応することにしました。
今回は通常のSPMへの対応に加えて、下記のケースに対応する必要がありました。
前準備 (C++で書かれたライブラリをSwiftで使えるように)
SPMへの対応に入る前に、全てC++で書かれているライブラリをSwiftで使えるようにする対応をしました。 方法としては、Objective-C++のWrapperを用意してそれを経由して呼び出すようにしました。
Swiftから直接C++を呼び出す方法も、実験的に導入されているみたいですが今回は試していないです。
SPMへの対応
完成したPackage.swiftの中身
手順の前に完成したPackage.swift
をお見せします。
import PackageDescription let package = Package( name: "upright_images", products: [ .library( name: "upright_images", targets: ["UprightImages"]) ], dependencies: [], targets: [ .binaryTarget( name: "opencv2", path: "iOS/libs/opencv2.xcframework.zip" ), .target( name: "UprightImagesWrapper", dependencies: ["opencv2"], path: "iOS", sources: ["rectification", "Wrapper"], publicHeadersPath: "Wrapper/include", cxxSettings: [.headerSearchPath("libs/Eigen")] ), .target( name: "UprightImages", dependencies: ["UprightImagesWrapper"], path: "iOS/Swift" ) ], cxxLanguageStandard: CXXLanguageStandard.cxx14 )
対応手順
1. OpenCVをXCFrameworkにしてプロジェクトに追加
ライブラリが利用しているOpenCVをライブラリに含める必要があります。 OpenCVは、XCFramework形式で追加しました。XCframeworkを使用すると、iOS実機、iOSシミュレーターなどの複数のプラットフォーム用のフレームワークを1つにまとめることができます。
方法はOpenCVのドキュメントに記載されている通りです。
--without
オプションで、使用しないmoduleを指定してサイズを小さくするのがポイントです。
2. OpenCVをTargetに追加
追加したOpenCVを利用できるようにTargetに追加します。
まず、Package.swift
ファイルを作成し、Target.binaryTarget(name:path:)
を用いてTargetに追加して、依存関係に追加できるようにします。
targets: [ .binaryTarget( name: "opencv2", path: "iOS/libs/opencv2.xcframework.zip" ), ]
3. Eigenを追加
submoduleを利用してプロジェクトに追加します。
4. 各Targetを追加
ライブラリ本体をTargetとして追加するのですが、C系のファイル(C, C++, Objective-C, Objective-C++)と Swiftファイルを同じTargetに混在させてはいけないという仕様があります。
Targets can contain Swift, Objective-C/C++, or C/C++ code, but an individual target can’t mix Swift with C-family languages. For example, a Swift package can have two targets, one that contains Objective-C, Objective-C++, and C code, and a second one that contains Swift code.
そのため、
- C++のコードとObjective-C++のWrapperのTarget
- SwiftのコードのTarget
を分けて作成します。
4-1. C++のコードとObjective-C++のWrapperのTarget
.target( name: "UprightImagesWrapper", dependencies: ["opencv2"], path: "iOS", sources: ["rectification", "Wrapper"], publicHeadersPath: "Wrapper/include", cxxSettings: [.headerSearchPath("libs/Eigen")] )
ここで、
- OpenCVに依存するので、
dependencies
に先ほど作成したopencv2
のTargetを追加します。 - C系のTargetは、他のTargetに公開するために
Target.publicHeadersPath
で公開するファイルのパスを指定する必要があります。今回利用したいのはObjective-C++のWrapperファイルのみなので、それが含まれたパスWrapper/include
を指定します。 - Eigenを利用するために、
CXXSetting.headerSearchPath(_:_:)
でEigenのsubmoduleが含まれるパスlibs/Eigen
を指定します。
4-2. SwiftのコードのTarget
.target( name: "UprightImages", dependencies: ["UprightImagesWrapper"], path: "iOS/Swift" )
こちらは、先ほど作成したC系のTargetのUprightImagesWrapper
を依存に追加するだけです。
5. library productとして公開
最後にライブラリとして利用するために、Product.library(name:type:targets:)
を使って公開します。
.library( name: "upright_images", targets: ["UprightImages"] )
以上でSPMとして公開できました。あとはiOSプロジェクトの方で通常通りSPMを追加するだけで利用できるようになります。
その他つまずいた箇所
おまけでつまずいたところを2箇所紹介します。
sourcesで指定し忘れていた
Undefined symbols for architecture arm64: "_OBJC_CLASS_$_UprightImagesWrapper", referenced from: objc-class-ref in UprightImages.o ld: symbol(s) not found for architecture arm64 clang: error: linker command failed with exit code 1 (use -v to see invocation)
ずっとこのエラーが消えないと悩んで時間を溶かしていたのですが、C系のTarget(UprightImagesWrapper
)の方でsourcesにObjective-C++のWrapperのパスを含め忘れていただけでした。
Git LFSを使用するとレポジトリのチェックアウトができなかった
iOSプロジェクトで、SPMでライブラリを追加しようとした時に下記エラーで追加できませんでした。
Couldn’t check out revision 'XXX'
対象のrepositoryがGit LFSを利用していると出るエラーのようです。
結局、今回は、OpenCVのmoduleを必要なものだけに削ってzip化した際に、Git LFSを使わなくなったので、どう解決するかは調べていません。
参考文献
- Apple Developerのdocument各種
- 社内ライブラリを Swift Package Manager に対応させた話 その2 ~OpenCV に依存したライブラリ編~
- How to Create a Swift Package From a C++ Library
さいごに
spacelyでは一緒に働いてくださる方を大募集中です。