- Xcode にて、コードをコードを複数のプロジェクトから再利用できるようにライブラリ化して、別プロジェクトから利用したい。
フレームワークというのを作ればいい、らしいんだけど
スタティックライブラリだけのプロジェクトを作って、利用側はそのライブラリとヘッダを直接指定する、という昔からの手法もあるみだいだけど、最近は フレームワーク というのを使えば、ライブラリにリソースなども含められるし、管理も楽らしい、が、世間のノウハウを読んでるとどうにも胡散臭い。要約すると、
Xcode で Cocoa Touch Framework のプロジェクトを新規作成(うんうん)
共用したいコードなりリソースなりどんどん追加(うんうん)
ビルドすると .framework ファイルができあがるので、それを利用側アプリのプロジェクトに追加して、アプリのターゲット設定の Embedded Binaries に追加すれば使えるようになる(おー簡単じゃん!)
ただし、.framework は実機用とエミュレータ用、Debug と Release とかで別バイナリだから、実機とエミュレータで切り替えて使ってネ!。世間には全部入りの .framework を作ってくれる定番のスクリプトがあるからそれコピペすればいいよたぶん。
いや待って何それキモイ!
どのノウハウ読んでても途中まではいいんだけど、途中から皆当然のように 「このスクリプトをコピペ」 とか言い出すのが気持ち悪い!。なんでそんな不便極まりないのが標準手法みたいになってんだよ。
実はすげえ楽な方法が
で、こんなのおかしいよとさらに調べてみたんだけど、作った .framework を世間様に配布したい場合とかは皆が説明してるように全部入りの .framework を作る事になるぽい。でも自分用のフレームワークを自分のアプリで使うだけなら、もっとすげえ簡単な方法があった。
.framework ではなく、フレームワークプロジェクトの .xcodeproj をアプリのプロジェクトに放り込む と、フレームワークプロジェクトが参照扱いでアプリプロジェクトに内包され、フレームワーク側のプロジェクトも自由に変更できるようになる。
追加後にアプリ側のターゲット設定の Embedded Binaries で + を押すと、追加したフレームワークの .framework が簡単に追加できるようになってるので追加する。
たったこれだけで、
- アプリのビルド設定に応じて、必要な .framework が自動でビルド、リンクされる
- 実機とエミュレータで何も変更する必要なし
- デバッガもフレームワーク側のソースまでちゃんと追える
- フレームワーク側のソースもいつでも修正、即ビルド可能
- アーカイブ時はちゃんと Release ビルドで全CPU対応の .framework がリンクされる
と、何もかもいい感じに動く。なんで皆あんな面倒な事をしてるのか。
でもこの方法で紹介してる人ってほとんど居ないんよね。本当に大丈夫なのか不安になる。だけど出来上がった ipa がちゃんと Release 版の動作をしている事、arm64 と armv7s の端末でどちらも動作する事は確認できたし、Chronus もこの方法でマスタリングして審査通ってるので、まあ問題ないんじゃね?、としか。下記サイトの人はこの方法に気付いた後もスクリプトで .framework 作ってるけど、必要ないと思う。
ちなみに Embedded Binaries で追加した .framework を Show in Finder で確認すると、Debug 版の実機用の .framework を直接参照しててすげえ不安になるんだけど、実際うまく動いてる以上、ビルド時に何とかされてると思うしかない。この .framework の場所って、フレームワーク側じゃなくてアプリ側の作業フォルダ内なので、アプリ側が自分用にビルドしてるという事は見て取れる。
ワナポイント1:並べるんじゃねぇ。従えるんだ
上の図にもコソっと書いたけど、フレームワークの .xcodeproj をアプリのプロジェクトに追加するときは、アプリのプロジェクトと同列ではなく、アプリプロジェクト内に内包 させた方がよさげ。
同列に並べても一見問題なく使えるんだけど、使い方が悪いと意図と違う .framework がリンクされる事があった。
自作フレームワークをアプリにリンクさせる方法をネットで調べてると、
Embedded Binaries に追加
Linked Frameworks and Libraries に追加。その場合は .framework が ipa にコピーされないので、Build Phases で New Copy Files Phase を使ってコピー
と2つの方法が見つかるんだけど、アプリとフレームワークのプロジェクトが同列に並んでいる状況で2の方法でリンクした場合、Release ビルド時にも Debug 版の .framework がコピー されてヒドイ事になった。そもそも2はどうにも無理矢理な感じがするし、素直に Embedded Binaries を使えばいいと思う。
他にも、同列に並べてるとアプリプロジェクト内に .framework が2つ表示されたり、バグっぽくなる印象。Visual Studio とかに慣れてると同列に並べたくなるけど、依存関係がはっきりするという意味ではアプリプロジェクト内に含めるのが自然な気もしてきた。
ワナポイント2:勝手に作られるヘッダが邪魔なんだけど
テンプレートでフレームワークを作ると、勝手にフレームワークと同名のヘッダが1つ作られてる。これは アンブレラヘッダ と言って、これを用意しとけば利用側でライブラリのヘッダの位置をいちいち設定しなくても、
#import <MyFramework/MyFramework.h>
てな感じで簡単にインポートできるし、そのヘッダのインポートだけで全機能使えるようできたり便利、とかそんな感じらしいんだけど、移植作業とかで元からフレームワークと同名のヘッダが存在してる場合、このアンブレラヘッダがすげえ邪魔。
で、なんか中を見るとバージョン定数とか勝手に宣言されてるし、無下に消していいものか扱いに困るんだけど、試しにファイル名変えてみたら
no umbrella header found for target 'フレームワーク名',
module map will not be generated
とかワーニング出た。だったら Build Setting で場所とか指定すればいいのかと思えばそんな設定もない。
調べてみたところ、アンブレラヘッダは必ずターゲット名(※プロジェクト名ではない)と同じにしないといけない ようなので、もう面倒なのでターゲット名を変えましたとさ。はいはいApple様の仰せのままに。なおターゲット名を変えると .framework の名前も変わるので注意。
あと、何が原因だったのか再現できないんだけど、アンブレラヘッダが存在してるのに無いと言い張られて、しかもエラー扱いでビルドも通らずキレかけた事がありましたが、そういう場合は Build Phases の Headers の内にあるアンブレラヘッダを Public に持っていけば治る そうな。確かに治った。でもこの記事を書くために再現させようと戻してみたらエラー出ない。もういいや放っとこう。
【2017/11/21追記】
フレームワーク内に Swift のソースが含まれる場合のみ、上記のようにアンブレラヘッダは Public に置かないとビルドエラーになる模様。また、以下のようにすればアンブレラヘッダ名をターゲット名と別の名前にすることも可能。
- 以下の内容のファイルを module.modulemap という名前でプロジェクトに追加。
framework module フレームワーク名 { umbrella header "アンブレラヘッダ名.h" export * module * { export * } }
- 上記ファイルをターゲットの Build Setting の Module Map File で指定。
これでアンブレラヘッダ名は以下のように import できるようになる。
#import <フレームワーク名/アンブレラヘッダ名.h>
なお Module Map は他にもいろいろできるみたいだし、本来こんな事をするための物じゃないんだろうけど、そもそもアンブレラヘッダとか Xcode 固有の機能は使う気ないし、ライブラリと同名のヘッダを勝手に作られるのは困る!、とかいう場合はこうすれば名前を変える事はできますよ、てことで。