Help us understand the problem. What is going on with this article?

Swift で Framework を作ってみる使ってみる

More than 3 years have passed since last update.

Framework

アプリを横断して同じコードやViewにViewControllerクラスを使いまわしたい時は時折あると思います。旧来は ソースそのものを使いまわしたり、static library を作って使いまわしていた事と思いますが、Swift時代では、そもそも Swift のコードは static library が作れなかったり、ViewController だけでなく、.xib (.nib) や storyboard を使いまわそうとすると、static library では無理があります。

iOS8 からは Framework が使えるようになりました。Frameworkはコードだけでなく、storyboard や画像などのリソースを含める事が出来。そして Header ファイルを含める事ができるので、使い回す側のクライアントサイドへ class 名や API を引き渡す事が出来ます。 OS X ではこれまでも Framework は使えるようになっていましたが、これがやっと iOS にもやってきましたので、試してみる事にしました。

環境

今回の記事は以下の環境で行っています。

  • Xcode 7.3.1
  • Apple Swift version 2.2 (swiftlang-703.0.18.8 clang-703.0.31)
  • OS X El Capitan 10.11.5

また、話をシンプルにするために、以下の点は考慮しないものとします。

  • iOS と OS X の両面戦略は考慮しない

構成

今回、Framework を試してみるにあたり、次のような構成を考えてみました。一つは「HelloKit」今後モジュール化したい部品を含めます。これには「HelloView」「HelloViewController」そしてその二つを含む storyboard。もう一つは「HelloApp」、先の「HelloKit」を利用する側のアプリ本体になります。

HelloKit Framework

では、HelloKit を作成してみましょう。Xcodeで、以下のメニューをたどり、Cocoa Touch Framework を選びます。

Xcode > File > New > Project...

Screen Shot

そして、共有化したい HelloView HelloViewController そしてその storyboard HelloView を用意します。

@IBDesignable public class HelloView: UIView {

    override public func drawRect(rect: CGRect) {
        UIColor.yellowColor().set()
        UIBezierPath(ovalInRect: self.bounds).fill()
    }

}

HelloView は view に黄色い楕円を描くという仕様とします。

class HelloViewController: UIViewController {

    @IBOutlet var helloView: HelloView!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

Screen Shot 2016-07-15 at 3.59.47 AM.png

さらには、View や ViewController だけではなく、UIを持たない class も定義して見る事にします。

public class NSHello: NSObject {
    public func hello() {
        print("Hello")
    }
}


public class SwiftyHello {
    public func hello() {
        print("Hello")
    }
}

ビルド

では、ビルドしてみましょう。ビルド後は、「Products」内の「HelloKit」を右クリック「Show in Finder」でその内容を確認してみます。中には「Headers」や storyboard などが含まれている事が確認出来ます。

Screen Shot 2016-07-13 at 12.44.52 PM.png

Screen Shot 2016-07-14 at 10.55.56 AM.png

「HelloKit」は実行可能コードで、file コマンドなどで確認する事が出来ます。iphonesimulatoriphoneos では当然アーキテクチャの違うコードになっています。

$ cd (略)/Debug-iphonesimulator/HelloKit.framework/
$ file HelloKit 
HelloKit: Mach-O dynamically linked shared library i386
$ cd (略)/Debug-iphoneos/HelloKit.framework/
$ file HelloKit 
HelloKit: Mach-O universal binary with 2 architectures
HelloKit (for architecture armv7):  Mach-O dynamically linked shared library arm
Debug-iphoneos/HelloKit.framework/HelloKit (for architecture arm64):    Mach-O 64-bit dynamically linked shared library

ヘッダーファイルを確認してみます。先ほどのクラスが並んでいますが、一つ気になるところがあります。NSObject をベースとした、NSHello class はあるのに、Swift ネイティブ の SwiftyHello class はヘッダーに入っていません。

(略)
SWIFT_CLASS("_TtC8HelloKit9HelloView")
@interface HelloView : UIView
- (void)drawRect:(CGRect)rect;
- (nonnull instancetype)initWithFrame:(CGRect)frame OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
@end

@class NSBundle;

SWIFT_CLASS("_TtC8HelloKit19HelloViewController")
@interface HelloViewController : UIViewController
@property (nonatomic, weak) IBOutlet HelloView * _Null_unspecified helloView;
- (void)viewDidLoad;
- (void)didReceiveMemoryWarning;
- (nonnull instancetype)initWithNibName:(NSString * _Nullable)nibNameOrNil bundle:(NSBundle * _Nullable)nibBundleOrNil OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
@end


SWIFT_CLASS("_TtC8HelloKit7NSHello")
@interface NSHello : NSObject
- (void)hello;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

HelloApp

今度は、Frameworkを使う側の HelloApp を作成してみます。プロジェクトのターゲットから、iOS の Application のターゲットを追加します。今回は Single View Application をベースに初めて見たいと思います。

Screen Shot 2016-07-14 at 12.16.12 PM.png

Main.storyboard を編集して、Navigation Controller ベースにしてみたいと思います。そして ViewControllerHello ボタンを押すと、HelloKitHelloViewController を表示してみたいと思います。

Screen Shot 2016-07-14 at 12.53.16 PM.png

そして、先ほどビルドした「HelloKit.framework」を「General」タグから「HelloApp」ターゲットに含めます。

Screen Shot 2016-07-15 at 3.43.08 AM.png

Embedded Binaries に framework を追加

Project のターゲットから「HelloApp」を選び、「General」を選択。そこに 「Embedded Binaries」のセクションの「+」から HelloKit のフレームワークを選びます。ちなみに「Embedded Binaries」にフレームワークを追加するまでは、「Build Phases」タブを見ても「Embedded Binaries」のセクションはないのですが、「General」タブから一度「Embedded Binaries」にフレームワークを追加すると、なぜか「Build Phases」にも「Embedded Binaries」が現れます。

Screen Shot 2016-07-15 at 1.14.11 AM.png

「Build Phases」の中はこんな感じになります。

Screen Shot 2016-07-15 at 4.24.14 AM.png

HelloKit の呼び出し

では、ViewController クラスに「Hello」ボタンを押された時の処理を書きます。import HelloKit の一文を忘れないでください。

ViewController.swift
import UIKit
import HelloKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    @IBAction func helloAction(sender: AnyObject) {

        let bundle = NSBundle(forClass: HelloViewController.self)
        let storyboard = UIStoryboard(name: "HelloView", bundle: bundle)
        let viewController = storyboard.instantiateInitialViewController() as! HelloViewController

        self.navigationController?.pushViewController(viewController, animated: true)
    }

}

HelloKit のバンドルより HelloView の storyboard を取り出し、initial view controller をインスタンス化しています。HelloKit側では ViewController を HelloViewController class に設定しているので、HelloViewController にキャストしています。そして、navigation controller に push しています。

では、実行してみましょう。

Screen Shot 2016-07-15 at 4.31.15 AM.png

課題

この記事ではスムーズな手順で進めているように見えますが、本来例えばSDKを提供する場合などは、HelloKit と HelloApp が同じワークスペースでビルドできる事は稀であると言えます。この場合、Debug/Release または Device/Simulator の区分は選択中のビルドターゲットが変われば Xcode が自動的に切り替えてくれます。ところが、これを別ワークスペース例えば、それぞれプロジェクトを分けてビルドすると、最初の一回目はちゃんとビルドしてくれるようなのですが、一度 Device/Simulator などの設定を入れ替えるとビルドエラーを起こすようになり、検索しても、なかなか的を得た回答が得られず、止むを得ず上記のような記事になっています。

Screen Shot 2016-07-15 at 1.28.59 AM.png

 'HelloViewController' is unavailable: cannot find Swift declaration for this class

実際には、様々はトライ&エラーを繰り返しているので、原因と結果の因果関係がはっきりできない状態ですので、上記で説明したビルドエラーの説明も的を得ているのかどうかなんとも言えず歯切れの悪い記事となってしまいました。

Github

上記のプロジェクトは github より取得可能です。ライセンスは MIT になっていますので、ご自由におためしくださいませ。

https://github.com/codelynx/HelloKit

参考資料

Building Modern Frameworks WWDC2014
Universal Cocoa Touch Frameworks for iOS 8
[Xcode6] Universal Frameworkを作る

質問ばかりで答えのないStackoverflow

MyClass is unavailable: cannot find Swift declaration for this class - Release Build Only
MyClass is unavailable: cannot find Swift declaration for this class
Bridging Header issue - MyClass is unavailable: cannot find Swift declaration for this class
Class is unavailable: Cannot find swift declaration for this Class

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした