こんにちは。koogawa です。
さて、一つのプロジェクトで複数のアプリを開発していると、共通部分をフレームワークに切り出して再利用したくなりませんか?
私はなります。
というわけで、今回は Xcode 6 から導入された Embedded Framework という仕組みを使って、フレームワークを作成する方法を紹介します。
目次
実行環境
プロジェクト構成
A と B、2つのターゲットがあり、ターゲットAの Client
クラスをフレームワークに切り出して、ターゲットA、Bの両方から利用可能にするケースを考えます。
Xcode 上は次のようになっています。
ターゲットAのソースファイルは A ディレクトリに、ターゲットBのソースファイルは B ディレクトリに格納します。
Client
クラスは API と通信し、レスポンスデータを String 型にして返す request
メソッドを持ちます。*1
import Alamofire
class Client {
init() {
}
func request(_ complete: @escaping (String?) -> Void) {
Alamofire.request("https://httpbin.org/get").responseJSON { response in
var responseString: String? = nil
if let data = response.data {
responseString = String(data: data, encoding: .utf8)
}
complete(responseString)
}
}
}
見ておわかりの通り、通信ライブラリである Alamofire を利用しています。
ライブラリ管理は CocoaPods で行ないます。
Podfile:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target 'A' do
pod 'Alamofire', '~> 4.7'
end
ターゲットAでの作業
それでは Client
クラスをフレームワークに切り出していきましょう。
Cocoa Touch Framework を追加
Xcodeのツールバーから「File」 → 「New」 → 「Target」を選択すると、次の画面が表示されます。
「Cocoa Touch Framework」を選択します。
「Product Name」は "Client" にしておきます。他の項目を適切なものにセットして「Finish」を押しましょう。
Project Navigator に「Client」が追加されたと思います。このディレクトリにフレームワークのソースファイルを追加していきます。
ソースファイルを追加
Client.swift をフレームワークに切り出したいので、ソースファイルをドラッグアンドドロップします。
切り出したクラスやメソッドは、外からもアクセスできるように public
修飾子をつけておきます。
public class Client {
public init() {
}
public func request(_ complete: @escaping (String?) -> Void) {
Alamofire.request("https://httpbin.org/get").responseJSON { response in
var responseString: String? = nil
if let data = response.data {
responseString = String(data: data, encoding: .utf8)
}
complete(responseString)
}
}
}
インポートして使ってみる
ターゲットAから使ってみましょう。Client
を import するだけで使えるようになります。
import Client
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let client = Client()
client.request({ responseString in
if let responseString = responseString {
print(responseString)
}
})
}
}
この時点でいったんビルドしてみると
/Path/To/Sample/Client/Client.swift:10:8: No such module 'Alamofire'
のようなエラーが出ると思います。どうやらフレームワークから Alamofire が読み込めていないようなので、Podfile を次のように書き換えてみましょう。
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target 'Client' do
pod 'Alamofire', '~> 4.7'
end
再度 pod update
してビルドしてみましょう。今度はビルドが通りましたね。
今度は ⌘R
で Run してみましょう。
dyld: Library not loaded: @rpath/Alamofire.framework/Alamofire
おや、クラッシュしましたね。まだ Alamofire が読み込めていないようです。今度は Podfile を下記のように書き換えてリトライしてみましょう。
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target 'A' do
pod 'Alamofire', '~> 4.7'
end
target 'Client' do
pod 'Alamofire', '~> 4.7'
end
今度はクラッシュしませんでしたね!
このように CocoaPods を使う場合は、フレームワークだけでしか利用していないライブラリもメインターゲットにインストールする必要があるので注意してください。
ターゲットBでの作業
次はターゲットBからもフレームワークを使えるようにしていきましょう。
Embedded Binaries にフレームワーク追加
TARGET から「B」を選択し、General の中にある「Embedded Binaries」に Client.framework
を追加します。
Podfile 更新
ターゲットBにも Alamofire をインストールします。
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target 'A' do
pod 'Alamofire', '~> 4.7'
end
target 'B' do
pod 'Alamofire', '~> 4.7'
end
target 'Client' do
pod 'Alamofire', '~> 4.7'
end
これでも動くのですが、ターゲットごとに同じライブラリ名を記述するのは冗長なので、次のように abstract_target
でまとめるのが良いでしょう。
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
abstract_target 'All' do
pod 'Alamofire', '~> 4.7'
target 'A' do
end
target 'B' do
end
target 'Client' do
end
end
インポートして使ってみる
Client
を import してビルドしてみます。ビルドターゲットを切り替えるのを忘れずに。
import Client
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let client = Client()
client.request({ responseString in
if let responseString = responseString {
print(responseString)
}
})
}
}
ビルドできましたね。
これでターゲットBからもフレームワークが使えるようになりました!
フレームワークがインポートできない場合は次のことを確認してみてください。
- フレームワークのクラス宣言やメソッドに
public
修飾子は付いていますか?
- Xcode のキャッシュが原因の場合もあるのでクリーンビルドすると解決することがあります
- 再度
pod update
することで解決することもありました
さいごに
Embedded Framework を使って、フレームワークを作成する方法を紹介しました。少しだけハマりポイントもありましたが、意外と簡単だったのではないでしょうか。
また、今回は触れませんでしたが、Apple Watch アプリや Today Widget などの App Extentions を持つアプリの場合、メインターゲットと Extension 間のコード共有も Embedded Framework で可能になりますのでぜひ活用してみてください。