これはなに
- 独自の拡張メソッドを認識しやすく、かつ名前が被らないようにするため、
ex
をもつプロトコルを作成してそれに準拠させるという実装スタイルがある - その実装をするときにハマったので共有
モダンなSwiftのExtensionについて
下記の記事を読めばOK。
この記事に影響を受けて自分のプロダクトにもモダンなExtensionを実装しようとした
- http://qiita.com/motokiee/items/e8f07c11b88d692b2cc5
- http://tech.vasily.jp/entry/swift_modern_extensions
どのような状況でエラーが起きたか
associatedtype
の名前が被っててエラー?
- 自分のアプリが
Kingfisher
を採用していて、Kingfisher
のプロトコルのtypealias
が自分の定義したプロトコルのassociatedtype
と同じだったので、試しに変更するとエラーは解消された - https://github.com/onevcat/Kingfisher/blob/master/Sources/String%2BMD5.swift#L32
- モダンな実装をしている外部ライブラリは
associatedtype CompatibleType
とかtypealias CompatibleType = Hoge
と書いてるのをよく見かけるので、そのまま参考にすると名前が被ってしまった
エラーを再現してみる
- どうしてエラーになるのか理解するために playground で同じような状況を再現してみる
- ACompatible の
associatedtype
の名前が、BCompatible と被るのでerror: type 'String' does not conform to protocol 'BCompatible'
とエラーがでる
Error_Contents.swift
import Foundation
struct AExample<Base> {
let base: Base
init(_ base: Base) {
self.base = base
}
}
struct BExample<Base> {
let base: Base
init(_ base: Base) {
self.base = base
}
}
protocol ACompatible {
associatedtype CompatibleType // 名前が同じ
var aEx: CompatibleType { get }
}
protocol BCompatible {
associatedtype CompatibleType // 名前が同じ、別の名前に変えると正常に動作する
var bEx: CompatibleType { get }
}
extension ACompatible {
var aEx: AExample<Self> {
return AExample(self)
}
}
extension BCompatible {
var bEx: BExample<Self> {
return BExample(self)
}
}
extension String: ACompatible { }
extension String: BCompatible { } // ここで error: type 'String' does not conform to protocol 'BCompatible' が出る
extension AExample where Base == String {
var count: Int {
return base.characters.count
}
}
extension BExample where Base == String {
var count: Int {
return base.characters.count
}
}
"hello".aEx.count
"bye".bEx.count
- エラーを直訳すると「String はプロトコル BCompatible に準拠していません」となる
-
extension String: BCompatible { }
で準拠してるのになんでだろうと結構悩んだ
associatedtype
の名前が被ってるからエラーという訳でもない?
-
associatedtype
の名前が同じだからエラーなのはイマイチ納得がいかないので色々と試すと、associatedtype
の名前が同じでも正常に動作するパターンがあった -
(追記:typoしてるだけでエラーになりました)ACompatibe
とBCompatible
のジェネリクスを別名に変更したり(NonError_1_Contents.swift) -
ACompatible
とBCompatible
の Extension で ○Exのプロパティの型を同じにする(NonError_2_Contents.swift)と正常に動作する - 下記のコードは playground で正常に動作する
NonError_2_Contents.swift
import Foundation
struct Example<Base> {
let base: Base
init(_ base: Base) {
self.base = base
}
}
protocol ACompatible {
associatedtype CompatibleType
var aEx: CompatibleType { get }
}
protocol BCompatible {
associatedtype CompatibleType
var bEx: CompatibleType { get }
}
extension ACompatible {
// aEx も Example<Base> で型を宣言
var aEx: Example<Self> {
return Example(self)
}
}
extension BCompatible {
// bEx も Example<Base> で型を宣言
var bEx: Example<Self> {
return Example(self)
}
}
extension String: ACompatible { }
extension String: BCompatible { }
extension Example where Base == String {
var count: Int {
return base.characters.count
}
}
"hello".aEx.count // 5
"bye".bEx.count // 3
まとまってないけど、まとめ
- なんでこうなるのかと聞かれると、うまく説明できないのでまだ困っている
- またアクセス修飾子の違いで条件がいろいろ変わるので、正確に検証できているのかと言われると怪しい
- ひとまず、プロトコルの
associatedtype
は他のプロトコルのものと被らないようにした - 詳しい人が教えてくれると嬉しい