アクセスコントロールとは、プロパティやメソッドへのアクセスに制限を設けることです。この機能によりオブジェクト指向のカプセル化レベルをより細かく制御することができます。
Swiftのアクセスコントロールは他のオブジェクト指向言語と少し違っているところもあります。プロパティやメソッド、イニシャライザ、サブスクリプトにアクセスレベルを設定できる他、それらが属するクラス、構造体、列挙型自体にも設定できます。アクセス制限はそれらのクラスや構造体の単位ではなく、それらが記述されたソースファイルレベルで設定されます。
設定できるアクセスレベルは次の3つです。
- public
-
最も緩いアクセスレベルです。自身が属するモジュールのソースファイルからだけでなく、別のモジュールのソースファイルからもアクセスできることを許可します。フレームワークのAPIを定義する場合等に使用します。
- internal
-
自身が属するモジュールと同じモジュールのソースファイルからのみアクセス可能です。アプリやフレームワークを作成する際に内部構造を定義する場合等に使用します。internalはデフォルトのアクセスレベルです。明示的にアクセスレベルを指定しない場合はinternalと看做されます。そのため一般的な用途ではアクセスレベルを気にせずに記述することも可能です。
- private
-
最も厳しいアクセスレベルです。同じソースファイルからしかアクセスできません。モジュールの使用者から内部構造を隠蔽したい場合等に使用します。
モジュールとはアプリやフレームワークの配布の単位で、Swiftのimportを使って別のモジュールに取り込むことができるまとまりを表します。
Xcodeでアプリやフレームワークといったビルドターゲットを作成しますが、これらがそれぞれ独立した1つのモジュールということになります。
そして、それらのモジュールを構成するためにソースファイルを記述することになります。一般的に1つの型の定義は1つのソースファイルに記述しますが、1つのソースファイルに複数の型を定義することもできます。その場合、アクセスレベルにprivateが指定してあっても同じソースコード内の別の型からアクセスできることになります。
他のモジュールへの機能公開を目的としない場合は、アクセスレベルを設定せずに開発することも可能です。デフォルトのアクセスレベルであるinternalが自動的に付与されるのでほとんどの用途でアクセスレベルを意識することなく開発できます。もちろん、同じモジュールの他のソースコードからのアクセスを禁止するためにprivateを設定しても構いません。
アクセスレベルの基本原則
Swiftのアクセスレベルには、既に設定されているアクセスレベルをより厳しくするアクセスレベルへ設定できないという原則があります。例えば、
- publicな変数をinternal又はprivateなアクセスレベルを持つ様に再定義することはできません。
- 関数のアクセスレベルを、その関数の引数や戻り値に設定されているアクセスレベルより厳しいアクセスレベルにすることはできません。
アクセスコントロールの書式
型やインスタンスにアクセスレベルを設定する場合は、それらの定義の前に、public, internal, privateの何れかの修飾子を記述します。
public class SomePublicClass {}
internal class SomeInternalClass {}
private class SomePrivateClass {}
public var somePublicVariable = 0
internal let someInternalConstant = 0
private func somePrivateFunction() {}
アクセスレベルを明示しない場合は、internalがデフォルトのアクセスレベルになるので、上のSomeInternalClassとsomeInternalConstantは次のように、アクセスレベルを指定しなくても同じになります。
class SomeInternalClass {}
let someInternalConstant = 0
独自の型
独自の型を定義する時にアクセスレベルを指定することができますが、例えばその型にprivateを指定した場合、その型は同じソースコード内でしか使用することができないということになります。
型に対して設定されたアクセスレベルは、その型のプロパティやメソッド、イニシャライザ、サブスクリプトのデフォルトのアクセスレベルに影響します。例えば、型のアクセスレベルをprivateにした場合、その型のプロパティやメソッドのデフォルトのアクセスレベルもprivateになります。また、型のアクセスレベルをinternalやpublicにした場合、その型のメンバのデフォルトのアクセスレベルはinternalになります。(型のアクセスレベルがpublicでもメンバのデフォルトはpublicではなく、internalです。)
型のアクセスレベルがpublicでもメンバのデフォルトはpublicではなく、internalです。メンバのアクセスレベルをpublicにしたい場合は、それらに明示的に指定する必要があります。
これにより、不用意に他のモジュールへ内部実装を公開することを防ぐことができます。
タプル型
タプルのアクセスレベルは、その構成要素のアクセスレベルの中で最も厳しいアクセスレベルになります。
private let s = "Hello!"
public let i = 100
let t = (s, i) // tのアクセスレベルはprivate
関数型
関数のアクセルレベルは、その関数の引数や戻り値の中で最も厳しいアクセスレベルになります。
publicなクラスと、privateなクラスのタプルを返す関数があった場合、その関数は、privateな関数となります。
public class SomePublicClass {
:
}
private class SomePrivateClass {
:
}
private func someFunction() -> (SomePublicClass, SomePrivateClass) {
let p = SomePublicClass()
let r = SomePrivateClass()
:
return (p, r)
}
関数からの戻り値であるタプル型のアクセスレベルがprivateになるため、関数のアクセスレベルもprivateになります。そしてこの場合。関数名の前にprivateが必要です。privateをつけて宣言しないとエラーになります。また、publicやinternalをつけて宣言した場合もエラーになります。
列挙型
列挙型の各caseの値のアクセスレベルは、列挙型自体のアクセスレベルと同じになります。個々のcaseの値にアクセスレベルを指定することは出来ません。
信号機を表す次の列挙型はpublicが指定されているため、個々の値のアクセスレベルも全てpublicになります。
// 信号機
public enum Signal {
case Blue // 青
case Yellow // 黄
case Red // 赤
}
列挙型に明示的に型を指定する場合、その型のアクセスレベルは、列挙型自体のアクセスレベル以上である必要があれます。例えば、internalな列挙型の値としてprivateなアクセスレベルの型を指定することはできません。
ネストした型
privateな型の内部で定義されたネストした型のアクセスレベルはprivateになります。また、public若しくはinternalな型の内部で定義されたネストした型のアクセスレベルはinternalになります。
もし、publicな型の内部で定義したネストした型のアクセスレベルをpublicにしたい場合は、明示的にpublicを指定する必要があります。
public class somePublicClass {
public class someNestedClass {
:
}
}
クラスの継承
あるクラスを継承してサブクラスを作成する場合、スーパークラスのアクセスレベルより緩いアクセスレベルを設定することはできません。つまり、internalなクラスを継承して、publicなクラスを定義することはできません。
サブクラスからアクセス可能であれば、スーパークラスのプロパティやメソッドをオーバーライドすることは可能です。
オーバーライドにより、スーパークラスのアクセスレベルより緩いアクセスレベルにすることができます。次の例では、スーバークラスのprivateなメソッドをinternalなアクセスレベルに変更しています。
public class A {
private func someMethod() {
:
}
}
internal class B: A {
override internal func someMethod() {
:
}
}
また、サブクラスのメソッドのアクセスレベルより厳しいアクセスレベルのメソッドも、ソースからアクセス可能であれば呼び出すことができます。
public class A {
private func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
上の場合は、internalなメソッドから、スーパークラスのprivateなメソッドを呼び出していますが、privateなメソッドでも同じソース内であればアクセス可能なので、サブクラスからの呼び出しも可能となります。
上のクラスAとBがそれぞれ別のソースファイルに記述されている場合は、クラスBからクラスAのprivateなメソッドの呼び出しはできなくなるのでコンパイルエラーになります。
定数、変数、プロパティ、サブスクリプト
定数、変数、プロパティは、その型のアクセスレベルより緩い設定にすることはできません。例えば、privateな型のpublicなプロパティを宣言することはできません。
また同様に、サブスクリプトのアクセスレベルを、そのインデックスや戻り値の型より緩くすることはできません。
次の様に、privateな型の変数はprivateとして宣言しなくてはなりません。
private var privateInstance = SomePrivateClass()
ゲッターとセッター
ゲッターとセッターのアクセスレベルは、対象となる定数や変数のアクセスレベルと同じになります。
セッターのアクセスレベルをゲッターより厳しくすることができます。
次の例では、構造体にもプロパティにも明示的にアクセスレベルを指定していないので、アクセスレベルはデフォルトのinternalになりますが、プロパティのセッターにはprivateを設定しています。
/* 値の変更を追跡する文字列 */
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits++
}
}
}
この構造体のnumberOfEditsプロパティは、同じモジュールの別ソースファイルから値を読み取ることはできますが、値の変更は同じソースファイルからしかできないことになります。
この構造体のインスタンスを生成して、valueプロパティの値を変更するたびにnumberOfEditsがインクリメントされます。
var stringToEdit = TrackedString()
stringToEdit.value = "この文字列は追跡されます。"
stringToEdit.value += "値を変更するたびに変更回数が加算されます。"
stringToEdit.value += "さらに変更します。"
println("変更回数は\(stringToEdit.numberOfEdits)回です。")
/* 実行結果
変更回数は3回です。
*/
ゲッターとセッターに明示的にアクセスレベルを設定することもできます。上のTrackedString構造体を別のモジュールからアクセスできるようにするにはpublicを指定します。
public struct TrackedString {
public private(set) var numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits++
}
}
public init() {}
}
この場合は、型、プロパティ、イニシャライザ全てにpublicが指定されているので、他のモジュールのソースファイルからもインスタンスを生成し、プロパティにアクセスすることができます。但し、numberOfEditsのセッタにはprivateが指定されているので、同じモジュールのソースファイルも含め他のどのソースファイルからもnumberOfEditsの値を変更することはできません。
publicな型のプロパティのデフォルトのアクセスレベルはinternalです。他のモジュールのソースファイルからプロパティやメソッドにアクセスできるようにするには、上の例のように明示的にpublicを指定してやる必要があります。
イニシャライザ
イニシャライザのアクセスレベルはその型のアクセスレベルと同じか、より厳しいアクセスレベルを設定できます。但し、必須イニシャライザ(requiredをつけたイニシャライザ)は、型と同じアクセスレベルにする必要があります。
関数やメソッドの場合と同様、イニシャライザの引数のアクセスレベルをイニシャライザ自体のアクセスレベルより厳しくすることはできません。
Swiftでは、スーパークラスも含めプロパティにデフォルト値が与えられていて且つイニシャライザが明示されていない場合は、引数をとらないイニシャライザ(デフォルトイニシャライザ)が自動的に生成されますが、このデフォルトイニシャライザのアクセスレベルは、その型のアクセスレベルと同じものになります。
publicな型のデフォルトイニシャライザのアクセスレベルはinternalになります。デフォルトイニシャライザのアクセスレベルをpublicにしたい場合は、引数をとらないイニシャライザをpublicをつけて明示的に記述する必要があります。
構造体を定義する時に自動的に生成されるメンバワイズイニシャライザは、privateなアクセスレベルをもつプロパティがあればイニシャライザのアクセスレベルもprivateになります。それ以外の場合のアクセスレベルはinternalです。もし、publicなメナバワイズイニシャライザが必要な場合は、上で説明したデフォルトイニシャライザの場合と同様、明示的にメンバワイズイニシャライザを記述する必要があります。
プロトコル
プロトコルにアクセスレベルを設定したい場合は、プロトコルを定義する時に指定します。
プロトコルで定義したメソッドやプロパティのアクセスレベルは、プロトコル自体のアクセスレベルと同じものになります。プロトコルのアクセスレベルと異なるアクセスレベルを指定することはできません。
publicなプロトコルで宣言されたメソッドやプロパティのアクセスレベルはpublicになります。他のpublicな型のメンバのデフォルトのアクセスレベルはinternalですが、プロトコルの場合は異なっています。
他のプロトコルを継承したプロトコルを定義する場合、そのプロトコルのアクセスレベルは、継承するプロトコルで許可されたものになります。internalなプロトコルを継承してpublicなプロトコルを定義することはできません。
型をプロトコルへ適合させる場合、そのプロトコルのアクセスレベルより緩いアクセスレベルを指定できますが、その場合はその型へアクセス可能な範囲はプロトコルのアクセスレベルに従うことになります。例えば、internalなプロトコルに適合するpublicな型を定義した場合、その型は同じモジュール内でしか使用できません。
あるプロトコルに適合する型のアクセスレベルは、そのプロトコルとより型の厳しい方のアクセスレベルに従うことになります。
エクステンションにより既存の型をプロトコルへ適合させる場合、その型で実装するプロトコルで定義されたプロパティやメソッドのアクセスレベルが、少なくともプロトコルのアクセスレベルと同じである必要があります。例えば、publicな型をinternalなプロトコルへ適合させる場合は、その型で実装するプロトコルのプロパティやメソッドのアクセスレベルもinternalである必要があります。
プロトコルへの適合はグローバルなものになります。1つのプログラム内で、ある型を同じプロトコルへ重複して適合させることはできません。
エクステンション
エクステンションを使って型を拡張する場合、追加されるメンバは型自体のアクセスレベルに従うことになります。例えばprivateな型に追加したメソッドはprivateに、publicな型に追加したメソッドはinternalになります。
エクステンションを定義する時に型に明示的にアクセスレベルを指定した場合、追加されるメンバのデフォルトのアクセスレベルも変わります。例えば、privateなエンステンションを定義すると追加するメソッドのデフォルトのアクセスレベルはprivateになります。追加するメンバに個々にアクセスレベルを指定してデフォルトのアクセスレベルを上書きすることも可能です。
エクステンションを使って、既存の型をあるプロトコルへ適合させる場合、アクセスレベルはそのプロトコルのアクセスレベルに従うことになるので、明示的にアクセスレベルを指定することはできません。追加するメンバのデフォルトのアクセスレベルもプロトコルのアクセスレベルに従います。
ジェネリクス
ジェネリクス関数やジェネリクス型のアクセスレベルは、その関数や型につけられたアクセスレベルと、パラメータとして使用される型のアクセスレベルの中で最も厳しいアクセスレベルになります。
タイプエイリアス
タイプエイリアスを使って型の別名を定義する場合、その型のアクセスレベルは、元の型のアクセスレベルと同じか、より厳しいアクセスレベルを指定することができます。例えば、internalな型の別名としてprivateな型を定義することはできますが、internalな型の別名としてpublicな型を定義することはできません。このルールはタイプエイリアスを使って、プロトコルの中で関連型(Associated Types)を定義する場合も同様です。