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

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

struct C: Identifiable {


}

现在的问题是,我不能设置 id 属性或 name 属性的值:

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

之所以会发生这种情况,是因为在 Identity 协议中,id 和 name 只是可获取的。现在,如果我将 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 中放入哪段代码? 因为如果我添加 Id = newValue,它会崩溃(递归)。

先谢谢你。

55029 次浏览

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

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

比如:

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

似乎您想通过协议扩展向类型添加 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 中没有多重继承,所以这将是唯一一个能够继承的类。

组件(面向协议编程)

这种技术在游戏开发中非常流行

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

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

这个属性变成了一个计算属性,这意味着它没有像在 ObjecC 中那样拥有一个后备变量,比如 _ x。在下面的解决方案代码中,您可以看到 xTimesTwo 不存储任何东西,而只是计算 x 的结果。

有关计算属性的正式文档见。

您需要的功能还可能是属性观察器。

Setters/getter 与 Objective-C 中的不同。

你需要的是:

var x:Int


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

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

Objective-C 相关对象

可以使用 目标 C相关对象将 stored property基本上添加到 classprotocol请注意,关联对象只对类对象有效。

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符合 Identifiable

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