【Swift】標準で定義されているプロトコルについて

# はじめに

標準で定義されているプロトコルについて簡単にまとめてみました

https://developer.apple.com/documentation/swift/basic-behaviors

# 内容

### Equatableプロトコル
Equatableに準拠することで、値が等しいかどうかを比較できる

```swift
protocol Equatable
```

Equatable に準拠するするには、
構造体の場合、格納されているすべてのプロパティが Equatable に適合していること（enum の場合、関連するすべての値が Equatable に適合していること）。enum の場合、関連する値はすべて Equatable に準拠する必要がある(関連する値がない enum は、宣言がなくても Equatable 準拠です)

```swift
class StreetAddress {
    let number: String
    let street: String
    let unit: String?

    init(_ number: String, _ street: String, unit: String? = nil) {
        self.number = number
        self.street = street
        self.unit = unit
    }
}
```

👇

```swift
extension StreetAddress: Equatable {
    static func == (lhs: StreetAddress, rhs: StreetAddress) -> Bool {
        return
            lhs.number == rhs.number &&
            lhs.street == rhs.street &&
            lhs.unit == rhs.unit
    }
}
```



### Comparableプロトコル

Comparable に準拠することで、値の大小関係を比較することができる

```swift
protocol Comparable : Equatable
```

Comparable に準拠するするには、型の静的メソッドとして<と==演算子を定義

```swift
struct Date {
    let year: Int
    let month: Int
    let day: Int
}
```

👇

```swift
extension Date: Comparable {
    static func < (lhs: Date, rhs: Date) -> Bool {
        if lhs.year != rhs.year {
            return lhs.year < rhs.year
        } else if lhs.month != rhs.month {
            return lhs.month < rhs.month
        } else {
            return lhs.day < rhs.day
        }
    }
    static func == (lhs: Date, rhs: Date) -> Bool {
        return lhs.year == rhs.year && lhs.month == rhs.month
            && lhs.day == rhs.day
    }
}
```

### Identifiableプロトコル

Identifiable に準拠することで、クラスまたは値の型にIDの概念を提供することができる

```swift
protocol Identifiable
```

IDは以下のいずれかの特性を持つ
- UUIDのように常に一意であることが保証される
- データベースのレコードキーのように、環境ごとに永続的に一意である
- グローバルインクリメント整数のように、プロセスの有効期間中一意である
- オブジェクト識別子のように、オブジェクトの寿命に対して一意である
- コレクションインデックスのように、現在のコレクション内で一意である

Identifiable に準拠するするには、`var id`を定義

```swift
associatedtype ID : Hashable

var id: Self.ID
```

### Hashableプロトコル

Hashableプロトコルに準拠した型は、その値をもとにハッシュ値（もとの値から特定のアルゴリズムで算出されるInt型の値）を計算できる

```swift
protocol Hashable : Equatable
```
独自のカスタム型をセットまたは辞書のキー型として使用するには、その型に Hashable 準拠を追加する

Hashable に準拠するには、`hash(into:)`メソッドを実装する
`hash(into:)`の実装では、提供された Hasher のインスタンスに対して、 combine(_:) を呼び出す

```swift
struct GridPoint {
    var x: Int
    var y: Int
}
```

👇

```swift
extension GridPoint: Hashable {
    static func == (lhs: GridPoint, rhs: GridPoint) -> Bool {
        return lhs.x == rhs.x && lhs.y == rhs.y
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(x)
        hasher.combine(y)
    }
}
```


### CustomStringConvertibleプロトコル

CustomStringConvertibleプロトコルに準拠した型は、インスタンスを文字列に変換する際、独自の表現を提供ができる

```swift
protocol CustomStringConvertible
```

Hashable に準拠するするには、`var description: String`を定義

```swift
struct Point {
    let x: Int, y: Int
}

let p = Point(x: 21, y: 30)
print(p)
// Prints "Point(x: 21, y: 30)"
```

👇

```swift
extension Point: CustomStringConvertible {
    var description: String {
        return "(\(x), \(y))"
    }
}

print(p)
// Prints "(21, 30)"
```

### CaseIterableプロトコル

CaseIterable プロトコルに準拠する型は、通常、associated valueを持たない列挙型
CaseIterable 型を使用する場合、型の allCases プロパティを使用すると、その型のすべてのケースのコレクションにアクセスすることができる

```swift
protocol CaseIterable
```

associated valueや `@available` 属性がない列挙型の場合、CaseIterable 要件の実装を自動的に提供することができる

```swift
enum CompassDirection: CaseIterable {
    case north, south, east, west
}

print("There are \(CompassDirection.allCases.count) directions.")
// Prints "There are 4 directions."
let caseList = CompassDirection.allCases
                               .map({ "\($0)" })
                               .joined(separator: ", ")
// caseList == "north, south, east, west"
```

### RawRepresentableプロトコル

RawRepresentableプロトコルに準拠した型は、元のタイプの値を失うことなく、カスタムタイプと関連するタイプを切り替えることができる
RawRepresentableプロトコルは主に、rawValueを持つ列挙型とオプションセットに関連している

```swift
protocol RawRepresentable
```

##### rawValueを持つ列挙型
文字列、整数などの列挙型は、自動的にRawRepresentableとなる

```swift
enum Counter: Int {
    case one = 1, two, three, four, five
}

for i in 3...6 {
    print(Counter(rawValue: i))
}
// Prints "Optional(Counter.three)"
// Prints "Optional(Counter.four)"
// Prints "Optional(Counter.five)"
// Prints "nil"

```

##### オプションセット

オプションセットは、OptionSetプロトコルを使った継承により、全てRawRepresentableに準拠しています。オプションセットを使うにせよ、自分で作るにせよ、オプションセットのインスタンスのrawValueを使って、そのインスタンスのビットフィールドを格納します。したがって、rawValueは UInt8 や Int など、FixedWidthInteger プロトコルに準拠した型である必要があります。たとえば、Direction 型は、ゲーム内で移動できる 4 方向のオプションセットを定義する

```swift
struct Directions: OptionSet {
    let rawValue: UInt8

    static let up    = Directions(rawValue: 1 << 0)
    static let down  = Directions(rawValue: 1 << 1)
    static let left  = Directions(rawValue: 1 << 2)
    static let right = Directions(rawValue: 1 << 3)
}

let allowedMoves: Directions = [.up, .down, .left]
print(allowedMoves.rawValue)
// Prints "7"
```

オプションセットについては、こちらの記事が分かりやすかったです

https://dev.classmethod.jp/articles/about-option-set/

# おわりに

基本的な部分を一部のみまとめたものですが、一度整理できてスッキリしました。


# 参考

https://developer.apple.com/documentation/swift/basic-behaviors

https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID627