LoginSignup
15
17

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-08-27

書いたこと

  • 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から値へのアクセスは、さほど大したコストではありませんが、
繰り返し処理が行われる場合はご用心です。

参考書

15
17
0

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
15
17