Objective-Cで書かれたframeworkのSwift対応

先日書いた記事 iOSにおける静的ライブラリ作成技法 - 渋谷ラーメン男道 で、iOSにおける静的ライブラリ・frameworkの作成方法について紹介しましたが、あの内容だけだと Swift のプロジェクトからインポートすることが出来なかったのでその辺の話になります。

Swift からObjective-C のソースを利用する方法

Swift から Objective-Cのソースを利用する方法は2種類あります。

  1. Bridging-Headerを利用する
  2. 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 ではインポートできないことを確認します。

f:id:DayBySay:20160124010857p:plain プロジェクトは特に特別な設定を行っていません。

次に framework のリンクを行います。

アプリのターゲット -> Linked Frameworks and LIbraries -> Add Other から前回作成した framework を選択します。 f:id:DayBySay:20160124011041p:plain

framework が追加されました。 f:id:DayBySay:20160124011216p:plain

framewrok のインポートを試してみます f:id:DayBySay:20160124011252p:plain

やはり、現状ではエラーがでてしまうので、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.hHOGEFugaService.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()
    }
}

ビルドしてみます。今度は成功しました!

f:id:DayBySay:20160124014854p:plain

f:id:DayBySay:20160124014936p:plain

実際に処理も成功し、ログが吐かれているのが確認できました。

以上、 Objective-C で書かれた framework のSwift対応でした。