web-dev-qa-db-ja.com

Swift-プロトコル拡張-プロパティのデフォルト値

私は次のプロトコルを持っているとしましょう:

protocol Identifiable {
    var id: Int {get}
    var name: String {get}
}

そして、私は次の構造体を持っていること:

struct A: Identifiable {
    var id: Int
    var name: String
}

struct B: Identifiable {
    var id: Int
    var name: String
}

ご覧のとおり、構造Aと構造Bの識別可能なプロトコルに「準拠」する必要がありました。しかし、このプロトコルに準拠する必要のある構造がさらにN個あると想像してください。「コピー/貼り付け」はしたくない'適合性(変数id:Int、変数名:String)

そこで、protocol extensionを作成します:

extension Identifiable {
    var id: Int {
        return 0
    }

    var name: String {
        return "default"
    }
}

この拡張機能を使用すると、両方のプロパティを実装することなく、識別可能なプロトコルに準拠する構造体を作成できます。

struct C: Identifiable {

}

問題は、idプロパティまたはnameプロパティに値を設定できないことです。

var c: C = C()
c.id = 12 // Cannot assign to property: 'id' is a get-only property

これは、Identifiableプロトコルでは、IDと名前が取得のみ可能なために起こります。 idプロパティとnameプロパティを{get set}に変更すると、次のエラーが表示されます。

タイプ「C」はプロトコル「Identifiable」に準拠していません

このエラーは、プロトコル拡張にセッターを実装していないために発生します...プロトコル拡張を変更します。

extension Identifiable {
    var id: Int {
        get {
            return 0
        }

        set {

        }
    }

    var name: String {
        get {
            return "default"
        }

        set {

        }
    }
}

エラーはなくなりましたが、新しい値をidまたはnameに設定すると、デフォルト値(getter)が取得されます。もちろん、セッターは空です

私の質問は次のとおりです。セッター内にどのコードを入れる必要がありますか?なぜなら、self.id = newValueクラッシュします(再帰的)。

前もって感謝します。

53
Axort

プロトコル拡張を介してstored propertyをタイプに追加したいようです。ただし、拡張機能では保存されたプロパティを追加できないため、これは不可能です。

いくつかの選択肢を紹介できます。

サブクラス化(オブジェクト指向プログラミング)

最も簡単な方法は(おそらく既に想像しているように)構造体の代わりにクラスを使用することです。

class IdentifiableBase {
    var id = 0
    var name = "default"
}

class A: IdentifiableBase { }

let a = A()
a.name  = "test"
print(a.name) // test

短所:この場合、AクラスはIdentifiableBaseから継承する必要があり、Swiftには複数の継承がないため、これがクラスAから継承できる唯一のクラスになります。

コンポーネント(プロトコル指向プログラミング)

この手法はゲーム開発でかなり人気があります

struct IdentifiableComponent {
    var id = 0
    var name = "default"
}

protocol HasIdentifiableComponent {
    var identifiableComponent: IdentifiableComponent { get set }
}

protocol Identifiable: HasIdentifiableComponent { }

extension Identifiable {
    var id: Int {
        get { return identifiableComponent.id }
        set { identifiableComponent.id = newValue }
    }
    var name: String {
        get { return identifiableComponent.name }
        set { identifiableComponent.name = newValue }
    }
}

これで、タイプをIdentifiableに準拠させることができます。

struct A: Identifiable {
    var identifiableComponent = IdentifiableComponent()
}

テスト

var a = A()
a.identifiableComponent.name = "test"
print(a.identifiableComponent.name) // test
71
Luca Angeletti

プロトコルとプロトコル拡張は非常に強力ですが、読み取り専用のプロパティと機能に最も役立つ傾向があります。

あなたが達成しようとしていること(デフォルト値で保存されたプロパティ)のために、クラスと継承は実際にはよりエレガントなソリューションかもしれません

何かのようなもの:

class Identifiable {
    var id: Int = 0
    var name: String = "default"
}

class A:Identifiable {
}

class B:Identifiable {
}

let a = A()

print("\(a.id) \(a.name)")

a.id = 42
a.name = "foo"

print("\(a.id) \(a.name)")
2
Nick

これが、プロパティを設定できなかった理由です。

プロパティは計算プロパティになります。つまり、ObjCの場合のように_xなどの補助変数がありません。以下のソリューションコードでは、xTimesTwoが何も保存せず、単にxから結果を計算することがわかります。

計算されたプロパティの公式ドキュメントを参照してください。

必要な機能は、プロパティオブザーバーでもあります。

セッター/ゲッターは、Objective-Cとは異なります。

必要なのは:

var x:Int

var xTimesTwo:Int {
    set {
       x = newValue / 2
    }
    get {
        return x * 2
    }
}

セッター/ゲッター内の他のプロパティを変更できます。

0
Harsh

Objective-C関連オブジェクト

Objective-C関連オブジェクトを使用して、基本的にstored propertyclassまたはprotocolに追加できます。 関連オブジェクトはクラスオブジェクトに対してのみ機能することに注意してください。

import ObjectiveC.runtime

protocol Identifiable: class {
    var id: Int { get set }
    var name: String { get set }
}

var IdentifiableIdKey   = "kIdentifiableIdKey"
var IdentifiableNameKey = "kIdentifiableNameKey"

extension Identifiable {
    var id: Int {
        get { 
            return (objc_getAssociatedObject(self, &IdentifiableIdKey) as? Int) ?? 0
        }
        set {
            objc_setAssociatedObject(self, &IdentifiableIdKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }   
    }

    var name: String {
        get { 
            return (objc_getAssociatedObject(self, &IdentifiableNameKey) as? String) ?? "default"
        }
        set {
            objc_setAssociatedObject(self, &IdentifiableNameKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }   
    }
}

これで、次のように書くだけで、classIdentifiableに準拠させることができます。

class A: Identifiable {

}

テスト

var a = A()
print(a.id)    // 0
print(a.name)  // default
a.id = 5
a.name = "changed"
print(a.id)    // 5
print(a.name)  // changed

0
DominicMDev