Darwin-style フレームワーク内に他ライブラリ・フレームワークへの依存関係を記述する

皆さんこんばんは。

最近腰をやってしまったDayBySayです。

本日はDarwin-styleのフレームワーク内に依存関係を記述する方法についてまとめました。

概要

Mac OSXやiOSで利用されるDarwin-styleのフレームワークですが、サードパーティの誰かがフレームワークを提供する場合に、プラットフォームが提供するライブラリやフレームワークに対して依存関係を持つことが有ります。

例えば、Dropboxが提供しているSDK(フレームワーク)をダウンロードして利用する場合、こちらに書いてあるとおりSecurity.frameworkなどのフレームワークへのリンクをSDK利用側が明示的に行う必要があり、ちょっとめんどくさいです。

一方で、Googleが提供するAdMobのSDKダウンロードして利用する場合、フレームワークのリンクを明示的に行う必要はありません。

この違いは、ClangLink declarationを使っているかどうかになります。

Link declaration

先日の記事で書いたClangのモジュール機能の中に、Link declarationという依存関係を記述する機能が存在します。

この機能を使うことでフレームワーク側に依存関係を記述し、フレームワーク利用側の負担を減らすことが出来ます。

機能の説明は下記のようにされています。

Link declaration

A link-declaration specifies a library or framework against which a program should be linked if the enclosing module is imported in any translation unit in that program.

説明の通り、モジュールがライブラリ(共有ライブラリを指しています)とフレームワークに対してリンクを行うべきかを定義するものです。

記述方法は下記のような感じです。

link-declaration:

link framework opt string-literal

optframework記述に対してかかっています。

どういうことかというと、ライブラリ、例えばlibxml2へのリンクは

link xml2

であり、フレームワーク、例えばAVFoundation.frameworkへのリンクは

link framework AVFoundation

となる、ということです。

これらをmodule.modulemapファイルに記述することでフレームワーク側で依存関係の記述を吸収することが出来ます。

ちなみに先述のAdMob SDKmodule.moduemapはこんな内容になっています。

framework module GoogleMobileAds {
  umbrella header "GoogleMobileAds.h"

  export *
  module * { export * }

  link framework "AdSupport"
  link framework "AudioToolbox"
  link framework "AVFoundation"
  link framework "CoreGraphics"
  link framework "CoreMedia"
  link framework "CoreTelephony"
  link framework "EventKit"
  link framework "EventKitUI"
  link framework "Foundation"
  link framework "MessageUI"
  link framework "StoreKit"
  link framework "SystemConfiguration"
  link framework "UIKit"

// ~~ 略 ~~

}

実際に使ってみる

実際に使ってみたリポジトリこちらになります。

実際の処理は下記のような感じになっています

#import "HOGEFugaService.h"


@import AdSupport;

@implementation HOGEFugaService

// 中略

+ (NSString *)ADID {
    return [[ASIdentifierManager sharedManager] advertisingIdentifier].UUIDString;
}

@end

今回は上記のようにASIdentifierManageradvertisingIdentifierメソッドフレームワーク内で利用しているため、このプロジェクトで作成されるHOGEFuga.frameworkAdSupport.frameworkへの依存を持つこととなります。

依存関係を記述するためのmodule.modulemapは下記のようになっています。

framework module HOGEFuga {
    umbrella header "HOGEFuga.h"

    export *
    module * { export * }

    link framework "AdSupport"
}

link framework "AdSupport"の記述を追加しました。

動かしてみる

HOGEFuga.frameworkを利用する側の実装は下記のようにしています。

import UIKit
import HOGEFuga

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        HOGEFugaService.show()
        NSLog("%@", HOGEFugaService.ADID())
    }

// 略
}

また、プロジェクトに対してAdSupport.frameworkのリンクを明示的に記述していません。

f:id:DayBySay:20160223220225p:plain

この状態でコンパイルできれば成功です。

Ctrl+Rでシミュレータ実行を行うと・・

f:id:DayBySay:20160223220345p:plain

コンパイル成功!そしてシミュレータのIDFAが出力されているので、動作確認も完了です!

まとめ

フレームワークを外部に対して提供する場合は、モジュールとして利用できる形にし、フレームワークへの依存を記述しておくと便利である。

CocoaPodsを使う場合リンク機能があるのでモジュールにしなくても済む場合があります

Unityプラグインとしてフレームワークを提供する場合、モジュール化とLink declarationが有効に働く気がします。