はじめに
こんにちは。株式会社レコチョクの長島と申します。
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などのアクセスレベルが存在していることなど、多くの違いがあるということに気付きました。
今回の記事を見て、アクセスレベルについての理解が進みましたら幸いです。
参考資料
- アクセスコントロール(Access Control) · The Swift Programming Language日本語版 (swiftlangjp.com)
- 【プログラミング初心者】Swift基礎 アクセス修飾子 – Qiita
- 知っているようで知らないSwift5のアクセス修飾子 – Qiita
- swift モジュールとは – Qiita
この記事を書いた人
最近書いた記事
- 2024.03.12「mainブランチ消しちゃったみたいでぇ……」
- 2023.12.14【iOS】ライトニングトークイベントでAI関連の発表をした振り返り
- 2023.07.21【Swift】enumとstatic let(型プロパティ)による定数定義
- 2023.06.27【ChatGPT】ChatGPTでChatGPTチャットボット試作