44
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

Swiftのアクセス修飾子とObjCをためしてみたらFrameworkでちょっとした罠があった

Xcode6-beta4がリリースされ、beta1の段階で言及されていたアクセスコントロールが満を持して追加されました!

そこで早速アクセスコントロールを試してみました。

Swiftには3種類の修飾子が用意されています。

public どこからでもアクセスできる
internal 同じターゲットからアクセスできる
private 同じファイル内のみアクセスできる

上記について、SwiftのObjectをSwift、ObjCからアクセスした時にどうなるのか調べました。

同一ターゲットの場合

下記のようなSwiftを書きました。

MyClass.swift
public class MyPublicClass:NSObject {
    public func publicMethod() {
        println("\(self.dynamicType.description()) public method")
    }

    internal func internalMethod() {
        println("\(self.dynamicType.description()) internal method")
    }

    private func privateMethod() {
        println("\(self.dynamicType.description()) private method")
    }

    func task() {
        MyPublicClass().privateMethod()
        MyInternalClass().privateMethod()
        MyPrivateClass().privateMethod()
    }
}

internal class MyInternalClass:NSObject {
    public func publicMethod() {
        println("\(self.dynamicType.description()) public method")
    }

    internal func internalMethod() {
        println("\(self.dynamicType.description()) internal method")
    }

    private func privateMethod() {
        println("\(self.dynamicType.description()) private method")
    }

}

private class MyPrivateClass:NSObject {
    public func publicMethod() {
        println("\(self.dynamicType.description()) public method")
    }

    internal func internalMethod() {
        println("\(self.dynamicType.description()) internal method")
    }

    private func privateMethod() {
        println("\(self.dynamicType.description()) private method")
    }
}

同じファイル内ではprivate宣言されたクラスやメソッドにアクセスできます。

別ファイルから呼び出す際はpublic,internalのものが呼び出せます。

Swift
        let pub = MyPublicClass()
        pub.publicMethod()
        pub.internalMethod()

        let inter = MyInternalClass()
        inter.publicMethod()
        inter.internalMethod()

        pub.task()

同様に同じターゲット内のObjCのクラスもpublic,internalのものが呼び出せます。

MyObjCClass.m
@implementation MyObjCClass

-(void)task
{
    MyPublicClass *pub = [MyPublicClass new];
    [pub publicMethod];
    [pub internalMethod];

    MyInternalClass *inter = [MyInternalClass new];
    [inter publicMethod];
    [inter internalMethod];
}

@end

別ターゲットの場合

別途Frameworkを作り同様に同じようなクラスを作ります

MyFwClass.swift
public class MyFwPublicClass:NSObject {
    public init()  {

    }

    public func publicMethod() {
        println("\(self.dynamicType.description()) public method")
    }

    internal func internalMethod() {
        println("\(self.dynamicType.description()) internal method")
    }

    private func privateMethod() {
        println("\(self.dynamicType.description()) private method")
    }

    func task() {
        MyFwInternalClass().privateMethod()
        MyFwPrivateClass().privateMethod()
    }
}

internal class MyFwInternalClass:NSObject {
    public func publicMethod() {
        println("\(self.dynamicType.description()) public method")
    }

    internal func internalMethod() {
        println("\(self.dynamicType.description()) internal method")
    }

    private func privateMethod() {
        println("\(self.dynamicType.description()) private method")
    }

}

private class MyFwPrivateClass:NSObject {
    public func publicMethod() {
        println("\(self.dynamicType.description()) public method")
    }

    internal func internalMethod() {
        println("\(self.dynamicType.description()) internal method")
    }

    private func privateMethod() {
        println("\(self.dynamicType.description()) private method")
    }

}

同じフレームワーク内の別Swiftファイルからはpublic,internalにアクセスできます。

MyFwOtherClass.swift
class MyFwOtherClass: NSObject {
    func task() {
        let fwPub = MyFwPublicClass()
        fwPub.publicMethod()
        fwPub.internalMethod()

        let fwInter = MyFwInternalClass()
        fwInter.publicMethod()
        fwInter.internalMethod()
    }
}

しかし、同じフレームワーク内の別ObjCファイルからはpublicにしかアクセスできませんでした。Frameworkだと同一モジュールでもObjCからinternalへのアクセスができません。

MyFwObjCClass.m
#import <MyFramework/MyFramework-Swift.h>

@implementation MyFwObjCClass

-(void)task {
    MyFwPublicClass *pub = [MyFwPublicClass new];
    [pub publicMethod];
}

@end

別ターゲットのSwiftを呼び出す

最後に別ターゲットのSwiftを呼び出します。

Swift
   let fwPub = MyFwPublicClass()
   fwPub.publicMethod()

記述通りpublicのみを呼び出すことができます。
ObjCでも同様です。

ObjC
    MyFwPublicClass *fwPub = [MyFwPublicClass new];
    [fwPub publicMethod];

しかしここで1つ罠がありました。

アクセス修飾子を指定しないとデフォルトでinternalになります。
そしてinitを明示的に宣言しないと必然的にinitinternalになってしまい、仮にモジュール内でpublic宣言していたとしてもinitinternalのため オブジェクトが生成できません!

なのでFrameworkを作る際はクラスとともにinitもPublic宣言を必ずしてください!

MyFwClass.swift
public class MyFwPublicClass:NSObject {
    public init()  {

    }
・・・

まとめ

  • 同一アプリ内ならばSwiftのアクセス修飾子はSwift/ObjCによらず通用する
  • Framework内ならばSwift同士ならばアクセス修飾子通りにアクセスできるが、ObjCのクラスからはpublicのみのアクセスになる
  • 別モジュールから呼び出すときはpublicのみアクセスできる。この時initも明示的にpublicにしないとオブジェクトの生成ができなくなるので必ずpublicにする

かなり適当な検証なので間違っていたらご連絡ください。

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
Sign upLogin
44
Help us understand the problem. What are the problem?