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

iOS, Swift

はじめに

こんにちは。株式会社レコチョクの長島と申します。
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の修飾子を記載することで設定できます。

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

open, public

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

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

internal

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

fileprivate, private

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

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

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

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

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

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

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

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

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

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

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

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

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

おわりに

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

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

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

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

参考資料

iOS, Swift