Embedded FrameworkをSwiftで作成
自分のアプリのWatchKit対応をしながら、そろそろコードをしっかりと共通化させようとしていたらエラーが発生してハマってしまったのでメモ。
エラー発生
プロジェクトの構成はこのようになっています。Extensionは作成せず、Embedded FrameworkであるFrameKitと本体のアプリターゲット、SwiftFrameがあるだけです。
FrameKitにはFrameObject
というクラスを追加しているだけです。
で、これを使おうとしたら "Use of unresolved identifier FrameObject
" というエラーがされてコンパイルが通りませんでした。
アクセス制御
原因はアクセス制御でした。
Swiftは モジュール と ソースフファイル をアクセス制御の単位としています。
アクセスレベル | 用途 | |
---|---|---|
public | どこからでもアクセス可能 | どこからでもアクセス可能なのでframeworkのAPIを定義する場合はこれを使う |
internal | デフォルトのアクセスレベル | 自身のモジュールと同じモジュールのソースファイル内であればアクセス可能。何も指定しない場合はこれが指定される。 |
private | 最も厳しいアクセスレベル | モジュール内の同じソースファイルでなければアクセスできない。外部に隠蔽しておきたいものはこれを指定。 |
Swiftはプロジェクト内のターゲットをモジュールとみなすようです。上記のFrameObject
はアクセスレベルを何も指定していなかったのでinternal
が指定されている状態、つまりモジュール内でしか読み込めないようになっていました。
これをSwiftFrameターゲットで使用するためにはFrameObject
をモジュール外からも読み込めるようにしなくてはいけません。Objective-Cで書いているときは「公開するのだけヘッダファイルに後で書き足せばいいや」くらいの感じでやっているので気をつけないといけないですね。
ということで、public
を指定すればエラーは起きなくなりました。
最初はクラス名にpublic
をつければ問題ないかと思っていたのですが、プロパティはもちろんメソッド名にもアクセスレベルの記述をしなければいけません。
プロパティそれぞれに異なるアクセスレベルを設定してみましょう。先ほど、Embedded Frameworkに作成したFrameObject
クラスをこんな感じに設定します。
public class FrameObject: NSObject {
public var publicName: String
internal var internalName: String
private var privateName: String
public init(publicName:String, internalName:String, privateName:String) {
self.publicName = publicName
self.internalName = internalName
self.privateName = privateName
}
}
public
なプロパティにはアクセスできますが、internal
、private
にはアクセスできません。
今度はFrameObject
を 同一モジュール内に作成 してみましょう。
public class FrameObject: NSObject {
public var publicName: String
internal var internalName: String
private var privateName: String
public init(publicName:String, internalName:String, privateName:String) {
self.publicName = publicName
self.internalName = internalName
self.privateName = privateName
}
}
今度はinternal
にはアクセスできるようになりました。当然ですがこの場合もprivate
にはアクセスできません。
普通に考えれば当たり前ですよね。
privateへのアクセス
上記のFrameObjectクラスを呼び出したいソースファイルの中で定義すればprivate
へのアクセスが可能になります。
同一モジュール・同一ファイル内であればアクセスが可能です。
別モジュールに同一名のクラスがあった場合
別モジュールに存在するクラスと同じクラス名が定義されていた場合はどうなるでしょう。
ログに注目してください。
モジュールを指定してコンストラクタを呼んだ場合は、該当するモジュールのクラスを呼び出します。
モジュールを指定しなかった場合は自身のモジュール内を探し、該当するクラスがあればそれを使用するみたいです。
まとめ
そういえばSwiftが出たばかりのころ、Betaのころってアクセス制御なかったんですよね。全然ついていってなかった。