【Swift 4.2】【Swift 5】HashableとCodableと継承を同時に使うとhashValueの参照でクラッシュする


【概要】

Swift 4.2/5.0においてHashableとCodableと継承を同時に使うとhashValueの参照でクラッシュします。

【環境】

let lang = "Swift 4.2" + "Swift 5.0"
let env = "XCode10" + "XCode10.2"

【詳細内容】

まずSwift 4.2において導入されたfunc hash(into hasher: inout Hasher)を使ってHashableを実装します。

ただし、下記のクラスはテストファイル中ではなく、アプリ内のファイル(アプリバンドルない)に記述します。(2019/3/30更新)


open class HashableTestModel: Hashable
{
    public var uniqueIdentifier: String

    public init()
    {
        self.uniqueIdentifier = String(arc4random())
    }

    // Equatable
    static public func ==(lhs: HashableTestModel, rhs: HashableTestModel) -> Bool
    {
        return true
    }

    public func hash(into hasher: inout Hasher)
    {
        hasher.combine(uniqueIdentifier)
    }
}

↑ここまでは問題ありません。次にHashableTestModelをCodableにします

open class HashableTestModel: Hashable, Codable
{

またHashableTestModelを継承したHashableTestModelChildを作成します。
HashableTestModelChildはブランクのままで構いません。


open class HashableTestModelChild: HashableTestModel
{

}

これでテストします。

    func testCoreModelHashable() {

        let val = HashableTestModel()
        print(val.hashValue)
    }

クラッシュします。
Codableをやめるか、継承をやめると動作します。
また問題となっているクラスをテストファイル内に記述すると動作します。
不思議ですね。

通常は、どちらのコードもアプリのメインバンドルに記述されるはずなので、問題は起こりません。テストでは死にますが。

問題なのは、Embedded Frameworkを使っていて呼び出し元と当該クラスのバンドルが異なる場合でしょう。(実際自分のテストでは、メインバンドルから、Embedded Frameworkの上のクラスを呼び出すと同じくクラッシュしました。

【さらにわかったこと】

ベースクラスを、別バンドル(Embedded Framework内など)に置き、
継承クラスをメインバンドルに置いた状態で、インスタンスを作成し print(val.hashValue) を実行するとクラッシュしません。

open class HashableTestModelChild: HashableTestModel
を作成すると、ベースクラスの

    public func hash(into hasher: inout Hasher)
    {
        hasher.combine(uniqueIdentifier)
    }

が呼ばれなくなります。

【サンプルコード】

サンプルコードはこちらに置きました。

6月にWWDCに行くので、Appleのエンジニアに直接聞いてこようかと思います。

【解決しました】


open class HashableTestModel_Other: Codable
{
    public var uniqueIdentifier: String

    public init()
    {
        self.uniqueIdentifier = String(arc4random())
    }

    // Equatable
    static public func ==(lhs: HashableTestModel_Other, rhs: HashableTestModel_Other) -> Bool
    {
        return true
    }
}

extension HashableTestModel_Other: Hashable
{
    public func hash(into hasher: inout Hasher)
    {
        hasher.combine(uniqueIdentifier)
    }
}

open class HashableTestModelChild_Other: HashableTestModel_Other
{

}

このように、Hashableをextensionで記述するとクラッシュしないことが分かりました。