はじめに
こんにちは。株式会社レコチョクの長島と申します。 2022年4月に新卒で入社し、現在はiOSアプリの開発を行うグループに所属しています。 最近聞いている音楽はダンスミュージックが中心で、Stream Paletteというアルバムを今はよく聞いています。よろしくお願いします。
さて、私はOJTとして既存アプリのモックを作成する課題を行っていたのですが、その中で、アクセスレベルという概念があることに気付きました。他言語で
publicやprivateといったアクセス修飾子を見たことがありましたが、Swiftではどのような要素があるのか知らないことが多くありました。
例えば以下のような疑問がありました。
internalというアクセスレベルがどういう動きをするのかinternalとpublicの違いがどのような部分にあるのか- 普段のプロパティやクラスなどではアクセス修飾子の記載を省略しているがどのような扱いになるのか
今回はそれらを調査して得られた結果として、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
openはpublicとは異なり、継承・オーバーライドが可能という差異があります。
その性質上、クラス・クラスメンバにのみ適用可能です。
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に対してのアクセスレベル設定
定数/変数のプロパティやサブスクリプトにおける
getとsetは、基本的にそのクラスや構造体のアクセスレベルの影響を受けます。しかし、getとsetのアクセスレベルを別々にしたい場合、<アクセスレベル>(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が設定されることや、openやfileprivateなどのアクセスレベルが存在していることなど、多くの違いがあるということに気付きました。
今回の記事を見て、アクセスレベルについての理解が進みましたら幸いです。
参考資料
長島大和