目次

目次

【Swift】アクセスレベルとそれぞれの違い

アバター画像
長島大和
アバター画像
長島大和
最終更新日2023/03/27 投稿日2023/03/27

はじめに

こんにちは。株式会社レコチョクの長島と申します。 2022年4月に新卒で入社し、現在はiOSアプリの開発を行うグループに所属しています。 最近聞いている音楽はダンスミュージックが中心で、Stream Paletteというアルバムを今はよく聞いています。よろしくお願いします。

さて、私はOJTとして既存アプリのモックを作成する課題を行っていたのですが、その中で、アクセスレベルという概念があることに気付きました。他言語で publicprivateといったアクセス修飾子を見たことがありましたが、Swiftではどのような要素があるのか知らないことが多くありました。

例えば以下のような疑問がありました。

  • internalというアクセスレベルがどういう動きをするのか
  • internalpublicの違いがどのような部分にあるのか
  • 普段のプロパティやクラスなどではアクセス修飾子の記載を省略しているがどのような扱いになるのか

今回はそれらを調査して得られた結果として、Swiftのアクセスレベルの概要と実装例を紹介します。

動作環境

  • Swift 5.7.2

アクセスレベルについて

前提・モジュールについて

Swiftにおけるアクセス制御は、モジュールとソースコードという概念に基づいています。

モジュールは複数のソースコードのまとまりであり、 importを利用して別のモジュールによってインポートできるフレームワークやアプリケーションと定義されています。 Xcodeであれば、各ビルドターゲット(アプリバンドルなど)がSwiftにおいては個別のモジュールとして扱われます。

アクセスレベルとは

Swiftにはアクセスレベルという概念があります。あるコードへアクセスできる他のソースコードやモジュールの範囲に制限をかけられます。

アクセスレベルはクラス・構造体・列挙型や、それに属するプロパティ・メソッド・イニシャライザ・サブスクリプトなどに割り当てることができます。これにより意図しない場所でコードが利用されることを防いだり、より広い場所でコードが利用できるように設定できます。

アクセスレベルには以下の5つがあります。

アクセスレベル 説明
open 対象は任意のソースファイル内からアクセスできる。
クラスとクラスメンバのみに適用できる。継承・オーバーライドが可能になる。
public 対象は任意のソースファイル内からアクセスできる。
internal 対象は、同一モジュール内からアクセスできる。
Swiftではデフォルトのアクセスレベルとして設定されている。
fileprivate 対象は、同一ファイル内からアクセスできる。
private 対象は、同一クラス・構造体などからアクセスできる。

実装例

アクセスレベルは、対象の宣言の先頭に open, public, internal, fileprivate, privateの修飾子を記載することで設定できます。

public class SomeClass {
    open var someOpen = "open"
    public var somePublic = "public"
    var someInternal = "internal"
    fileprivate var someFilePrivate = "fileprivate"
    private var somePrivate = "private"

    public init() {
        // 初期化処理
    }
}
let sampleClass = SomeClass()

ここから、より詳しく各アクセスレベルの特徴を見ていきます。

open, public

openおよびpublicはどこからでもアクセスできます。モジュールを隔てたとしても外部に公開されており、アクセス可能です。

// 別のモジュールからインスタンスを作成し実行
print(sampleClass.someOpen)    // open
print(sampleClass.somePublic)  // public

openpublicとは異なり、継承・オーバーライドが可能という差異があります。 その性質上、クラス・クラスメンバにのみ適用可能です。

internal

internalは同じモジュール内であればどこからでもアクセスできます。

print(sampleClass.someInternal) // internal

fileprivate, private

fileprivateは、同じファイル内であればアクセスすることができるレベルです。 以下のコードは同じファイル内であれば正常に実行できますが、別ファイルから同じことをすると 'someFilePrivate' is inaccessible due to 'fileprivate' protection levelというエラーが発生します。

// 同じファイル内で実行
print(sampleClass.someFilePrivate) // fileprivate
// 別のファイル内で実行
print(sampleClass.someFilePrivate) // エラー

privateは宣言されている範囲内でなければアクセスできないレベルであり、外部からアクセスしようとすると'somePrivate' is inaccessible due to 'private' protection levelというエラーが発生します。

print(sampleClass.somePrivate) // エラー

アクセスが限られているため、意図しない場所でのアクセスがなくなります。

プロパティのget, setに対してのアクセスレベル設定

定数/変数のプロパティやサブスクリプトにおける getsetは、基本的にそのクラスや構造体のアクセスレベルの影響を受けます。しかし、getsetのアクセスレベルを別々にしたい場合、<アクセスレベル>(set)を用いることで設定できます。

以下では Counterというクラスを作成し、<アクセスレベル>(set)を用いることで、getよりも厳しいアクセス制限をsetにかけています。

class Counter {
    private(set) var count = 0  // getはinternal, setはprivate
    func add() {
        count += 1
    }
}

こうすることで、countへの直接的なアクセスを防ぐことができます。下記コードでは 'count' setter is inaccessibleというエラーが出ることを確認できます。

let counter = Counter()
counter.count = 5 // エラー

getのアクセスレベルはinternalのままなので、クラス外からアクセス可能です。

let counter = Counter()
counter.add()
print(counter.count) // 1

なお、 getのアクセス範囲をsetよりも狭めてしまうとエラーが発生します。以下の例ではPrivate property cannot have a public setterというエラーになります。

class Counter {
    private public(set) var count = 0  // getはprivate,setはpublicに設定、エラーになる
    func add() {
        count += 1
    }
}

デフォルトのアクセスレベル

宣言時にアクセス修飾子を省略した場合、一部の例外を除き internalが設定されます。 publicなクラスであっても、クラスメンバのデフォルトのアクセスレベルはinternalのままです。クラスメンバも公開したい場合は、明示的にアクセスレベルを宣言する必要があります。

public class SomePublicClass {
    let sample = "internal" // アクセス修飾子がないが、internal
    public let sample2 = "public" // 明示的に宣言すると、public
}

ただし、一部の条件ではデフォルトのアクセスレベルが internalではない場合があります。 たとえば、 privateなクラスを作成した場合、そのクラスメンバのデフォルトはprivateになり、fileprivateなクラスであればデフォルトはfileprivateになります。

private class SomePrivateClass {
    let sample = "private" // アクセス修飾子がないが、private
}

クラスメンバのアクセスレベルは、クラス自体のアクセスレベルよりも広く設定することができないため、このような動作になります。

おわりに

ここまでの内容をまとめると以下のようになります。

  • openはモジュールの外部からアクセスができ、継承・オーバーライドできるクラス・クラスメンバに設定する
  • publicはモジュールの外部からもアクセスができる要素に設定する
  • internalは同一モジュール内からのみアクセスできる要素に設定する
  • fileprivateは同一ファイル内からのみアクセスできる要素に設定する
  • privateは同一クラス・構造体などからのみアクセスできる要素に設定する
  • get, setでアクセス制限を分けたい場合は<アクセスレベル>(set)を用いて設定する
  • アクセス修飾子の記載を省略した場合、一部の例外を除きデフォルトのアクセスレベルとしてinternalが設定される

他の言語で public,privateの2つは知っていましたが、Swiftではアクセス修飾子を省略した場合にinternalが設定されることや、openfileprivateなどのアクセスレベルが存在していることなど、多くの違いがあるということに気付きました。

今回の記事を見て、アクセスレベルについての理解が進みましたら幸いです。

参考資料

アバター画像

長島大和

目次