【Swift】Frameworkを作成する

# １．はじめに

　いくつかプロジェクトを作成しているとコードを使いまわしたいことが出てくると思います。そんな時に便利なのがフレームワーク。一度作成したら、各プロジェクトにインポートするだけで使えるようになります。
　フレームワークの作成、デバッグ方法をまとめたいと思います。


# ２．環境
 Xcode Version 9.2
 Swift Version 4.0.3

# ３．前提
 当記事ではプロジェクト名を以下の通りにしています
 フレームワーク　：　Sally
 フレームワークを使用するプロジェクト　：　Jack

# ４．フレームワークの作成

File > New > Projectから「Cocoa Touch Framework」を選択。
「Product Name」を「Sally」にして作成。

「Sally」フォルダの中にファイルを追加します。このファイルにフレームワークとして作成したい機能を実装します。
File > New > File から「Cocoa Touch Class」を選択。
クラス名を「Sally」にします。
フォルダ構成はこのようになります。
<img width="277" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/93b4f5b3-e0ed-4afa-8265-67f5df744140.png">

以下のように作成したクラスに機能を実装していきます。
ポイントはクラス、メソッドともに `public` にすることです。
 `public` にすることでフレームワーク外からアクセスできるようにします。

```swift
// Sally.swift

public class Sally: NSObject {
    public func sallyMethod() {
        print("Sally().sallyMethod is called !!")
    }
}
```


ビルドします。ビルドすることでプロジェクトで使用できる状態のフレームワークを生成します。
ポイントはシミュレーター用と実機用で別々に作成する必要があることです。
シミュレーター、実機共通のフレームワークの作成も後々説明しますが、その場合も一度それぞれのフレームワークを作成する必要があります。

まずは実機用のフレームワークを作成します。
ターゲットに実機か「Generic iOS Device」を選択。
ビルド前はまだファイルがないので「Sally.framework」が赤字になっています。
<img width="1272" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/06f78487-cef0-41ff-7658-b9dbbd1a7036.png">

「command + B」 でビルドします。
ビルドに成功すると「Sally.framework」が黒字になります。
<img width="1072" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/3c686d7f-e385-a9fa-60b9-c0701bb5dcc2.png">

次にシミュレーター用のフレームワークを作成します。
ターゲットにシミュレーター（iPhone X でもiPhone 8 Plusでもなんでもいいです）を選択。
実機用を作成した時と同様に「command + B」でビルドします。


作成したフレームワークは「Sally.framework」を選択して「Show in Finder」で確認することができます。
<img width="958" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/9bde285f-2e20-13eb-6dfe-98285c2f0a75.png">

「Debug-iphoneos」というフォルダ内のフレームワークが実機用、「Debug-iphonesimulator」というフォルダ内がシミュレーター用です。
<img width="619" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/75ee754a-93d7-90b3-7c38-701aec469781.png">

# ５．フレームワークを使用する
ここまでで作ったフレームワークを使ってみます。
File > New > Project を選択し、「Single View App」でプロジェクトを新規作成します。
プロジェクト名は「Jack」にしました。

プロジェクトに先ほど作成したフレームワークをインポートします。
「Jack」をシミューレーターで確認する場合は「Debug-iphonesimulator」フォルダ内のフレームワークを、実機で確認する場合は「Debug-iphoneos」フォルダ内のフレームワークをドラッグ&ドロップでインポートします。
<img width="1072" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/fcee24e0-00d2-8fc4-adc6-35710ed335e8.png">

「Copy Items if needed」をチェックしない場合、フレームワークのコピーは取り込まれずリンクがされます。
この場合、「Build Setting」の「Search Paths」> 「Framework Search Paths」に適切なパスが設定されていないとビルドできません。
<img width="1275" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/075c386d-655d-708f-f785-e8fde0ac8be8.png">

インポートしたフレームワークのパスを設定します。
<img width="1275" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/c67c61fe-b433-351e-af53-ace666b60acc.png">

次にインポートしたフレームワークを「Embedded Binaries」にドラッグします。
この時、「Linked Frameworks and Libraries」にも追加されてしまうので、削除します。
<img width="1275" alt="スクリーンショット 2018-01-06 13.28.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/124577/3fcd3c3d-87b3-7c6d-b10e-5915f736c50d.png">

それではフレームワークの機能を使ってみます。
`Sally` フレームワークをインポートし、 `viewDidLoad` 内でフレームワークのメソッドを実行します。

```swift
//ViewController.swift

import UIKit
import Sally

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        Sally().sallyMethod()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
```

ランしてみましょう。
コンソールに「Sally().sallyMethod is called !!」と表示されれば成功です！


# ６．シミュレーターと実機の共通のフレームワークを作成する
先ほど作成したフレームワークはシミュレーター用と実機用とで別々でした。フレームワークを使用するプロジェクトをどちらで確認するかによってインポートするフレームワークを変える必要があります。
そこで、共通のフレームワークを作成することにします。

作業するプロジェクトは再び「Sally」です。
「Sally」に新しいターゲットを追加します。
File > New > Target から「Aggregate」を選択。
<img width="1072" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/30d52eab-9e4a-548f-668a-ee41839799eb.png">

プロジェクト名を「Sally-Universal」にしました。

次に「Sally-Universal」の「Build Phases」で「＋」のアイコンから「New Run Script Phase」を選択。
<img width="958" alt="スクリーンショット 2018-01-06 13.43.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/124577/a9b871b8-0dc2-a1a5-5813-cffe55622372.png">

スクリプトをコピペします。
<img width="1072" alt="スクリーンショット 2018-01-06 13.46.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/124577/eea0a83d-dd25-15ba-c774-19b0a817ba5e.png">

```スクリプト
######################
# Options
######################

REVEAL_ARCHIVE_IN_FINDER=false

FRAMEWORK_NAME="${PROJECT_NAME}"

SIMULATOR_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${FRAMEWORK_NAME}.framework"

DEVICE_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FRAMEWORK_NAME}.framework"

UNIVERSAL_LIBRARY_DIR="${BUILD_DIR}/${CONFIGURATION}-iphoneuniversal"

FRAMEWORK="${UNIVERSAL_LIBRARY_DIR}/${FRAMEWORK_NAME}.framework"


######################
# Build Frameworks
######################

xcodebuild -workspace ${PROJECT_NAME}.xcworkspace -scheme ${PROJECT_NAME} -sdk iphonesimulator -configuration ${CONFIGURATION} clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphonesimulator 2>&1

xcodebuild -workspace ${PROJECT_NAME}.xcworkspace -scheme ${PROJECT_NAME} -sdk iphoneos -configuration ${CONFIGURATION} clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos 2>&1

######################
# Create directory for universal
######################

rm -rf "${UNIVERSAL_LIBRARY_DIR}"

mkdir "${UNIVERSAL_LIBRARY_DIR}"

mkdir "${FRAMEWORK}"


######################
# Copy files Framework
######################

cp -r "${DEVICE_LIBRARY_PATH}/." "${FRAMEWORK}"


######################
# Make an universal binary
######################

lipo "${SIMULATOR_LIBRARY_PATH}/${FRAMEWORK_NAME}" "${DEVICE_LIBRARY_PATH}/${FRAMEWORK_NAME}" -create -output "${FRAMEWORK}/${FRAMEWORK_NAME}" | echo

# For Swift framework, Swiftmodule needs to be copied in the universal framework
if [ -d "${SIMULATOR_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/" ]; then
cp -f ${SIMULATOR_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/* "${FRAMEWORK}/Modules/${FRAMEWORK_NAME}.swiftmodule/" | echo
                                                                      fi
                                                                      
                                                                      if [ -d "${DEVICE_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/" ]; then
                                                                      cp -f ${DEVICE_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/* "${FRAMEWORK}/Modules/${FRAMEWORK_NAME}.swiftmodule/" | echo
                                                                      fi
                                                                      
                                                                      ######################
                                                                      # On Release, copy the result to release directory
                                                                      ######################
                                                                      OUTPUT_DIR="${PROJECT_DIR}/Output/${FRAMEWORK_NAME}-${CONFIGURATION}-iphoneuniversal/"
                                                                      
                                                                      rm -rf "$OUTPUT_DIR"
                                                                      mkdir -p "$OUTPUT_DIR"
                                                                      
                                                                      cp -r "${FRAMEWORK}" "$OUTPUT_DIR"
                                                                      
                                                                      if [ ${REVEAL_ARCHIVE_IN_FINDER} = true ]; then
                                                                      open "${OUTPUT_DIR}/"
                                                                      fi
```

ターゲットを「Sally-Universal」にしてビルドをしてフレームワークを作成します。
<img width="1287" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/ed3a209f-61ea-ee9f-c7ce-131b5fec013c.png">

先ほど別々のフレームワークを作成した時と同様に「Show in Finder」で作成したフレームワークを確認します。
「Debug-iphoneuniversal」というフォルダに作成されたことが確認できます。
<img width="615" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/6efef96c-a200-c000-caa5-69a527a3f422.png">

このフレームワークを「Jack」プロジェクトにインポートすることでシミュレーターでも実機でも同一のフレームワークが使えるようになります。

# ７．フレームワークとアプリを一緒に作成する
後々別のアプリでも使用することが想定される場合、アプリと同時にフレームワークを作成してデバッグしたい。そんなことができたら便利！！
今回は「Jack」プロジェクトで同時に「Sally」フレームワークを作成します。

まず、「Jack」プロジェクトから「Sally」フレームワークを削除しておきます。

「Add Files to "Jack"...」を選択。
<img width="432" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/3390f3a8-a752-5af5-26e1-26f7eb916bc2.png">

「Sally.xcodeproj」を選択すると、こんな感じのフォルダ構成になります。
先ほど確認した場所とは違う場所にフレームワークを格納するようで、「Sally.framework」が赤字になっています。
<img width="274" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/3a348154-bfd4-513d-cfbd-0da1c78a092d.png">

ターゲットを「Sally」にしてシミュレーター、実機用それぞれビルド。
ターゲットを「Sally-Universal」にしてビルド。
<img width="1275" alt="スクリーンショット 2018-01-06 14.18.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/124577/605ac2d8-299a-374d-7292-815a40446d1f.png">

ここで新しく作成したUniversalな「Sally」フレームワークを「Jack」プロジェクトに取り込みます。
「Embedded Binaries」への追加と「Build Setting」の「Framework Search Paths」への追加をお忘れなく。


さて、フレームワークの更新をしてみます。
`Sally` クラスに `sallyMethod2` を追加します。

```swift
// Sally.swift

import UIKit

public class Sally: NSObject {
    public func sallyMethod() {
        print("Sally().sallyMethod is called !!")
    }
    public func sallyMethod2() {
        print("Sally().sallyMethod2 is called !!")
    }
}

```

これまでと同様に実機用ビルド、シミュレーター用ビルド、Universal用ビルドを実行します。
「Jack.swift」の `Jack.swift` の `viewDidLoad` に `Sally().sallyMethod2()` を追加します。

「Jack」プロジェクトでフレームワークの機能を使用します。

```swift
// Jack.swift

import UIKit
import Sally

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        Sally().sallyMethod()
        Sally().sallyMethod2()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

```

`Sally` クラスの　`sallyMethod2` でブレークポイントを設定してから、「Jack」をランします。
<img width="1221" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/e95f602a-fb35-3693-1172-7865c667607e.png">

設定したブレークポイントで止まれば成功！！
<img width="1221" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/29fd7f1d-968e-4047-4e4b-0c4a1afe890b.png">


これでアプリもフレームワークも同時に作成できますね。


# ８．最後に
フレームワークの作成が終わったら、「Sally.xcodeproj」を削除します。
<img width="464" alt="スクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/124577/c0abddd1-5312-7d77-0b80-cac4f254ef58.png">

「Sally.xcodeproj」単体で開いてビルドしてフレームワークを生成します。
生成したフレームワークを「Jack」プロジェクトに取り込めば、「Jack」プロジェクトから「Sally」プロジェクトを削除した上で、フレームワークを使用することができます。


# ９．課題というか疑問
生成したフレームワークは「Debug-○○○」というフォルダに格納されます。
「Debug」。。。ものすごく気になります。
アプリをリリースする際はこのフレームワークもリリース用にビルドしなければならないのかどうか。
今後試してみたいと思います。


# １０．参考
[[Xcode6] SwiftでCocoa Touch Frameworkを作る](http://mushikago.com/i/?p=5521 "[Xcode6] SwiftでCocoa Touch Frameworkを作る")
[[Xcode6] Universal Frameworkを作る](http://mushikago.com/i/?p=5646 "[Xcode6] Universal Frameworkを作る")
[[Xcode6] iOSアプリプロジェクト内にframeworkプロジェクト](http://mushikago.com/i/?p=5671 "[Xcode6] iOSアプリプロジェクト内にframeworkプロジェクト")
[Xcode7.3 & SwiftでつくるCocoaTouch Framework（作成編）](https://qiita.com/mfks17/items/b1ae615744e311a7f9eb "Xcode7.3 & SwiftでつくるCocoaTouch Framework（作成編）")




間違っている点や改善点などありましたら、ぜひフィードバックをお願いします。