はじめに
最近は多くのiOSアプリ用ライブラリでSwift Package Manager(SwiftPM)に対応してきています。
Xcode11でSwiftPMのサポートが充実し導入しやすくなり、
Swift5.3からはコード以外のリソース類もSwiftPMで管理できるようになりました。
今では、もう普通に使えるんじゃないかって感じで利用している人も多いと思います。
自分としてはXcodeに組み込まれたことでXcodeでライブラリを自動管理できるようになったことがかなり大きく、
今では基本的にはライブラリをSwiftPMで導入しています。
そんな感じでとても便利に使えるSwiftPMなのですが、作る側からするとどうでしょうか?
いくつかのライブラリでSwiftPM対応した経験から、リソース周りで結構癖が強いなということを感じました。
そこで実際にすんなりいかなかったポイントをTipsとしてまとめておきます。
Tips
自動的に入るリソースの種類は限られている
そもそもリソースを含んだプロジェクトでPackage.swift
を作ったらどうやってリソース指定するの...?という感じなのですが、何も指定しなくて大丈夫です。
target
のSource
以下にxcassets
やstoryboard
などを突っ込んでおけば、Xcode上でライブラリを導入したときに勝手に入れてくれます。
let package = Package(
name: "HogeLibrary",
platforms: [
.iOS(.v14)
],
products: [
.library(name: "HogeLibrary", targets: ["HogeLibrary"])
]
targets: [
.target(name: "HogeLibrary", path: "Sources")
]
)
しかし、勝手に入ってくれるものはごく一部で、例えばjsonファイルなどは含まれません。
そういったファイルの場合どう対応すればいいでしょうか?
target
にresources:
を追加する必要があります。
targets: [
.target(name: "HogeLibrary", path: "Sources", resources: [.copy("./hoge.json")])
]
リソースがうまく入っていなかった場合はこのような対応を試してみましょう。
mlmodelはコンパイルしておく必要がある
iOS的に特殊に扱われるリソースであれば大丈夫なのかと思って高をくくっていると、思わぬところで躓いてしまいます。
自分の場合は、.mlmodel
を持っているライブラリをSwiftPMへ対応するときに面倒なことになりました。
.mlmode
lはSwiftPMで自動的にライブラリとして導入することはできません。
では、どうするかときうと事前に.mlmodelc
へコンパイルしておく必要があります。
xcrun coremlcompiler compile HogeModel.mlmodel HogeModel.mlmodelc
そしてjsonの時と同じように、resources
として指定します。
targets: [
.target(name: "HogeLibrary", path: "Sources", resources: [.copy("./HogeModel.mlmodelc")])
]
しかしここで不都合が発生してしまいます。
.mlmodelc
にして導入したということは、IFである.swift
クラスの自動生成などは導入側では行われません。
そのためSwiftPMで.mlmodel
を入れるのであれば、一旦自動生成されたmlmodelのIFとなるSwiftクラスをコピーしてきてそのクラスへ.mlmodelc
を渡すことで初期化する必要があります。
特定のパッケージマネージャーに対応するためにコードの修正が必要になるというちょっと面倒なケースになります。
Bundleの指定はSwift Flagで出し分ける必要がある
上述の.mlmodel
はライブラリとして持つことは特殊なケースなのかもしれません。
普通はstoryboardやxibなどが多いと思うので特に何も考えなくていいかなと思うかもしれませんが、そうはいきません。
Storyboardが自動で入るのはいいものの、いざそのStoryboardをUIStorybard
クラスで初期化しようとするとクラッシュします。
理由としてはSwiftPMはSwiftPM専用のBundleを指定する必要があるからです。
ライブラリを作る場合、.main
バンドルは使わないように注意していると思います。
そのため、以下の初期化子を使っている人が多いかなと思います。
SwiftPMの場合はこれらではnilが返ってきます。ではどうするのかというとその代わりに↓を使います。
- Bundle.moudle
「APIドキュメントに載ってないけど、これはなんだろう?」って思うかもしれません。
こいつはSwiftPM専用のStatic変数で、Package.swift経由で導入したリソースを取得するためのものです。
SwiftPM専用のものなのでXcode上で直接呼び出してもエラーになってしまいます。使うときは↓のようにします。
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: HogeViewController.self)
#endif
この辺りもSwiftPM用にコードの書き換えが必要になってくるため、ちょっと面倒なところではあります。
Storyboard上ではModuleを明示しないければならない
これは単にひたすら対応がめんどくさいやつです。SwiftPMではStoryboard・XIB上の↓の部分を明示的に指定しないと、IBOutletのプロパティがnilになってしまいます。
(ほとんどのケースがそうだと思いますが)IUOとして定義されている場合、一個でも指定漏れがあるとクラッシュを引き起こします。
丁寧に対応していきましょう。
Storyboard上ではinherit module from targetをつけなければならない
よっしゃ全部にModule名つけた!完璧だ!ってなってもまだ駄目です。
その下のInherit Module From Target
もちゃんと全部チェックつけていきましょう。
そうしないと同じくクラッシュの原因になります。
これも丁寧に対応していきましょう。
まとめ
他にも、xcodeからdynamic frameworkとして作った場合に設定が反映されない、などの細かいハマリポイントなどもあります。
この辺は慣れもありますが、やってみると「あれ...?SwiftPMちょっと扱いにくいかも...」と感じるかもしれません。
ただし、使う側としてはSwiftPM対応されたライブラリはとても導入しやすいので、もっとどんどん対応されたライブラリが増えるとありがたいな!と思っています(笑)