Menu

Category

Archive

logo


The Swift Programming Language ~ Protocols [基本編]

2014-06-30 16:00:00 +0900
  • このエントリーをはてなブックマークに追加

iBooks にある “The Swift Programming Language” の勉強メモ。Objective-C と C を普段書いている自分から、ちょっと馴染みがないものを特にまとめておきます。目次は こちら

今回は、基本的な Protocols の宣言に関して。

Protocols

プロトコルは、クラス・構造体・Enumeration が実装すべきメソッド・プロパティ等の必要要件を定義します。このプロトコルは、実際には、実装を提供しません。ただ宣言するだけです。実際に実装するのは、このプロトコルに従うクラス・構造体・Enumeration です。

1. Protocol Sytax

クラス等と同じようにプロトコルを定義できます。

1
2
3
protocol SomeProtocol {
    // protocol definition goes here
}

そして、タイプ(クラス・構造体・Enumeration)は、コロンで区切り、プロトコルに準拠していることを準拠(adopt)していることを宣言します。複数ある場合は、カンマで区切ります。クラスにプロトコルを採用したく、そのクラスがスーパークラスを持っている場合は、コロンの後にスーパークラスを書き、その後にカンマで区切りプロトコルを宣言します。

1
2
3
4
5
6
7
struct SomeStructure: FirstProtocol, AnotherProtocol {
    // structure definition goes here
}

class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
    // class definition goes here
}

2. プロパティの要求

プロトコルは、あるタイプに対して、インスタンスプロパティ・タイププロパティをある名前で持たせることを求めることができます。プロトコルは、Computed プロパティか Stored プロパティであるかは指定しません。プロトコルは、ただ名前とタイプのみを求めます。また、読み込み専用か、読み込み書き込み可能かを指定します。

プロパティの要件は、変数として宣言すれば基本的に問題ありません。下記のように読み込み専用か読み込み書き込み可能かを指定します。

1
2
3
4
protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

また、タイププロパティをプロトコルの中で定義する場合は、class キーワードを使用します。これは構造体・Enumeration においては、通常 static を使用しないといけないですが、class で統一して問題ありません。

1
2
3
protocol AnotherProtocol {
    class var someTypeProperty: Int { get set }
}

簡単な例を見てみます。

1
2
3
4
5
6
7
8
9
protocol FullyNamed {
    var fullName: String { get }
}

struct Person: FullyNamed {
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName is "John Appleseed"

これはフルネームを持つべきだというプロトコルです。Person 構造体は、これを Stored プロパティとして定義しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Starship: FullyNamed {
    var prefix: String?
    var name: String
    init(name: String, prefix: String? = nil) {
        self.name = name
        self.prefix = prefix
    }
    var fullName: String {
    return (prefix ? prefix! + " " : "") + name
    }
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName is "USS Enterprise"

この Starship クラスは、フルネームを Computed プロパティとして定義しています。

これらのプロトコルが要求しているプロパティを実装していない場合、Swift はコンパイルエラーを吐きます。

3. メソッドの要求

次にプロトコルがタイプメソッド・インスタンスメソッドを要求する場合をみていきます。プロトコル内では、通常のメソッドのように宣言されます。しかし、{} を持ちません。また、引数のデフォルト値を持つことはできません。可変引数のメソッドは可能です。

タイプメソッドの場合は、タイププロパティの時と同じように class キーワードを先に記述します。

1
2
3
protocol SomeProtocol {
    class func someTypeMethod()
}

1 つ例をみてみます。

1
2
3
protocol RandomNumberGenerator {
    func random() -> Double
}

インスタンスメソッドのため、class はありません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = ((lastRandom * a + c) % m)
        return lastRandom / m
    }
}
let generator = LinearCongruentialGenerator()
println("Here's a random number: \(generator.random())")
// prints "Here's a random number: 0.37464991998171"
println("And another one: \(generator.random())")
// prints "And another one: 0.729023776863283"

4. Mutating メソッドの要求

メソッドがそのインスタンスを変更する場合もあります。構造体・Enumeratio のインスタンスメソッドの場合、mutating キーワードを使用します。クラスの場合は、必要がありませんが、プロトコルは構造体や Enumeration でも使用されるので、プロトコル側では mutating を書いておくことで、全てのケースに対応できます。

1
2
3
protocol Togglable {
    mutating func toggle()
}

toggle とはあるステートを変更することです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum OnOffSwitch: Togglable {
    case Off, On
    mutating func toggle() {
        switch self {
        case Off:
            self = On
        case On:
            self = Off
        }
    }
}
var lightSwitch = OnOffSwitch.Off
lightSwitch.toggle()
// lightSwitch is now equal to .On

プロトコル側で mutatign と指定されているので、Enumeration の中で Mutating メソッドとして実装されています。