SwiftでFramework(ライブラリ)を作成、利用する方法

投稿者: | 2019年8月31日

Swiftで自前ライブラリを作る方法(Xcode利用)

Swiftでは関数やクラス、構造体などを詰め込んだFrameworkを作ることができる。Swiftでは関数が「一級市民」なのでいわゆる「メンバ関数」のような事をしなくてもよい事も補足しておく。

iOSのアプリは巨大化しているので、コードの再利用が望ましい。そしてそれはFrameworkにするのがよいだろう。例によって最新の情報がなかったので自分でやってみた。

また複雑なサンプルだと本当に必要なことがなんなのか分からなくなるのでごく簡単なアプリケーションを作成する。基本的な流れはmacOS等でも同じだろう。

大まかな手順

大まかに言って次のような手順を取る。

  1. Frameworkに詰め込みたいものを含む動くコードの準備

  2. 対象をFrameworkとしてビルドする(アクセス制限に注意)

  3. 元ファイルのFrameworkに組み込んだ部分をコメントアウト

  4. ビルドするとエラーが出るので先ほどビルドしたものを組み込む
    A. プロジェクトにコピー
    B. リンクを張る
    C. importをソースコードに書き込む
    これで元と同じように動作するはず。

  5. コメントアウトしていた部分を削除
    これを忘れないように、そして余計な部分を削除してしまうこともあるのでテストを忘れないこと。

ただし、UI上に表示させるようなControlをFrameworkにする際はもう一工夫しないとXcodeのInterface Builderリアルタイムに更新されない。それについてはまた別の項で書く予定。

またCocoaPodsを利用したより大きなプロジェクト構成法については項を改めて記す。

詳細な手順

簡単なプログラムを作って、詳細な手順を見ていこう。

作成対象の概要:Xcode10の場合を想定する。固定の挨拶文に現在の日付を加えてラベルに表示するという簡単なプログラムだ。

手順1:Frameworkに詰め込みたいものを含む動くコードの準備

XcodeでSingle View Appを作る。ここではHelloDateTimeというプロジェクトを作っている。

Labelを作ってViewControllerにリンクを張っておく。

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var myLabel: UILabel!// リンク
    let greetText = "Hello"//挨拶文

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        myLabel.text = datePlus(helloString: greetText)//呼び出す関数
    }

}

そして挨拶文に日付を加えたStringを返却する関数を別ファイルに作っておく。

import Foundation

func datePlus(helloString: String) -> String {
    let format = DateFormatter()
    format.dateFormat = "yyyy-MM-dd"

    let date = Date()

    return helloString + " on " + format.string(from: date) + "."
}

Java等と違い、この関数、クラスのメンバ関数のようなものではないことにも注目。これが「一級市民」と呼ばれる理由の一つであろう。

手順2:対象をFrameworkとしてビルドする

これは独立したプロジェクトでやった方が良いと思う。なぜなら別のプロジェクトでも使いたいというのが動機だからである。

XcodeでNew Projectを開き、下の方にあるCocoa Touch Frameworkを選んでFramework用のプロジェクトを作成する。

ファイル追加でFrameworkの中にSwiftのファイルを追加する。

中身は以下のようなもの。

import Foundation

public func datePlus(helloString: String) -> String {//publicが必要
    let format = DateFormatter()
    format.dateFormat = "yyyy-MM-dd"

    let date = Date()

    return helloString + " on " + format.string(from: date) + "."
}

気をつけなくてはいけないのはアクセスコントロールがpublicにしてあること。SwiftではDefaultがinternalなので外部利用するために必要なのである。

また構造体やクラスの場合はダミーのInitializerをpublicで入れるのを忘れないようにする。

    public init() {
        // This initializer intentionally left empty
    }

これがないとビルドが通らない。もちろん、すでにExplicitなInitializerがあればそれをpublicにすればよい。

Frameworkの場合、ビルドする際にターゲットをGeneric iOS Deviceにしておく。そうしないと特定のデバイスでしか動かないモジュールが出来てしまう。

間違いがなければビルドも上手く通り、Productsの中にできている.frameworkファイルを元プロジェクトにFinderでドラッグする。(作業は手順4の第一段階で実施)

手順3:元ファイルのFrameworkに組み込んだ部分をコメントアウト

重複定義になってしまうので、既存の部分をまずはコメントアウトしておく。Finishすると下のようになる。

手順4:先ほどビルドしたFrameworkを組み込む

これはいくつかの段階を踏まなくてはいけない。

第一段階:Frameworkファイルのプロジェクトへの読み込み

ドラッグする場所はプロジェクトプロジェクトナビゲーターで.xcodeprojの下。この時、開くダイアログで

一番上のチェックボックスをチェックしておくこと。

第二段階:TargetのEmbedded Binaries, Linked Frameworks and LibrariesにFrameworkファイルをあてがう

第一段階が終わった時点でプロジェクトマネージャーで一番上のアイコンを選択すると中央に出てくるエディタで下の方にあるLinked Frameworks and Librariesの中に先ほど追加したFrameworkファイルが現れている。これを一旦マイナスアイコンで削除する。

その上で、第一段階の最後でリンクしたファイルをドラッグしてEmbedded Binariesに追加するとLinked Frameworks and Librariesの中にも同じものができる。

第三段階:Frameworkファイル内のオブジェクトを必要とするソースコードでimportを記述

追加したFrameworkの中にある関数などを利用するソースコードでインポートする。補完が働くので安心してよい。

import UIKit
import DatePlusLib

class ViewController: UIViewController {

    @IBOutlet weak var myLabel: UILabel!
    let greetText = "Hello"

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        myLabel.text = datePlus(helloString: greetText)
    }

}

import追加前だとi以下のようなエラーが出る。

追加後はでない。

手順5:コメントアウトしていた部分を削除

不要になるので削除する。もちろん、十分テストを行ってからすべきだろう。

以上でFrameworkを作成、利用できるようになる。Frameworkの中で更新があったら、プロジェクト毎に手順4を繰り返す必要がある。

まとめ

やや面倒だが、ソースコードのリユースを進める上で避けられないFramework作成を詳述した。業務アプリケーションをプロトタイプから派生させていく際には不可欠なプロセスだろう。

今回は関数でやったが、クラスや構造体でも同じプロセスである。

コメントを残す