Embedded Frameworkを導入してXcodeのビルドパフォーマンスをあげる

iOSエンジニアの遠藤です。 先日iQONで、Xcodeのビルドパフォーマンス改善の一環としてEmbedded Frameworkの導入を行いました。 今回は、そのEmbedded Frameworkの導入について紹介したいと思います。

Embedded Frameworkとは?

Embedded FrameworkはiOS 8・Xcode 6から追加された機能です。 アプリのコードを分割してFrameworkとして扱うことができます。

Embedded Frameworkを導入することのメリット

コードを分割してFramework化することで以下のメリットがあります。

  • コードのターゲットが分かれるため、差分コンパイルされビルドパフォーマンスが向上する
  • App Extentionsを持つアプリの場合、メインターゲットとExtension間でコード共有することができる
  • Frameworkに分けることで、依存関係がシンプルになる
  • Frameworkごとにテストを書くことができる

導入方法

Xcode 7.3.2 Swift 2.2での導入方法です。

Frameworkの作り方

Embedded Frameworkの導入は簡単で、Xcodeのツールバーから「File」 → 「New」 → 「Target」を選択すると、以下の画面が表示されます。

f:id:vasilyjp:20160915145455p:plain

そこから「Framework & Library」 → 「Cocoa Touch Framework」を選択すると新しくターゲットが追加されます。

f:id:vasilyjp:20160915145525p:plain

新しくできたTargetに分けたいコードを追加していきます。

Frameworkの使い方

Frameworkとして扱うメソッドやclass、変数にはメインターゲットからアクセスできるように public 修飾子をつけてください。

import Foundation
import Alamofire

public class Util {

    public var name: String?

    public init() {

    }
}

使用したいFrameworkをimportするだけで使えるようになります。

import UIKit
import SampleFramework

class ViewController: UIViewController {
  
    override func viewDidLoad() {
        super.viewDidLoad()
        let util = Util()
        util.name = "hoge"
    }
}

Embedded Frameworkの導入はすごく簡単です。

しかし、Framework内でライブラリを使う場合はいろいろとハマる部分がありました。 その対応方法をいくつか紹介したいと思います。

CocoaPodsでライブラリを使う場合

Embedded Frameworkだけでしか利用していないライブラリでも、メインターゲットにもインストールする必要があります。 Podfileにターゲットごとに同じライブラリ名を記述するのは手間なので、iQONではabstract_targetでまとめています。

// Podfile

abstract_target 'All' do # Targetとかぶらなければ、名前の文字列は何でも大丈夫です

  pod 'Alamofire'

  target 'SampleApp' do # AlamofireとSVProgressHUDがインストールされる
    pod 'SVProgressHUD'
  end

  target 'SampleFramework' do # Alamofireがインストールされる
  end
end

Carthageでライブラリを使う場合

CarthageもCocoaPodsと同様にFrameworkとメインターゲットどちらにも、ライブラリを設定する必要があります。 メインターゲットのEmbedded BinariesにFrameworkで利用しているライブラリを設定します。

f:id:vasilyjp:20160915145823p:plain

Library not loadedでクラッシュする場合

CocoaPodsでインストールしたライブラリがLibrary not loadedでクラッシュしてしまう時は「Build Phases」を確認してみてください。

おそらく、インストールしたライブラリをアプリ内に組み込むための Run Script が存在していないためにクラッシュしている可能性があります。 [CP] Embed Pods Frameworks という名前の Run Script は本来CocoaPodsが自動的に追加するもので、なぜそのRun Scriptが存在しないのかは詳しくは分かりませんが、追加すればクラッシュしなくなります。

f:id:vasilyjp:20160915145947p:plain

Framework not found Pods_**でビルドが通らない場合

[Build Phases] -> [Link Binary With Libraries]を確認してみてください。 エラーで表示されているFrameworkを削除してください。

以前からCocoaPodsでライブラリを管理しているプロジェクトだと、Pod_**.frameworkというものがLink Binary With Librariesに設定されています。 しかし、Podfileをabstract_targetで書くようにした場合、新しくPods-**-**.frameworkというものが設定されるので、Pod_**.frameworkが不要になります。 不要なものが設定されているために、ビルドに失敗していました。

f:id:vasilyjp:20160915150037p:plain

導入した結果

まだ、一部分のコードしかFrameworkにできていないので、差分コンパイルによるビルドパフォーマンスの向上はあまり感じることはできていません。

しかし、frameworokに分けることでコードの依存関係が明確化されたのは良かったと思います。 今までコード感の依存関係がごちゃごちゃになっていたのが明確化されたので、コードの設計を見直すきっかけになりました。

まとめ

Embedded Frameworkを導入した話でした。

Frameworkを作ること自体はすごく簡単です。 Framework内でライブラリを使用する際にハマるポイントがいくつかありますが、慣れてしまえばすごく便利でメリットが多いのでおすすめです。

まだiQONは一部しか対応をできていませんが、随時Framework化してビルドパフォーマンスの向上を目指していきたいと思います。

最後に

VASILYでは一緒にiQONを開発してくれる仲間を募集しています。少しでもご興味のある方は以下のリンク先を御覧ください。

www.wantedly.com