🕊

[Swift] protocol の関数の引数にデフォルト値を設定する方法

2022/11/17に公開

伝えたいこと

  • protocol の関数の引数にデフォルト値を設定することはできない
  • protocol の関数の引数にデフォルト値は extension で拡張することで関数で設定することが可能である
  • 上記の方法で複数の引数についてデフォルト値を設定すると、想定していないインターフェースが増えてしまうので、必要な引数のインターフェースだけを提供して、デフォルト引数を設定するのではなく、直接デフォルト値を引数に与えることで、制限することが可能である

protocol の関数の引数にデフォルト値を設定することはできない

以下のように、protocol の関数の引数にデフォルト値を設定することはできません。

// ❌ これはできない
protocol HogeProtocol {
    func hogeFunc(moge: Int = 0, fuga: Int = 0, piyo: Int = 0) // Default argument not permitted in a protocol method
}

// expression failed to parse:
// error: protocol.playground:50:5: error: default argument not permitted in a protocol method
//     func hogeFunc(moge: Int = 0, fuga: Int = 0, piyo: Int = 0)
//     ^

protocol の関数の引数にデフォルト値は extension で拡張することで関数で設定する

protocol の宣言時では関数の引数にデフォルト値を設定できませんが、以下のように extension で拡張すれば、引数のデフォルト値を設定することができます。

protocol HogeProtocol {
    func hogeFunc(moge: Int, fuga: Int, piyo: Int)
}

extension HogeProtocol {
    // ⭕️ このように記述することは可能
    func hogeFunc(moge: Int, fuga: Int = 0, piyo: Int = 0) {
        hogeFunc(moge: moge, fuga: fuga, piyo: piyo)
    }
}

適応例

改善前のコード

protocol Hoge1Protocol {
    func hogeFunc(moge: Int)
}

protocol Hoge2Protocol {
    func hogeFunc(moge: Int, fuga: Int)
}

protocol Hoge3Protocol {
    func hogeFunc(moge: Int, fuga: Int, piyo: Int)
}

struct Hoge: Hoge1Protocol, Hoge2Protocol, Hoge3Protocol {
    func hogeFunc(moge: Int) {
        hogeFunc(moge: moge, fuga: 0)
    }
    
    func hogeFunc(moge: Int, fuga: Int) {
        hogeFunc(moge: moge, fuga: fuga, piyo: 0)
    }
    
    func hogeFunc(moge: Int, fuga: Int, piyo: Int) {
        print("moge: \(String(describing: moge))")
        print("fuga: \(String(describing: fuga))")
        print("piyo: \(String(describing: piyo))")
    }
}

Hoge().hogeFunc(moge: 1)
// moge: 1
// fuga: 0
// piyo: 0

Hoge().hogeFunc(moge: 1, fuga: 2)
// moge: 1
// fuga: 2
// piyo: 0

Hoge().hogeFunc(moge: 1, fuga: 2, piyo: 3)
// moge: 1
// fuga: 2
// piyo: 3

Hoge の定義がやや冗長です。

改善後のコード

protocol HogeProtocol {
    func hogeFunc(moge: Int, fuga: Int, piyo: Int)
}

extension HogeProtocol {
    func hogeFunc(moge: Int, fuga: Int = 0, piyo: Int = 0) {
        hogeFunc(moge: moge, fuga: fuga, piyo: piyo)
    }
}

struct Hoge: HogeProtocol {
    func hogeFunc(moge: Int, fuga: Int, piyo: Int) {
        print("moge: \(String(describing: moge))")
        print("fuga: \(String(describing: fuga))")
        print("piyo: \(String(describing: piyo))")
    }
}

Hoge().hogeFunc(moge: 1)
// moge: 1
// fuga: 0
// piyo: 0

Hoge().hogeFunc(moge: 1, fuga: 2)
// moge: 1
// fuga: 2
// piyo: 0

Hoge().hogeFunc(moge: 1, fuga: 2, piyo: 3)
// moge: 1
// fuga: 2
// piyo: 3

改善前と比較して、Hoge の定義がすっきりしました。

応用編(引数の組み合わせを制限する)

複数の引数についてデフォルト値を設定すると、想定していない引数の組み合わせで関数が呼ばれてしまうことがあります。

例えば、以下のような関数を呼ばれてほしくないとしても、複数の引数のデフォルト値を設定したことによって、このように呼ぶこともできてしまいます。

// fuga を抜きで moge と piyo を指定することは想定していない
Hoge().hogeFunc(moge: 1, piyo: 3) // ← この呼び方を制限させたい

その場合も、extension で必要な引数のインターフェースだけを提供して、デフォルト引数を設定するのではなく、直接デフォルト値を引数に与えることで、制限することが可能です。

protocol HogeProtocol {
    func hogeFunc(moge: Int, fuga: Int, piyo: Int)
}

// 必要な引数のインターフェースだけを提供する
extension HogeProtocol {
    func hogeFunc(moge: Int) {
        hogeFunc(moge: moge, fuga: 0, piyo: 0)
    }
    
    func hogeFunc(moge: Int, fuga: Int) {
        hogeFunc(moge: moge, fuga: fuga, piyo: 0)
    }
}

// これで以下の呼び方ができないように制限できた!!
Hoge().hogeFunc(moge: 1, piyo: 3) // Missing argument for parameter 'fuga' in call

個人的には、無闇に引数のデフォルト値を設定して、インターフェースが増えるよりもこのように、制限してあげたほうがいいと思います。

結論(伝えたいことの繰り返し)

  • protocol の関数の引数にデフォルト値を設定することはできない
  • protocol の関数の引数にデフォルト値は extension で拡張することで関数で設定することが可能である
  • 上記の方法で複数の引数についてデフォルト値を設定すると、想定していないインターフェースが増えてしまうので、必要な引数のインターフェースだけを提供して、デフォルト引数を設定するのではなく、直接デフォルト値を引数に与えることで、制限することが可能である

以上になります。

GitHubで編集を提案

Discussion