通常 Swift で protocol
を定義した場合、その protocol
は class
のみならず struct
や enum
などにも適用できてしまいます。ほとんどの場合これは非常に便利ですが、時々逆に不便になることもあります。例えば Delegate を protocol
で定義した際、値型の struct
などにも使えるため、var delegate = Delegate?
が書けても weak var delegate = Delegate?
が書けません。値型に weak
がそもそも意味ないからです。もちろん Delegate を struct
にするという手もありますが、Delegate の性質上、参照型の方が嬉しいという場面が非常に多いです、例えば MVC アーキテクチャで UIViewController
を Model の Delegate にしたいときとかまさにそれです。そういった場合、あらかじめこのプロトコルは class
だけにしか使えないとわかっていても、通常の protocol
の宣言の仕方ではそれがわからないため、weak
が使えずメモリリークにつながりかねません。
例えば下記のようなソースコード見てみればわかると思います:
protocol ADelegate {
}
class A {
var delegate: ADelegate?
deinit {
print("A deinited")
}
}
class P {
let child: A
init(child: A) {
self.child = child
}
deinit {
print("P deinited")
}
}
extension P: ADelegate {
}
var a: A? = A()
var p: P? = P(child: a!)
a!.delegate = p
p = nil
a = nil
上記のコードを Playground で実行してみればわかると思いますが、A
も P
も deinit
が呼び出されていません。そのつもりはなかったが結局循環参照が起こってしまうため a!.delegate
が参照カウンターを保持しており結局 a
も p
もメモリが解放されません。これを解決するには a = nil
の前に a!.delegate = nil
を入れないといけません。でもこれだと面倒だし忘れがちだしあまりいい解決法とは言い難いですね。
というわけで、ここはこの delegate
は参照型だと最初から分かっている場合、やはり weak
を使いたいのですが、ここで登場してもらいたいのがこの Class-Only Protocol です。
実は Class-Only Protocol の宣言は非常に簡単で、定義した protocol
に class
の継承を付け加えるだけで OK です。つまりこう:
protocol ADelegate: class {
}
です。こうすることによって、ADelegate
は class
のみに適用される protocol
とみなされ、当然 ADelegate
の変数も weak
が使えるようになります。
これで最初のコードをちょっと修正してみましょう:
protocol ADelegate: class {
}
class A {
weak var delegate: ADelegate?
deinit {
print("A deinited")
}
}
class P {
let child: A
init(child: A) {
self.child = child
}
deinit {
print("P deinited")
}
}
extension P: ADelegate {
}
var a: A? = A()
var p: P? = P(child: a!)
a!.delegate = p
p = nil
a = nil
これでもう一度 Playground で実行してみると、deinit
がちゃんと両方の class
に呼び出されたことが確認できます。めでたしめでたし。
ちなみにもう一つ嬉しいのは、参照型だとわかったから、protocol
には mutating func
の宣言が要らなくなります。すべて func
のみで OK ですので、これで mutating func
のメソッドが入ってる protocol
を class
で対応した時も変数は var
使わなければなりません、という問題が根本的に解決されます。例えば下記のようなものがあるとします:
protocol SomeProtocol {
var aVariable: Int { get set }
mutating func increase()
}
extension SomeProtocol {
mutating func increase() {
self.aVariable += 1
}
}
class SomeClass: SomeProtocol {
var aVariable: Int
init() {
self.aVariable = 0
}
}
let some = SomeClass()
some.increase()
ここで increase()
は self.aVariable
の値を変更しているのでどうしても mutating func
で宣言しないといけませんが、そのせいで本来 let
で宣言できた some
が increase()
を使いたい場合 var
で宣言しないといけなくなるのでこのコードではエラーが出ます。これも SomeProtocol
を Class-Only Protocol として宣言すれば解決されます:
protocol SomeProtocol: class {
var aVariable: Int { get set }
func increase()
}
extension SomeProtocol {
func increase() {
self.aVariable += 1
}
}
class SomeClass: SomeProtocol {
var aVariable: Int
init() {
self.aVariable = 0
}
}
let some = SomeClass()
some.increase()
ちなみに C# では Abstract Class(抽象クラス)という、いわば protocol
と非常に似ているものがあって、要するに class
のようにプロパティやメソッドなどの宣言があるがその定義や実現は一切せず、すべてはこの Abstract Class を継承した子クラスに任せており、当然ながら Abstract Class 自身のインスタンスも作ることができません。Swift では Abstract Class と言う概念がありませんが、Class-Only Protocol を使えばある意味 Abstract Class とほぼ同じようなものが作れるのではないでしょうか。
というわけで、もし class
専用の protocol
を作りたい場合は、ぜひこの Class-Only Protocol の宣言を活用しましょう。
2018/11/06 追記
The Swift Forums によりますと、今後は : class
が非推奨となり、代わりに : AnyObject
の方が推奨となるそうです、なぜならば実際欲しい動きは「クラスに限定」よりも、「参照型に限定」ですので:
protocol SomeProtocol: AnyObject {
// ...
}