今回はSwiftにおけるアクセス修飾子の違いについて。
アクセス修飾子とは、クラス、構造体、プロパティ、メソッドなどのアクセス範囲を指定するキーワードのことだ。
Swiftにおけるアクセス修飾子は下記5つの種類がある。
アクセス修飾子 | 概要 |
open | どこからでもアクセス可能 |
public | どこからでもアクセス可能。但し、他のモジュール(他のアプリ等)からの継承不可 |
internal | デフォルト。同じプロジェクト内(つまり、同じアプリ内)のみアクセス可能 |
fileprivate | 同じファイル内からのみアクセス可能 |
private | 同じクラスや構造体等からのみアクセス可能 |
アクセス可能な範囲はprivate → fileprivate → internal → public → open の順で緩くなる。
つまり、privateがアクセス制限が最も厳しく、openが最も甘い。
よく使うのはprivateとinternalだと思うが、私はこの違いが最初の頃だいぶ曖昧だったので、簡単に整理しておきたい。
open
openは最もアクセス範囲が広いアクセス修飾子だ。
モジュール外(アプリ外)からも継承可能であり、プロパティやメソッドのオーバーライド(上書き)も可能となる。
1 2 3 4 5 | open class OpenClass { open func openMethod() { print("このメソッドはアプリ外からオーバーライド可能です") } } |
APIやライブラリとしてクラスを定義する場合はこのopenか後述するpublicを指定する。
その中でも特に、外部モジュールでクラスを拡張したりオーバーライドしたりしたい場合にopenを使用する。
public
publicはopenに似ていて、クラスはモジュール外での利用可能だ。
ただし、継承やオーバーライドは同じモジュール内(アプリ内)でしか行えない点がopenとの違いだ。
1 2 3 4 5 | public class PublicClass { public func publicMethod() { print("このメソッドはアプリ外からアクセスできますが、オーバーライドは出来ません") } } |
他のモジュールからクラスやメソッドにアクセスさせたいが、継承やオーバーライドを制限したい場合はpublicを使用する。
internal
internalは、同一モジュール内(同じアプリ内)からのアクセスのみが可能となるアクセス修飾子だ。
同じアプリ内からは自由にアクセスできるが、他のモジュール(他のアプリ等)からは一切アクセスできない。
このinternalはSwiftのデフォルトのアクセスレベルだ。
そのため、省略して使われていることが多く、馴染みがない人も多いかもしれない。
例えば、下記2つのコードのアクセスレベルはどちらも同じinternalとなり、同じアプリ内からしかアクセスできない。
【明示的にinternalを指定した場合】
1 2 3 4 5 | internal class InternalClass { internal func internalMethod() { print("このメソッドは同じモジュール内からのみアクセス可能です") } } |
【internalを省略する場合】
1 2 3 4 5 | class InternalClass { func internalMethod() { print("このメソッドは同じモジュール内からのみアクセス可能です") } } |
アプリ内からもアクセス制限を掛ける必要があったり、外部からアクセスできるようにしたりする必要がなければ、アクセスレベルはこのinternalを使えばOKだ。
fileprivate
fileprivateは同じファイル内からのみアクセスが可能となるアクセスレベルだ。
デフォルトのアクセスレベルのinternalはアプリ内のどのファイルからもアクセス可能だったが、fileprivateの場合は他のファイルからアクセスすることはできない。
もし、同じアプリ内の別ファイル(~.swift)からアクセスしようとすると下記のようにXcode上でエラーが発生する
1 | 〜 is inaccessible due to 'fileprivate' protection level |
例えば、File1.swiftというファイルに下記コードがあるとする。
1 2 3 4 5 6 7 | // File1.swift class FilePrivateClass { // このメソッドはFile1.swift内からのみアクセス可能 fileprivate func revealSecret() { print("プライベートデータ") } }こ |
このメソッドを同じアプリ内のContentView.swiftファイルで下記のように呼び出そうとすると前述したエラーがXcode上で表示される。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // ContentView.swift import SwiftUI struct ContentView: View { var body: some View { VStack { Button("fileprivateメソッドを別ファイルから呼び出す") { let filePrivateClass = FilePrivateClass() filePrivateClass.revealSecret() //この行でエラーが発生する } } .padding() } } |
理由はFile1.swiftファイル内からではなく、ContentView.swiftという別ファイルからfileprivateのアクセスレベルに設定したメソッドを呼び出したからだ。
このrevealSecret()メソッドの先頭に指定してある fileprivate を削除して、デフォルトのinternalのアクセスレベルに変更するとエラーが解消する。
1 2 3 4 5 6 7 | class FilePrivateClass { // fileprivate→internal(省略)にアクセスレベルを変更 func revealSecret() { print("プライベートデータ") } } |
もしくはContentView.swiftファイル内にFilePrivateClassを移動させてしまえば、同じファイル内での呼び出しになり、こちらでもエラーが解消する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // ContentView.swift import SwiftUI //ContentView.swift内に移動して同じファイル内で呼び出せるように変更 class FilePrivateClass { fileprivate func revealSecret() { print("プライベートデータ") } } struct ContentView: View { var body: some View { VStack { Button("fileprivateメソッドを同じファイル内で呼び出す") { let filePrivateClass = FilePrivateClass() filePrivateClass.revealSecret() //同じファイル内からの呼び出しのため、この行のエラーは解消する } } .padding() } } |
このようにfileprivateを指定すると他のファイルからはアクセスできないため、ファイル内でのみ動作するプライベートな機能を実装する場合に利用する。
private
privateはfileprivateよりも更にアクセスレベルが制限される。
同じクラスや構造体の内部でのみアクセス可能となるのがprivateだ。
当然、アプリ外やアプリ内の他のファイルからのアクセスも出来ない。
fileprivateとの違いは同じファイル内の別のクラスや構造体からのアクセスもエラーになる点だ。
例えば、先ほどfileprivateの例で違いを確認しよう。
【ContentView構造体で同じファイル内のfileprivateのメソッドを呼び出し】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import SwiftUI class FilePrivateClass { // fileprivateのメソッドなので、ContentView構造体から呼び出し可能 fileprivate func revealSecret() { print("プライベートデータ") } } struct ContentView: View { var body: some View { VStack { Button("fileprivateメソッドを呼び出し") { let filePrivateClass = FilePrivateClass() filePrivateClass.revealSecret() //エラーは起きない } } .padding() } } |
上記は先ほど説明した通りで、fileprivateのメソッドを同じファイル内のContentView構造体から呼び出しているのでエラーは起きない。
一方で上記コードのアクセスレベルをfileprivateからprivateに変えるだけで今度はエラーとなる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import SwiftUI class FilePrivateClass { // fileprivate→privateに変更 private func revealSecret() { print("プライベートデータ") } } struct ContentView: View { var body: some View { VStack { Button("privateメソッドを呼び出し") { let filePrivateClass = FilePrivateClass() filePrivateClass.revealSecret() //今度はこの行でアクセスエラーが発生する } } .padding() } } |
理由はFilePrivateClassで定義されたprivateメソッドを同じファイル内のContentView構造体というFilePrivateClassとは別の構造体で呼び出そうとしているからだ。
クラスや構造体の内部のみで利用したい場合はprivateを利用しよう。
まとめ
Swiftにおける5つのアクセス修飾子の要点は下記のとおりだ。
- アクセスレベルはprivate → fileprivate → internal → public → open の順で緩くなる。
- privateが最もアクセス制限が厳しく、同一クラスや構造体からしかアクセスできない
- fileprivateは同じファイル内からのみアクセス可能
- internalはデフォルトのアクセスレベルで同じアプリ内からは別ファイルからでもアクセス可能。省略される事が多い
- publicとopenはどちらも他のモジュール(他のアプリ等)からアクセス可能でライブラリ等でよく使われる
- publicはopenと違い、継承やオーバーライドは同じモジュール内(アプリ内)でしか行えない