Objective-Cで書かれたframeworkのSwift対応
先日書いた記事 iOSにおける静的ライブラリ作成技法 - 渋谷ラーメン男道 で、iOSにおける静的ライブラリ・frameworkの作成方法について紹介しましたが、あの内容だけだと Swift のプロジェクトからインポートすることが出来なかったのでその辺の話になります。
Swift からObjective-C のソースを利用する方法
Swift から Objective-Cのソースを利用する方法は2種類あります。
Bridging-Header
を利用する- Clang の
Moduels
を利用する
Bridging-Header
公式のリファレンス Using Swift with Cocoa and Objective-C (Swift 2.1): Swift and Objective-C in the Same Project にもありますが、Bridging-Header.h
ファイルを利用することで利用可能です。
Swift 対応していないサードパーティライブラリを利用する場合はこの方法が主流じゃないでしょうか。
しかし、この方法はライブラリ提供側ではなく利用側の設定になります。
ライブラリを提供する立場では、次に紹介するModules
を利用して Swift 対応したライブラリを提供することで、利用側でBridging-Header
を設定する手間をなくすことが出来ます。
Modules
Modules
は Clang の3.3から提供されている機能です。
iOSの文脈ではXcode5、iOS7から利用可能になっており、Modules
を利用することで、コンパイル周りが最適化され、アプリケーションのコンパイル速度向上が期待できます。
また、Objective-Cで書かれたライブラリを Module として読み込むことで、Swift から利用可能になります。
今回は、前回の記事で作成した framework を例に、 Module を作成する方法を紹介します。
前回同様こちらのリポジトリで作成したプロジェクトを公開しています。
Swiftプロジェクトの作成
まずは Swift プロジェクトを作成して、前回のままの framework ではインポートできないことを確認します。
プロジェクトは特に特別な設定を行っていません。
次に framework のリンクを行います。
アプリのターゲット -> Linked Frameworks and LIbraries -> Add Other から前回作成した framework を選択します。
framework が追加されました。
framewrok のインポートを試してみます
やはり、現状ではエラーがでてしまうので、Module 化を行います。
framework を Module 化する
作業としては framework 内に Modules
ディレクトリを作成し、その下にmodule.modulemap
を追加するだけです。
framework の最終的なディレクトリ構成は、公式ページにある通り下記のようになるはずです。
Name.framework/ Modules/module.modulemap Module map for the framework Headers/ Subdirectory containing framework headers Frameworks/ Subdirectory containing embedded frameworks Resources/ Subdirectory containing additional resources Name Symbolic link to the shared library for the framework
今回はmodule.modulemap
ファイルは下記のようにしました。
framework module HOGEFuga { umbrella header "HOGEFuga.h" export * module * { export * } }
上記記述では、HOGEFuga.h
をアンブレラヘッダとして追加しているので、HOGEFuga.h
内でインポートされているファイルを SubModule
として読み込む記述になっています。
HOGEFuga.h
はHOGEFugaService.h
をインポートしているので、Swift プロジェクト側で Import HOGEFuga
するだけでHOGEFugaService
クラスを利用できるようになるはずです。
#import <Foundation/Foundation.h> #import <HOGEFuga/HOGEFugaService.h>
元のプロジェクトではMakefile
を使って framework を作成しているのでそれも編集します。
最終的には下記のようにしました。
XCODEBUILD?=$(shell which xcodebuild) TARGET_NAME?=HOGEFuga LIB_NAME:=lib${TARGET_NAME}.a PROJECT_NAME:=${TARGET_NAME}.xcodeproj BUILD_DIR:=./ CONFIGURATION?=Release FW_DIR:=${TARGET_NAME}.framework FW_DIR_RES:=${FW_DIR}/Resources FW_DIR_HEAD:=${FW_DIR}/Headers FW_DIR_MOD:=${FW_DIR}/Modules FW_DIRS:=${FW_DIR_RES} ${FW_DIR_HEAD} ${FW_DIR_MOD} all: HOGEFuga.framework HOGEFuga.framework: libHOGEFuga.a module.modulemap mkdir -p ${FW_DIRS} cp ${TARGET_NAME}/*.h ${FW_DIR_HEAD}/. cp ${TARGET_NAME}/Info.plist ${FW_DIR_RES}/. cp module.modulemap ${FW_DIR_MOD}/. cp $< ${FW_DIR}/${TARGET_NAME} libHOGEFuga.a: libHOGEFugaPhoneos.a libHOGEFugaPhonesimulator.a lipo -create -output $@ $^ rm -rf build/ Release-*/ libHOGEFugaPhoneos.a: ${XCODEBUILD} -target ${TARGET_NAME} -project ${PROJECT_NAME} -configuration ${CONFIGURATION} -sdk iphoneos -arch armv7 -arch armv7s -arch arm64 clean build ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" mv ${BUILD_DIR}/${CONFIGURATION}-iphoneos/${LIB_NAME} $@ libHOGEFugaPhonesimulator.a: ${XCODEBUILD} -target ${TARGET_NAME} -project ${PROJECT_NAME} -configuration ${CONFIGURATION} -sdk iphonesimulator -arch i386 -arch x86_64 clean build ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" mv ${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${LIB_NAME} $@ clean: rm *.a rm -rf *.framework
最後に$ make
で framework を作り直します。
Swiftで実装する
Swift の実装は下記のようにしました。
import UIKit import HOGEFuga class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. HOGEFugaService.show() } }
ビルドしてみます。今度は成功しました!
実際に処理も成功し、ログが吐かれているのが確認できました。
以上、 Objective-C で書かれた framework のSwift対応でした。