Swift のアクセスコントロールを理解する
Swift プログラミング
Swift のアクセスコントロール識別子 (public, internal, private) の使い方や影響範囲がなんとなく複雑だったので詳しく整理してみました。
アクセスコントロール
Swift言語 には『定義したオブジェクトやメソッドなどをどこから利用できるようにするか』を示すアクセスコントロール が用意されています。
アクセスコントロールの種類 | 範囲 | 適用の優先度 |
---|---|---|
private
|
同一ファイル内で利用可能 | 高 |
internal
|
同一モジュール内で利用可能 | 中 |
public
|
モジュールの外側で利用可能 | 低 |
private
アクセスコントロール指定子private
は、同じファイルからだけのアクセスが可能なオブジェクトに指定します。
ファイル内であれば、あるクラス内で定義したprivate
な値に、ほかの異なるクラスからでもアクセスできるので、たとえば『ある値を保持するクラス』と『そのクラスの値をセットするパーサークラス』を作りたいときなどに便利です。
値を保持するクラスの中身は外部から変更させたくないけれど、その値をセットするパーサー機能を別のクラスに任せたいときでも、値をセットするメソッドやプロパティをprivate
で実装しておいて、同じファイル内でパーサークラスを実装すれば、それらを外部に公開することなく、パーサークラスが自由に値クラスを操作できます。
internal
アクセスコントロール指定子internal
は、同じモジュール内でのアクセスが可能なオブジェクトに指定します。
モジュールというのはSwift言語 のプログラムのまとまりを表すもののひとつで、ひとつのアプリケーションや、Cocoa Framework やCocoa Touch Framework をひとつのまとまりとして、それをひとつのモジュールとして扱います。
フレームワークを作って外部に公開したい場面でなければ、通常はこのinternal
で自由にアクセスできます。
public
アクセスコントロール指定子public
は、どこからでもアクセスが可能なオブジェクトに指定します。
このアクセスコントロールが必要になるのは、自分が作成したオブジェクトを別のプロジェクトでも利用できるようにしたいときだけです。それ以外の場面、たとえばアプリのプロジェクト内で使うオブジェクトとかでは、この指定子を使う必要はありません。
フレームワークとして機能を分けて実装したいときに、このpublic
指定が重要になってきます。
アクセスコントロールの指定を省略した場合
アクセスコントロールの指定子は記載を省略できるようになっています。
指定子を省略した場合は、原則的にはinternal
が指定されているものとみなされます。
ただし、クラス内のプロパティやメソッドなどについては、公開される範囲の最大がクラスの公開範囲までに制限されます。これについては後で説明します。
アクセスコントロールの適用方法
アクセスコントロールは、クラスやプロトコル、メソッドやプロパティや関数といった識別子を定義するときに、その先頭に記載することで、それらをどこから利用できるかを指定できます。
関数のアクセスコントロール
関数の場合は、関数定義の先頭にアクセスコントロールの識別子を指定します。
たとえば次のようにtoString関数
の先頭にpublic
を指定して定義することで、モジュールの外でも利用可能な関数を定義できます。
public func toString<T>(value:T) -> String {
}
列挙型のアクセスコントロール
列挙型にアクセスコントロールを指定する方法は、先ほどの関数のときと同じです。
private enum Choice {
case A
case B
}
たとえば上記のようにすることで、同じファイル内でだけ利用可能な列挙型を定義できます。
クラスのアクセスコントロール
モジュール内でだけ使用できるクラスを定義したい場合は、クラス定義の先頭にinternal
を指定します。
internal class MyClass {
}
メソッドやプロパティのアクセスコントロール
クラス内にはプロパティやメソッドを実装できますが、それらはクラスそのもののアクセスコントロールの影響を受けます。
たとえばpublic
で定義したクラス内では、メソッドやプロパティにはpublic
, internal
, private
のいずれかを指定できますが、internal
で定義したクラス内では、実質的にinternal
かprivate
だけしか指定できなくなります。
クラスのアクセスコントロール | 内部でのpublic
|
内部でのinternal
|
内部でのprivate
|
無指定の場合 |
---|---|---|---|---|
public
|
⚪︎ | ⚪︎ | ⚪︎ | internal
|
internal
|
× | ⚪︎ | ⚪︎ | internal
|
private
|
× | × | ⚪︎ | private
|
アクセスコントロールの範囲を狭めることはできますが、広げることはできないため、予期せず公開されるような心配はありません。
たとえばinternal
で定義したクラスで、メソッドにpublic
を指定すると、次のようなビルド警告が表示されます。
Declaring a public instance method for a internal class.
このままでもビルドは通りますが、実際にはこのクラス自体がinternal
の範囲でしか利用できないため、それを超えるpublic
の範囲でこのメソッドを使うことはできません。
入れ子にしたオブジェクトのアクセスコントロール
Swift言語 ではクラス内に、サブクラスや構造体などを入れ子で定義できますが、それらについても個別にアクセスコントロールを指定できます。
このとき、指定したアクセスコントロールは、親であるクラスのアクセスコントロールの影響を受けます。その影響の受け方は、上記のメソッドやプロパティのときと同様です。
プロトコルのアクセスコントロール
プロトコルにもアクセスコントロールを指定できます。
プロトコルにアクセスコントロールを指定すると『そのプロトコルが使える範囲』を、つまり『そのプロトコルをどの範囲内でクラスに適用できるか』を指定できます。
public protocol MyProtocol {
}
プロトコルの場合はクラスとは違って、内部のプロパティやメソッドなどにはアクセスコントロールを指定することはできません。
プロトコルにアクセス範囲が指定されているということは、そのプロトコルが期待している実装のすべても『最低限』その範囲からアクセスできるものと期待されます。
型エイリアスのアクセスコントロール
Swift言語 では、型エイリアスを使って型に別名をつけられますが、この型エイリアスについてもアクセスコントロールを指定できます。
private typealias MyType = Int
こうすることで、この別名を利用できる範囲を制限できます。
型エイリアスは、クラス内で定義することもできますし、単独で定義することもできます。
クラス内に定義した場合、指定したアクセスコントロールの範囲であれば、MyClass.MyType
のようにして自由に利用できます。
クラスの定義のなかで型エイリアスを使う場合
この型名を戻り値や引数の型として使用するには、その機能がその別名を使える範囲内に定義されている必要があります。そのクラスの内部では自由に利用できます。
型エイリアスのアクセスコントロール | 内部での使用 | public
な機能の定義で使用 |
internal
な機能の定義で使用 |
private
な機能の定義で使用 |
---|---|---|---|---|
public
|
⚪︎ | ⚪︎ | ⚪︎ | ⚪︎ |
internal
|
⚪︎ | × | ⚪︎ | ⚪︎ |
private
|
⚪︎ | × | × | ⚪︎ |
継承関係を持つオブジェクトのアクセスコントロール
派生クラスの定義や、クラスをプロトコルに準拠させるときには、アクセスコントロールの適用の仕方が少し変わってきます。
派生クラスのアクセスコントロール
あるクラスを継承して派生クラスを作成したい場合には『新しいクラスのアクセス範囲は、派生元クラスのアクセス範囲と同じかそれより狭い』必要があります。
新しいクラスのアクセスコントロール | public
を継承 |
internal
を継承 |
private
を継承 |
---|---|---|---|
public
|
⚪︎ | × | × |
internal
|
⚪︎ | ⚪︎ | × |
private
|
⚪︎ | ⚪︎ | ⚪︎ |
たとえばpublic
で定義したクラスを、新しくinternal
で定義するクラスで継承することはできますが、その逆はできません。そして継承したクラスの機能は、最大でも新しいクラスのアクセス範囲に制限されます。
ちなみに、あるクラスを自身より広いアクセス範囲を持つクラスに継承しようとした場合には、次のようなエラーが発生します。
Class cannot be declared public because its superclass is internal.
より狭い範囲のアクセスコントロールが設定されたクラスで継承すると、クラス自体がそれ以上の範囲で利用できなくなるため、継承元の機能もその範囲内でしか利用できなくなります。
継承先でアクセス範囲を広げる
たとえば、継承先のクラスがpublic
だったときに、継承元のクラスがpublic
で、そこに定義されているプロパティやメソッドがinternal
などの継承先より低いアクセス範囲の場合があります。
このようなときは、継承先でも元々のクラスで定義されていたアクセス範囲に制限されています。
この公開範囲を、継承先のクラスで引き上げることもできます。
たとえば、継承元のクラスMyBaseClass
で次のようにactionメソッド
が定義されていたとします。
public class MyBaseClass {
// 既定クラスでは private で機能を実装しています。
private func action() {
}
}
このとき、継承先のクラスMyClass
でメソッドをオーバーライドすることで、それと併せて公開範囲を変更できます。
public class MyClass : MyBaseClass {
// 既定クラスでは private だった機能をオーバーライドして public にできます。
public override func action() {
}
}
このようにすることで、既定クラスではprivate
に制限されていたメソッドをpublic
の範囲から利用できるようになります。
private
で定義したメソッドをオーバーライドできるのは、同一ファイル内に定義したクラスに限られます。別のファイルで定義したクラスのprivate
な機能をオーバーライドしてそれ以上の範囲からアクセスさせることはできません。
クラスをプロトコルに準拠させる場合
プロトコルよりも狭い範囲のクラスで使用する
クラスをプロトコルに準拠させるときには、プロトコルのアクセス範囲よりも狭い範囲で定義されたクラスであっても大丈夫です。
ただしそのとき、プロトコルに定義された機能は最大でも、実質的にクラスのアクセス範囲に制限されます。
次の例では、プロトコルはpublic
ですが、それを準拠させたクラスはinternal
で定義されているため、プロトコルが要求する機能のアクセス範囲はinternal
までに制限されます。
// プロトコル自体は public で定義されています。
public protocol MyProtocol {
func action()
}
// クラスは internal で定義されています。
internal class MyClass : MyProtocol {
// プロトコルの機能は最大でも internal でのアクセス範囲になります。
internal func action()
}
プロトコルよりも広い範囲のクラスで使用する
クラスをプロトコルに準拠させる場合は、クラス自身のアクセス範囲よりも狭い範囲のプロトコルであっても大丈夫です。
このとき、プロトコルが求める機能は、そのプロトコル自身のアクセス範囲にさえ提供できていれば問題ありません。
次の例では、プロトコルはprivate
で定義されていて、それを準拠させたクラスはpublic
で定義されています。この場合は、プロトコルが要求する機能のアクセス範囲は、プロトコル自身に設定されているprivate
以上の範囲で自由に指定できます。
// プロトコル自体は private で定義されています。
private protocol MyProtocol {
func action()
}
// クラスは public で定義されています。
public class MyClass : MyProtocol {
// プロトコルの機能は、プロトコルのアクセス範囲である private 以上で実装できます。
internal func action()
}
外部で使わせたくないクラスを定義するには
Swift言語
では仕様上、private
なクラスを用意してそれをpublic
なクラスで継承することはできません。
そのため、共通する基本機能を持った複数のクラスを継承で作成するときに、基本機能だけのクラスも公開しないといけません。
ただ、基本機能だけのクラスを外部からは使わせたくない場合があります。
そんな時には次のようにすることで、内部からの利用に限定したクラスを作る事ができます。
基本機能だけを持つクラスをpublic
で定義する
まず、最終的にはクラスをpublic
にしたいため、基底クラスもpublic
で定義する必要があります。
public class _MyBaseClass {
}
明らかに『外から使うことを想定していないクラス』と思わせるために、クラス名の先頭をアンダーバーで始めるのも良いかもしれません。
クラス内部だけで使う機能をprivate
で定義する
そして、基底クラスの機能をprivate
で定義します。そうすることで、継承後のクラスの外側からは利用できない機能を実装できます。
public class _MyBaseClass {
// 継承後のクラスで公開したくない機能は private で実装
private func _prepare()
// 継承後のクラスで公開したい機能は public で実装
public func action()
}
完成後のクラスで公開する必要のない機能では、名前をアンダーバーから始めるようにしておくと分かりやすくなるかもしれません。
基本機能だけのクラスを直接生成できないようにする
そして肝心なのが、基本機能だけのクラスを外部で勝手に作られないようにする方法です。
Swift言語
の仕様上、基本機能だけのクラスもpublic
で定義する必要がありますが、そのイニシャライザをprivate
で定義することで、外部からインスタンスを作ることができなくなります。
public class _MyBaseClass {
// イニシャライザを private で実装し、外部からのインスタンス化を防ぎます。
private init() {
}
private func _prepare()
public func action()
}
基本機能のクラスを継承したクラスを作成する
あとは、基本機能を継承した公開用のクラスをpublic
で作成します。
このとき、公開用の新しいクラスにはイニシャライザをpublic
で実装することで、外部から自由にインスタンスを生成できるようになります。基本機能のクラスと同じイニシャライザを公開したい場合は、イニシャライザをオーバーライドします。
// 公開用のクラスで、基本機能だけのクラスを継承します。
public class MyClass : _MyBaseClass {
// イニシャライザを public で実装します。
public override init() {
super.init()
}
}
これで、基本機能を継承した新しいクラスを実装できます。
基本機能だけを実装した_MyBaseClassクラス
もpublic
で定義しなければいけない都合、外部から存在だけは見えてしまいますが、それ自体のイニシャライザはpublic
には公開されていないため、不用意なインスタンス化を防げます。