[iOS]Frameworkについて | Cocoa練習帳

[iOS]Frameworkについて

iOSでは開発者が独自のフレームワークを作成することは推奨されていない。フレームワークの利用の利点となる動的リンクやバージョン管理の恩恵を受けることはできないが、外部に対してライブラリを提供する方法としてフレームワークを選択することには利点があるので、試してみることにした。

最も簡単なフレームワークのディレクトリ構成は以下のとおり。


MyFramework.framework/
    Frameworks   -> Versions/Current/Frameworks
    Headers      -> Versions/Current/Headers
    MyFramework  -> Versions/Current/MyFramework
    Resources    -> Versions/Current/Resources
    Versions/
        A/
            Frameworks/
                OtherFramework.framework
            Headers/
                MyHeader.h
            MyFramework
            Resources/
                English.lproj/
                    Documentation
                    InfoPlist.strings
                Info.plist
        Current  -> A

Frameworksに参照しているフレームワークを配置する。

Headersに公開するヘッダーファイルを配置する。

MyFrameworはライブラリ本体。

Resourcesはリソースを格納しているディレクトリ。

DocumentationにHTMLまたはPDFの文書を配置する。

Info.plistにフレームワークの設定を記述する。

iOSではXcodeにフレームワーク用にひな形が用意されていないため、このディレクトリ構成になるように自分で対応を記述することになる。

それでは、著者がGitHubで公開しているグラフ描画プログラムSimpleChartをフレームワーク化してみよう。

SimpleChartは一つのプロジェクトファイルに、グラフ描画ビューとサンプルコードが納められているが、グラフ描画ビューはフレームワーク用プロジェクトに、サンプルコードも別プロジェクトに分けて、それをまとめるワークスペースの用意することにした。このプロジェクトの分割とワークスペースの用意については、フレームワーク化とは関係ないので説明は割愛する。

まずは、フレームワーク用プロジェクトを生成する。Static Libraryのひな形を選べばよい。

プロジェクトの生成

フレームワークのディレクトリ構成を生成しやすいようにする為、HeadersとClasses、Resourcesのディレクトリを用意して、ソースファイルを移動する。

ディレクトリ構成

フレームワーク用のProperty ListをResourcesディレクトリ内に、ファイル名はInfo.plistで生成する。

Info.plistの内容は以下のとおり。


<?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>
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
</dict>
</plist>

フレームワーク用のターゲットを追加する。追加するのはAggregateという種類だ。

Aggregate

ターゲットの名前は、分かりやすいように例えば、SimpleChart.frameworkとフレームワーク用だと分かる名前がよいと思う。

追加したターゲットのBuild Phasesにフレームワーク作成用のスクリプトを追加する。

addscript

script

スクリプトの内容は、以下のとおり。

#!/bin/sh
 
# ==============================
# 変数設定
# ==============================
#FRAMEWORK_NAME=$(/usr/libexec/PlistBuddy -c "Print CFBundleName" ${INFOPLIST})
FRAMEWORK_NAME='SimpleChart'
INFOPLIST="${FRAMEWORK_NAME}/Resources/Info.plist"
BUILD_TARGET_NAME=${FRAMEWORK_NAME}
#FRAMEWORK_BUILD_CONFIGURATION= ${CONFIGURATION}
FRAMEWORK_BUILD_CONFIGURATION="Release"
FRAMEWORK_VERSION_NUMBER=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${INFOPLIST})
FRAMEWORK_VERSION=A
FRAMEWORK_BUILD_PATH="Framework"
FRAMEWORK_DIR="${FRAMEWORK_BUILD_PATH}/${FRAMEWORK_NAME}.framework"
PACKAGENAME="${FRAMEWORK_NAME}.${FRAMEWORK_VERSION_NUMBER}.zip"
 
# ==============================
# ビルド
# ==============================
echo xcodebuild -configuration ${FRAMEWORK_BUILD_CONFIGURATION} -project ${PROJECT_NAME}.xcodeproj -target ${BUILD_TARGET_NAME} clean
xcodebuild -configuration ${FRAMEWORK_BUILD_CONFIGURATION} -project ${PROJECT_NAME}.xcodeproj -target ${BUILD_TARGET_NAME} clean
echo xcodebuild -configuration ${FRAMEWORK_BUILD_CONFIGURATION} -project ${PROJECT_NAME}.xcodeproj -target ${BUILD_TARGET_NAME} -sdk iphonesimulator${IPHONEOS_DEPLOYMENT_TARGET}
xcodebuild -configuration ${FRAMEWORK_BUILD_CONFIGURATION} -project ${PROJECT_NAME}.xcodeproj -target ${BUILD_TARGET_NAME} -sdk iphonesimulator${IPHONEOS_DEPLOYMENT_TARGET}
[ $? != 0 ] && exit 1
echo xcodebuild -configuration ${FRAMEWORK_BUILD_CONFIGURATION} -project ${PROJECT_NAME}.xcodeproj -target ${BUILD_TARGET_NAME} -sdk iphoneos${IPHONEOS_DEPLOYMENT_TARGET}
xcodebuild -configuration ${FRAMEWORK_BUILD_CONFIGURATION} -project ${PROJECT_NAME}.xcodeproj -target ${BUILD_TARGET_NAME} -sdk iphoneos${IPHONEOS_DEPLOYMENT_TARGET}
[ $? != 0 ] && exit 1
 
# ==============================
# ディレクトリ作成
# ==============================
[ -d "${FRAMEWORK_BUILD_PATH}" ] && rm -rf "${FRAMEWORK_BUILD_PATH}"
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
ln -s ${FRAMEWORK_VERSION} ${FRAMEWORK_DIR}/Versions/Current
ln -s Versions/Current/Headers ${FRAMEWORK_DIR}/Headers
ln -s Versions/Current/Resources ${FRAMEWORK_DIR}/Resources
ln -s Versions/Current/${FRAMEWORK_NAME} ${FRAMEWORK_DIR}/${FRAMEWORK_NAME}
 
# ==============================
# framework作成
# ==============================
lipo -create \
build/${FRAMEWORK_BUILD_CONFIGURATION}-iphoneos/lib${FRAMEWORK_NAME}.a \
build/${FRAMEWORK_BUILD_CONFIGURATION}-iphonesimulator/lib${FRAMEWORK_NAME}.a \
-o "${FRAMEWORK_DIR}/Versions/Current/${FRAMEWORK_NAME}"
 
cp -Rf ${BUILD_TARGET_NAME}/Headers/* ${FRAMEWORK_DIR}/Headers/
cp ${BUILD_TARGET_NAME}/Resources/* ${FRAMEWORK_DIR}/Resources/
cp ${INFOPLIST} ${FRAMEWORK_DIR}/Resources/
cd ${FRAMEWORK_BUILD_PATH}
chmod -fR 777 "${FRAMEWORK_NAME}.framework"
zip -ry ${PACKAGENAME} $(basename $FRAMEWORK_DIR)
 
# End Of File

今回、既存のプロジェクトからソースコードを抜き出してフレームワーク化したということで、フレームワーク化によって、既存のソースコードにも手を加えることにある。例えば、ヘッダーファイルのインクルードも、フレムワーク名/ヘッダーファイル名というパスとなる。

#import <SimpleChart/SimpleChartView.h>

この程度の対応だけで大丈夫だと思うが、今回、既存のプロジェクトからソースファイルをフレームワークのプロジェクトに移動したからだと思うが、InterfaceBuilderが、フレームワーク内のクラスの認識に失敗するという問題が発生した。

対応方法を試行錯誤した結果、以下のコードを埋め込むとうまくいったのだが、少し気持ちが悪い。

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self _init];
    
    /* InterfaceBuilderでフレームワークのクラスを認識できない問題の対処 */
    [SimpleChartView class];
	

継続して本格対処方法の調査を行っている。

ソースコード GitHubからどうぞ。
https://github.com/murakami/SimpleChart - GitHub
関連情報 Framework Programming Guide
Cocoaの日々:[Mac][iOS] Frameworkとは?
[iPhoneアプリ開発]自作Frameworkをつくる
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)