首页 文章

Swift - 协议扩展 - 属性默认值

提问于
浏览
39

假设我有以下协议:

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个结构需要符合这个协议......我不想'复制/粘贴' '一致性(var id:Int,var name:String)

所以我创建了 protocol extension

extension Identifiable {
    var id: Int {
        return 0
    }

    var name: String {
        return "default"
    }
}

现在有了这个扩展,我可以创建一个符合Identifiable协议的结构,而不必实现这两个属性:

struct C: Identifiable {

}

现在的问题是我无法为id属性或name属性设置一个值:

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

发生这种情况是因为在可识别协议中,id和name只能获取 . 现在如果我将id和name属性更改为 ,我会收到以下错误:

类型'C'不符合协议'Identifiable'

发生此错误是因为我没有在协议扩展中实现setter ...所以我更改了协议扩展:

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

        set {

        }
    }

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

        set {

        }
    }
}

现在错误消失但是如果我为id或name设置了一个新值,它将获得默认值(getter) . 当然, the setter is empty .

我的问题是: What piece of code do I have to put inside the setter? 因为如果我添加self.id = newValue它会崩溃(递归) .

提前致谢 .

3 回答

  • 2

    您似乎想通过协议扩展将 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中,theres不是多重继承,这将是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
    
  • 0

    协议和协议扩展非常强大,但它们往往对只读属性和函数最有用 .

    对于您要完成的任务(使用默认值存储的属性),类和继承实际上可能是更优雅的解决方案

    就像是:

    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)")
    
  • 57

    这就是您无法设置属性的原因 .

    该属性成为计算属性,这意味着它没有像ObjC那样的后备变量,如_x . 在下面的解决方案代码中,您可以看到xTimesTwo不存储任何内容,而只是计算x的结果 .

    请参阅有关计算属性的官方文档 .

    您想要的功能也可能是Property Observers .

    Setter / getters与Objective-C中的不同 .

    你需要的是:

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

    您可以修改setter / getters中的其他属性,这就是它们的用途

相关问题