🎐

【DAY13】100 Days of SwiftUI -protocols, extensions

2023/01/11に公開

はじめに

iOSアプリ界隈で(たぶん)有名なPaul Hudsonさんが無料で公開しているSwiftUIの100日学習コースを進めてみます。学習記録及び備忘録がてらにつらつらと書いてみます。
100 Days of SwiftUI

学んだこと

プロトコル、エクステンション、プロトコルエクステンションというSwiftyな機能を学習。このセクションは内容が重い。

プロトコル

変数名、メソッド名とその型を定義したプロトコルという概念が存在する。プロトコルに準拠するように定義されたクラスなどはプロトコルで定義された変数名、メソッド名を含む必要があり、型もプロトコルで定義されたものでなければならない。クラスとの違いは変数に値を定義したり、メソッドの具体的な処理を定義できないことである。つまり、クラスや構造体の設計書に近い概念。

# Vehicleプロトコルを定義
protocol Vehicle {
    func estimateTime(for distance: Int) -> Int
    func travel(distance: Int)
}

# Vehicleプロトコルに準拠したCar構造体を定義
# Vehicleプロトコルで定義されているものはすべて(estimateTime, travel)を含む必要がある
struct Car: Vehicle {
    func estimateTime(for distance: Int) -> Int {
        distance / 50
    }
    
    func travel(distance: Int) {
        print("I'm drivint \(distance)km.")
    }
    
    func openSunroof() {
        print("It's a nice day!")
    }
}

opaque return types

雰囲気は理解できたが具体的にはよくわからなかったので飛ばす。

エクステンション

任意の型に機能を追加する機能。例えば以下のようにタブ等を削除するtrimmingCharacters(in: .whitespacesAndNewlines)があるとき

var quote = "   The truth is rarely pure and never simple   "
let trimmed = quote.trimmingCharacters(in: .whitespacesAndNewlines)
print(trimmed)
// 出力: The truth is rarely pure and never simple

これを毎回書くのは面倒なのでエクステンションで以下のように書ける。

extension String {
    func trimmed() -> String {
        self.trimmingCharacters(in: .whitespacesAndNewlines)
    }
}

エクステンションを用いるとtrimmedは以下のように書ける。

let trimmed = quote.trimmed()

上記の例の場合、関数を用いて書くこともできるが、記載が煩雑になることと、グローバル関数となってしまうことから保守性や安全性の観点からエクステンションの方が優れている。

プロトコルエクステンション

言葉のままプロトコルのエクステンション。型より上位の概念であるプロトコルにエクステンションを設定することができる。プロトコルエクステンションを利用すると以下の例のように配列だけではなく、辞書や集合等のCollectionプロトコルに準拠するものに対して一度に設定できる。
配列に値があるかどうかを判定し、要素数を出力するとき通常は

let guests = ["Mario", "Luige", "Peach"]
if guests.isEmpty == false {
    print("Guest count: \(guests.count)")
}

のようにすればよいが、falseでifの中に入るのがわかりにくい人もいるので配列にエクステンションを用いてisNotEmptyの機能を追加する。

extension Array {
    var isNotEmpty: Bool {
        isEmpty == false
    }
}

このエクステンションを用いると以下のようにわかりやすく書ける。

if guests.isNotEmpty {
    print("Guest count: \(guests.count)")
}

この場合Array型にしか適用されないが、実用性を考えるとSetやDictionaryにも設定できると嬉しい。それぞれの方に対してエクステンションを書けば実現できるが面倒くさい。そのようなときにエクステンションプロトコルを利用できる。Array、Set、Dictionaryは共通のプロトコルであるCollectionに準拠しているため以下のようにCllectionにエクステンションを設定することができる。

extension Collection {
    var isNotEmpty: Bool {
        isEmpty == false
    }
}

Discussion