2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

モダンなSwiftのExtension(TargetedExtensions)を実装するときハマったところ

Last updated at Posted at 2017-05-20

これはなに

  • 独自の拡張メソッドを認識しやすく、かつ名前が被らないようにするため、ex をもつプロトコルを作成してそれに準拠させるという実装スタイルがある
  • その実装をするときにハマったので共有

モダンなSwiftのExtensionについて

 下記の記事を読めばOK。
 この記事に影響を受けて自分のプロダクトにもモダンなExtensionを実装しようとした

どのような状況でエラーが起きたか

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 の名前が同じでも正常に動作するパターンがあった
  • ACompatibeBCompatible のジェネリクスを別名に変更したり(NonError_1_Contents.swift) (追記:typoしてるだけでエラーになりました)
  • ACompatibleBCompatible の 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 は他のプロトコルのものと被らないようにした
  • 詳しい人が教えてくれると嬉しい
2
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?