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


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

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

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

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

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

同一ターゲットの場合
----

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

```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:Swift
        let pub = MyPublicClass()
        pub.publicMethod()
        pub.internalMethod()
        
        let inter = MyInternalClass()
        inter.publicMethod()
        inter.internalMethod()
        
        pub.task()
```

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

```objc:MyObjCClass.m
@implementation MyObjCClass

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

@end
```

別ターゲットの場合
----

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

```swift: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`にアクセスできます。

```swift: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`へのアクセスができません。

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

@implementation MyFwObjCClass

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

@end

```

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

```swift:Swift
   let fwPub = MyFwPublicClass()
   fwPub.publicMethod()
```

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

```objc:ObjC
    MyFwPublicClass *fwPub = [MyFwPublicClass new];
    [fwPub publicMethod];

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

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

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

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

まとめ
---

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


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