提问者:小点点

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

所以我创建了一个协议扩展:

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

发生这种情况是因为在可识别协议中,id和name只有gettable。现在如果我将id和name属性更改为{get set},我会收到以下错误:

类型“C”不符合协议“可识别”

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

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

        set {

        }
    }

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

        set {

        }
    }
}

现在错误消失了,但如果我将新值设置为id或name,它会获得默认值(getter)。当然,setter是空的。

我的问题是:我必须在setter中放入什么代码?因为如果我添加self.id=newValue,它就会崩溃(递归)。

提前感谢。


共3个答案

匿名用户

您似乎想通过协议扩展将存储属性添加到类型。但是,这是不可能的,因为使用扩展您无法添加存储的属性。

我可以给你看几个替代方案。

最简单的方法(可能您已经想象过)是使用类而不是结构。

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

class A: IdentifiableBase { }

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

缺点:在这种情况下,您的A类需要从IdfiableBase继承,并且由于在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 }
    }
}

现在你可以让你的类型符合可识别简单地写

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

测试

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

匿名用户

您可以使用Objective-C关联对象将存储属性添加到协议。请注意,关联对象仅适用于类对象。

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)
        }   
    }
}

现在您可以通过简单地编写使您的符合可识别

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

匿名用户

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

对于您试图实现的目标(具有默认值的存储属性),类和继承实际上可能是更优雅的解决方案

类似于:

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