よく使う機能やクラスをひとまとめにして他で流用したいことがある。そんな時は、Frameworkを自作してしまうと楽だ。この記事では、こんな感じでオリジナルのフレームワーク(画像中の”TestFramework.framework”)を作る方法を説明する。
フレームワークでも静的ライブラリでもなんでもよかったんだけど、猫好きモバイルアプリケーション開発者記録の「Xcode 4におけるiOS Frameworkの作成方法」という記事が分かりやすかった。ただ、このブログ記事の通りに進めて行ってもビルドエラーが出てしまい、色々試行錯誤が必要だったので、ブログ記事を参考にした上で自分が進めたやり方を残す。
Frameworkとは何か
Xcode 4におけるiOS Frameworkの作成方法という記事が本当に分かりやすいので、そちらを参照すれば問題ない。
iOS向けFrameworkの作り方(準備編)
いよいよ本題。まずは当該記事のように進めていく。
メニューからFile → New → New Project…とたどって、iOSの中にあるFramework & Libraryの中の「Cocoa Touch Static Library」を選ぶ。
プロジェクトナビゲータ(画面左のファイル一覧)からプロジェクトを選んで、TARGETSの中のテストじゃない方を選び、「Valid Architectures」の中に「i386」を追加する。
同じくターゲットの「Public Headers Folder Path」を「/usr/local/include」から「/Headers」に書き換える。
「Resources」というグループを追加して、その中に「Info.plist」を追加する。
中身は以下を書けばよいが、、、
プロジェクトナビゲータのInfo.plistを右クリックしてOpen As → Source Codeを選択して、以下をコピペすると楽ちん。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundleExecutable</key> <string>${EXECUTABLE_NAME}</string> <key>CFBundleGetInfoString</key> <string>${EXECUTABLE_NAME}</string> <key>CFBundleIconFile</key> <string></string> <key>CFBundleIdentifier</key> <string>com.example.${PRODUCT_NAME:rfc1034identifier}</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>${EXECUTABLE_NAME}</string> <key>CFBundlePackageType</key> <string>FMWK</string> <key>CFBundleShortVersionString</key> <string>1.0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> <string>1</string> <key>NSHumanReadableCopyright</key> <string>Copyright (C) example.com All rights reserved.</string> <key>NSPrincipalClass</key> <string></string> </dict> </plist>
「Bundle identifier」と「Copyright (human-readable)」の変更を忘れないようにする。
プロジェクトナビゲータからプロジェクトファイルを選んで、下の方にある「Add Target」をクリック。
iOSの中のOthersの中の「Aggregate」を選択。Product Nameは”TestFramework-Universal”など適当につける。
TestFramework-Universalを選択後、「Build Phases」タブを選んで、右下のAdd Build Phaseから「Add Run Script」を選ぶ。
「Type a script or drag a script file from your workspace」と書かれている箇所に、以下をコピペする。(※Xcode 4におけるiOS Frameworkの作成方法という記事からコピー、一部を改変した。)
# Environment Variables FRAMEWORK_NAME=${PROJECT_NAME} FRAMEWORK_VERSION=A FRAMEWORK_VERSION_NUMBER=1.0 FRAMEWORK_BUILD_PATH="${SRCROOT}/build/${CONFIGURATION}-framework" FRAMEWORK_DIR="${FRAMEWORK_BUILD_PATH}/${FRAMEWORK_NAME}.framework" FRAMEWORK_PACKAGE_NAME="${FRAMEWORK_NAME}.${FRAMEWORK_VERSION_NUMBER}.zip" # Clean directories rm -rf "${FRAMEWORK_BUILD_PATH}" # Build simulator and device binaries. xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphonesimulator${IPHONEOS_DEPLOYMENT_TARGET} -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} clean build xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphoneos${IPHONEOS_DEPLOYMENT_TARGET} -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} clean build # create framework directories. mkdir -p ${FRAMEWORK_DIR} mkdir -p ${FRAMEWORK_DIR}/Versions mkdir -p ${FRAMEWORK_DIR}/Versions/${FRAMEWORK_VERSION} mkdir -p ${FRAMEWORK_DIR}/Versions/${FRAMEWORK_VERSION}/Resources mkdir -p ${FRAMEWORK_DIR}/Versions/${FRAMEWORK_VERSION}/Headers # create symlinks ln -s ${FRAMEWORK_DIR}/Versions/${FRAMEWORK_VERSION} ${FRAMEWORK_DIR}/Versions/Current ln -s ${FRAMEWORK_DIR}/Versions/Current/Headers ${FRAMEWORK_DIR}/Headers ln -s ${FRAMEWORK_DIR}/Versions/Current/Resources ${FRAMEWORK_DIR}/Resources ln -s ${FRAMEWORK_DIR}/Versions/Current/${FRAMEWORK_NAME} ${FRAMEWORK_DIR}/${FRAMEWORK_NAME} # create the universal library lipo ${SRCROOT}/build/${CONFIGURATION}-iphoneos/lib${FRAMEWORK_NAME}.a ${SRCROOT}/build/${CONFIGURATION}-iphonesimulator/lib${FRAMEWORK_NAME}.a -create -output "${FRAMEWORK_DIR}/Versions/Current/${FRAMEWORK_NAME}" # copy files cp ${SRCROOT}/build/${CONFIGURATION}-iphoneos/Headers/*.h ${FRAMEWORK_DIR}/Headers/ cp Info.plist ${FRAMEWORK_DIR}/Resources # zip (配布用に TestFramework.framework ディレクトリをZIP圧縮するだけなので、配布しないなら以下はコメントアウトしてもよい) cd ${FRAMEWORK_BUILD_PATH} zip -ry ${FRAMEWORK_PACKAGE_NAME} $(basename $FRAMEWORK_DIR)
ここまでで、基本的な下ごしらえはできた。あとは実際にフレームワークとして提供したいクラスをObjective-C Classの新規作成などから追加して作り始めればよい。ここから先は、フレームワークを実際に使う前もしくは配布前におこなう。
コーディングする
実際にクラスを作ったりする時は、ふつうにObjective-C Classとして追加したりすればよいが、外部から使えるようにしたい場合は、「プロジェクト名.h」のファイル内で#importしておく必要がある。
例えば、「TestFrameworkCalculator」というクラスを作ったとする。
“TestFrameworkCalculator.h”の中身は以下の通りで、
#import <Foundation/Foundation.h> @interface TestFrameworkCalculator : NSObject + (float)calcBMIByHeight:(float)height weight:(float)weight; @end
“TestFrameworkCalculator.m”の中身は以下の通りである。
#import "TestFrameworkCalculator.h" @implementation TestFrameworkCalculator + (float)calcBMIByHeight:(float)height weight:(float)weight { return weight / (height * height); } @end
この時、プロジェクト作成時に自動生成されたファイル「Testframework.h」は以下のように書き換える。(TestFrameworkクラスがいらなければ@interface〜@endと.mファイルの実装部を削除してしまっていい)
#import <Foundation/Foundation.h> #import "TestFrameworkCalculator.h" @interface TestFramework : NSObject @end
コンパイルする
コーディングが終わったら実際に他のプロジェクトでも使えるようにする。
ターゲット(テストやUniversalじゃない方)のBuild Phasesタブを開いて、Copy Headersカラムを開く。
Copy Headersの中の「Public」に、プロジェクトナビゲータからフレームワークとして必要な.hファイルをドラッグ&ドロップする。
左上のSchemeの左側(プロジェクト名になってるところ)をクリックして、Testframework-UniversalのiOS Deviceを選択する。
あとは普通にビルドするだけ。
実際にフレームワークを使ってみる
実際に使いたいiOSアプリのプロジェクトを作ったら、そのあぷりのターゲットの「Build Phases」タブを開いて、「Link Binary With Libraries」カラムにある「+」をクリックして、「Add Other…」からフレームワークのディレクトリを選ぶ。
フレームワークのディレクトリはフレームワークのプロジェクトファイルと同じディレクトリから./build/Debug-framework/{フレームワーク名}.framework」を選べばよい。
これでプロジェクト内で使えるようになった。あとは「#import <Testframework/Testframework.h>
」みたいなことをアプリ内で使いたいファイルに書けば、使える。便利!
まとめ
フレームワークつくるの大変だけど便利。
本記事は多くの部分を猫好きモバイルアプリケーション開発者記録の「Xcode 4におけるiOS Frameworkの作成方法」という記事を参考にした上で、私が実際にやってみた中で気づいた点などを補って書いています。リンク先の記事の方が構造などの説明が分かりやすく丁寧に書いてあるので、ぜひあわせて読んでください。
追記:Apple Mach-O Libker Error
上記の方法でつくったライブラリを他のプロジェクトで再利用しようとしたら「Apple Mach-O Linker Error」が大量に出てコンパイルが通らなくなってしまった。
なんでだろうって悩んでいたんだけど、一覧を見ると特定のFrameworkに収録されているクラス名ばかりが並んでいる。この画像の場合は、自作のframeworkで使ってるframework(CoreData.framework)を、利用するプロジェクトにも取り込んでおけばコンパイルが通った。
追記:unrecognized selector sent to class ナントカ
カテゴリを使ってクラスを拡張している場合、拡張したメソッドを呼び出すと「unrecognized selector sent to class 0xabcdefg」というような実行時エラーが出る。これを解決するには、ライブラリを使うプロジェクト(アプリケーションのプロジェクト)のBuild Settings内の「Other Linker Flags」に「-ObjC」と「-all_load」を追加すればよい。
詳しくはEZ-NET: 静的ライブラリ内のカテゴリされた関数が呼び出せない – Xcode4を参照。
追記:ディレクトリ名の環境依存を直す
今まで書いた方法だと、ディレクトリ名が環境依存になってしまうらしい。以下のブログ記事に、当ページのやり方を踏まえた上での修正方法が書かれていたので参照。
- 坂の街より : iOS: 自作Frameworkを作る
- http://blog.livedoor.jp/baradagi/archives/67648663.html
はじめまして。
iOS でのフレームワーク作成についてこのページを基に作成してみました。大変参考になり、フレームワークを作成してみました。
ただ、1点不明な点があり、もしご存知でしたら御教授頂きたいのですが…。
フレームワークで nib ファイルなどを使用してViewControllerを表示するようなフレームワークを作成し、テストアプリからフレームワーク内のメソッドをコールすることにより、ViewControllerが表示される、ということを実現したいのですが、
フレームワーク内のメソッドで、nib をベースに UIViewController の派生クラスを呼び出しているのですが、フレームワーク内でコールしている initWithNibName:bundle 部分で、Could not load NIB … となってしまいました。
他のサイトなどを見た感じだと、フレームワークで使用している nib などのリソースファイルを、フレームワークを使用するアプリ側に追加する必要があるような感じなのですが…。
その場合は、どのように指定(追加?)するのが正しいでしょうか?
もしご存知でしたら、御教授頂けると大変助かります。
そのあたりの実装はやったことがないので分からないのですが、例えば、
・Resoucesフォルダの中にnibファイルを移動する
・TARGETSのプロジェクト名(記事の例だとTestFramework)の「Build Phases」タブを開いて、右下の「Add Build Phase」を押してメニューの「Add Copy Bundle Resources」を選ぶ
・増えた「Copy Bundle Resources」の中の「+」を押してnibファイルを一覧に追加する
といった手順を加えた上でコンパイルしたらできそうな気がします。
時間のある時に検証してみますね。