Non-Optionalなパラメータを受け取るメソッドをサブクラスでOptionalなパラメータを受け取るメソッドとしてオーバーライドする話

はじめに

「SwiftにおけるMethod Dispatchについて」を発表した後にいただいた質問への回答編です。

以下のコードを実行した場合の振る舞いについて、どのように解釈されるでしょうか?

class BaseClass {
    func print(_ string: String) {
        Swift.print("BaseClass - \(string)")
    }
}

class SubClass: BaseClass {
    override func print(_ string: String?) {
        Swift.print("SubClass - \(string)")
    }
}

let s = SubClass()
let string: String? = "Optional"
s.print(string)
s.print("String")

実験環境

Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1)
Target: x86_64-apple-macosx10.9
Xcode 8.2

実行結果

直感的には、以下のような出力が得られるのではないかと考えられます。

SubClass - Optional("Optional")
BaseClass - String

実際に、上記のコードを実行すると、以下のような出力が得られます。

SubClass - Optional("Optional")
SubClass - Optional("String")
let string: String? = "Optional"
s.print(string) // SubClass - Optional("Optional")

上記コードの出力結果は、予想通りの結果が得られています。
しかし、その次のs.print("String")の結果は、予想と異なっています。
どちらのprintの呼び出しも、渡しているパラメータの実際の型(String, String?)に関係なく、SubClass.print(_:)が呼び出されています。

SILを用いた解析

BaseClassSubClass、それぞれの仮想テーブルは以下のようになっています。

sil_vtable BaseClass {
  #BaseClass.print!1: _TFC16OverrideOptional9BaseClass5printfSST_   // BaseClass.print(String) -> ()
  #BaseClass.deinit!deallocator: _TFC16OverrideOptional9BaseClassD  // BaseClass.__deallocating_deinit
  #BaseClass.init!initializer.1: _TFC16OverrideOptional9BaseClasscfT_S0_    // BaseClass.init() -> BaseClass
}

sil_vtable SubClass {
  #BaseClass.print!1: _TTVFC16OverrideOptional8SubClass5printfGSqSS_T_  // override SubClass.print(String?) -> ()
  #BaseClass.init!initializer.1: _TFC16OverrideOptional8SubClasscfT_S0_ // SubClass.init() -> SubClass
  #SubClass.deinit!deallocator: _TFC16OverrideOptional8SubClassD    // SubClass.__deallocating_deinit
}

仮想テーブルより、SubClassでは、BaseClassで定義したprintメソッドをオーバーライドしていることが分かります。

#BaseClass.print!1: _TFC16OverrideOptional9BaseClass5printfSST_ // BaseClass.print(String) -> ()
...
#BaseClass.print!1: _TTVFC16OverrideOptional8SubClass5printfGSqSS_T_    // override SubClass.print(String?) -> ()

ここで、SubClassで定義しているメソッドの実態が、SILでは、どのようになっているかを見てみます。
_TTVFC16OverrideOptional8SubClass5printfGSqSS_T_という名前から、該当箇所を探します。

// override SubClass.print(String?) -> ()
sil private @_TTVFC16OverrideOptional8SubClass5printfGSqSS_T_ : [email protected](method) (@owned String, @guaranteed SubClass) -> () {
// %0                                             // user: %2
// %1                                             // user: %4
bb0(%0 : $String, %1 : $SubClass):
  %2 = enum $Optional<String>, #Optional.some!enumelt.1, %0 : $String, scope 10 // user: %4
  // function_ref SubClass.print(String?) -> ()
  %3 = function_ref @_TFC16OverrideOptional8SubClass5printfGSqSS_T_ : [email protected](method) (@owned Optional<String>, @guaranteed SubClass) -> (), scope 10 // user: %4
  %4 = apply %3(%2, %1) : [email protected](method) (@owned Optional<String>, @guaranteed SubClass) -> (), scope 10
  %5 = tuple (), scope 10                         // user: %6
  return %5 : $(), scope 10                       // id: %6
}

このメソッドで行なっていることを分かりやすく記述すると、以下のようになります。
(厳密には、異なりますが、大まかなイメージは以下の通りです)

class SubClass: BaseClass {
    private override func print(_ string: String) {
        let optional: String? = string
        _print(optional)
    }

    func _print(_ string: String?) {
        Swift.print("SubClass - \(string)")
    }
}

つまり、privateなメソッドで、基底クラスのメソッドのオーバーライドを行い、そのメソッドから、コード上で定義したメソッドの呼び出しを行なっている、という形になります。
そのため、はじめに見た実行結果のように、Non-Optionalなパラメータであっても、Optional型に変換される、ということがSILから分かります。

まとめ

Optional/Non-Optionalなパラメータをとるメソッドを、派生クラスでNon-Optional/Optionalなパラメータをとるメソッドでオーバライドした場合、コンパイル時に、暗黙的にメソッドが作成されます。
そのため、このようなメソッドを実装する場合には、より注意が必要です。

おまけ

今回は、パラメータとして受け取るメソッドのみを考えましたが、もちろん、以下のようなコードも考えられます。

class BaseClass {
    func generate() -> String {
         return "BaseClass"
    }
}

class SubClass: BaseClass
    override func generate() -> String? {
         return "SubClass"
    }
}

let s = SubClass()
let string = s.generate()

上記のコードは、現状、コンパイルが行えません。
以下の関連したSwiftのバグでコンパイルに失敗する、という状況なので、将来バージョンでは、解決しているかと思います。
SR-1980
SR-1529

カテゴリ:Default 時間:2017-02-22 人気:0
この記事では、 Swift

関連記事

Copyright (C) socapnw.com, All Rights Reserved.

Socapnw All Rights Reserved.

processed in 0.160 (s). 11 q(s)