Swift
Swift2.2

swift Protocolを型宣言して使う場合は、ご用心を

More than 1 year has passed since last update.


書いたこと


  • swiftのProtocolには以下の2種類があること


    • 型宣言が出来るProtocol

    • 型宣言が出来ないProtocol



  • Protocolを型として使う場合の注意


Swiftの2種類のProtocol


1. 型宣言が出来るProtocol

Self、関連型を持たないProtocolは型宣言として使えます。


a.swift

  protocol A {}

// 型としてプロトコルAを宣言することができます
func f(a: A) { ... }



2. 型宣言が出来ないProtocol

Self、関連型を持つProtocolは型宣言として使えません。


b.swift

protocol B {

associatedtype Element
}

// コンパイルエラーが発生します。
// protocol 'B' can only be used as a generic constraint because it has Self or associated type requirements
func f(b: B) { }


エラー内容からもSelf,関連型をもつプロトコルは型制約でしか使えないよと出力されています。


Protocolで型宣言した場合は、実行コストが発生する

Protocolを型として宣言する場合は、以下の注意が必要です。


  • コンパイラの型推測による型変換が行われるわけではない

  • 実行時にコストが発生します (Protocolインスタンスの生成、値へのアクセス)

以下は上記の検証コードです。

Advanced Swiftから拝借しております。

使うProtocol


test.swift

// 関連型、Selfを持たないProtocolを定義

protocol NumberGeneratorType {
mutating func generateNumber() -> Int
}

// 呼び出されるたびに値をインクリメントするGenerator
struct IncrementingGnerator: NumberGeneratorType {
var n: Int
init(start: Int) {
self.n = start
}
mutating func generateNumber() -> Int {
self.n += 1
return n
}
}


次にProtocolを型宣言として引数に受け取る関数と、Genericsを利用して具体型を引数に受け取る関数を実装し、処理速度を比較します。


test.swift

// Protocolでの計算

func generateUsingProtocol(g: NumberGeneratorType, count: Int) -> Int {
var generator = g
return 0.stride(to: count, by: 1).reduce(0) { total, _ in
return total &+ generator.generateNumber()
}
}

// Genericでの計算
func generateUsingGeneric<T: NumberGeneratorType>(g: T, count: Int) -> Int {
var generator = g
return 0.stride(to: count, by: 1).reduce(0) { total, _ in
total &+ generator.generateNumber()
}
}


Generics vs Protocol型宣言


test.swift

// swift -O test.swift で最適化して実行します

time("Generic incr") {
// 0s
generateUsingGeneric(IncrementingGnerator(start: 1), count: 922337203)
}

time("Protocol incr") {
// 4s かかる ※ MacBook Air (11-inch, Mid 2013)
generateUsingProtocol(IncrementingGnerator(start: 1), count: 922337203)
}


上記は最適化して実行した場合、Protocolを型として宣言している関数では 4秒 かかっているのに対して、Genericsの方は、0秒 です。

Advanced Swiftの調査によると、Protocolを型として宣言した場合は、内部の処理において、Protocolは具体型として振る舞うようです。

(暗黙的に、引数に渡された値へのポインタを持ったProtocolインスタンスが生成される。)

※ ただしSwift v2.2において、公式ドキュメントに記載されているわけではありません。


終わり

関連型、Selfを持たないProtocolは変数の型宣言に用いられるだけでなく、

返り値の型としても宣言することができます。

またProtocolから値へのアクセスは、さほど大したコストではありませんが、

繰り返し処理が行われる場合はご用心です。


参考書