Swiftでコードを書く際、任意のクラスをdelegate
としてweak
にしたい場合は、class
プロトコルを採用するコードを書かなければ ‘weak’ cannot be applied to non-class type というコンパイルエラーとなる。
(言い換えると、class
プロトコルに準拠したクラスはweak
にできるようになりdelegate
として扱える)
例がこれ
protocol SomeProtocolOne {
func run() -> Void
}
protocol SomeProtocolTwo : class {
func run() -> Void
}
class SomeView {
weak var delegate: SomeProtocolOne? // これはコンパイルエラー ‘weak’ cannot be applied to non-class type
weak var delegate: SomeProtocolTwo? // これは大丈夫
}
この理由は何か、「そういうものと覚えておけばいい」と思っていたんだけど納得できる理由のためにおさらいしてみる。
おさらい
まず、キーワード : class
は見た目通りclass
プロトコルを採用していると言ってもいい。
また、プロトコルの採用をクラス限定に出来ることも意味していて、"class-only protocol"と名付けられている。つまり 構造体のStruct
や 列挙体のEnum
でこのプロトコル(SomeProtocolTwo
)を採用することは出来なくなるということ。
前提
Swiftでは参照型と値型に分かれている。
Swiftの公式ブログから引用
Types in Swift fall into one of two categories: first, “value types”, where each instance keeps a unique copy of its data, usually defined as a struct, enum, or tuple. The second, “reference types”, where instances share a single copy of the data, and the type is usually defined as a class. In this post we explore the merits of value and reference types, and how to choose between them.
最近できたアクターも参照型なので箇条書きにしてみると次のようになる。
- 参照型
- クラス
- アクター
- 値型
- 構造体
- 列挙体
- タプル
ここで参照型は3つの参照の仕方に分かれる
- 参照型
- 参照の仕方
- 強参照
- strong
- デフォルトでstrong
- strong
- 弱参照
- weak
- 参照先が存在しない場合に参照しようとされると自動でnilになってる
- weak
- 非所有参照
- unowned
- 参照先が存在しない場合に参照しようとされると解放済みへのアクセスとなりクラッシュする
- unowned
- 強参照
- 参照の仕方
もちろん値型は別の変数に代入されると参照されずコピーされるので参照の仕方という分類はない(日本語の意味で「参照」というのは閲覧する的な意味があるが、プログラミング的な意味でのオブジェクトの参照とは、単一のオブジェクト自身を複数間で共有するというような意味)。どうでもいいけどリファレンスされるという変な言い方のほうが日本語に邪魔されなくていいかもしれない。
非所有参照というのは解放されたらアクセスしてはいけないので危険なのに、なぜそんなものがあるのだろうかと考えてしまうのは無理もない。それはたとえば「世の中の車はなぜ全部オートマ車じゃないんだろう?」と思うのに似ているのかもしれない。もしくは「パンがなければケーキを食べればいいじゃない」かもしれない。
考察
つまり、弱参照にできるのは参照型である必要があり、クラスやアクターでなければいけない。言い換えると構造体や列挙体やタプルで弱参照にしようとすると困るのでエラーになり、プロトコル側で今回の"class-only protocol"を使う必要が出てくる。
また言い換えてみると、そのプロトコルは参照が確か使えないということを"class-only protocol"へプロトコルが準拠することでプロトコルの制約を表現している。
備考
すごい昔のベータの頃
逆にそのプロトコルは参照が使えないということを"class-only protocol"への準拠で表現したら賢すぎるやろ、もっと長い記述で説明してくれたらわかりやすいかな、と思ったら、Swift Beta4以前は長いアトリビュートが必要だったのが、Beta5から簡潔になったらしい。
// Beta5以降
protocol ProtocolNameDelegate: class {
}
// Beta4以前
@class_protocol protocol ProtocolNameDelegate {
}
現在はclass
を指定するのではなくAnyObject
を指定することが推奨されている
protocol ProtocolNameDelegate: AnyObject {
}
理由は下記
参考URL