Swift - 协议扩展 - 属性默认值
Posted
技术标签:
【中文标题】Swift - 协议扩展 - 属性默认值【英文标题】:Swift - Protocol extensions - Property default values 【发布时间】:2016-12-17 13:24:40 【问题描述】:假设我有以下协议:
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 中的 Identifiable 协议。但是想象一下,如果我还有 N 个需要符合此协议的结构......我不想'复制/粘贴'一致性 (var id: Int, var name: String)
所以我创建了一个协议扩展:
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
这是因为在 Identifiable 协议中,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 是空的。
我的问题是: 我必须在 setter 中放入哪段代码?因为如果我添加 self.id = newValue,它会崩溃(递归)。
提前致谢。
【问题讨论】:
【参考方案1】:Objective-C 关联对象
您可以使用Objective-C 关联对象基本上将stored property
添加到class
或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)
现在您只需编写即可使您的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
【讨论】:
【参考方案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 中没有多重继承,这将是 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
【讨论】:
@Axort:不客气。我还建议您观看 video 关于 2015 WWDC 的 Protocol Oriented Programming 的内容。很有趣。 为什么要有 HasIdentifiableComponent 协议?为什么不直接做protocol Identifiable: var identifiableComponent: IdentifiableComponent get set
然后extension Identifiable var id: Int get return identifiableComponent.id set identifiableComponent.id = newValue etc...
您能解释一下为什么在组件示例中创建 2 个协议(HasIdentifiableComponent 和 Identifiable)吗?
@BohdanSavych 只是因为我想将HasIdentifiableComponent
的概念与Identifiable
的概念分开。如果您不喜欢这种分离,请随意将所有内容组合到一个协议中。
在尝试设置任一属性时,我在 Swift 4 中收到“无法分配给属性:函数调用返回不可变值”错误。尝试从符合的类中设置这些属性时出现类似错误,称 self 是不可变的。这些需要是纯类协议吗?【参考方案3】:
这就是您无法设置属性的原因。
该属性成为计算属性,这意味着它没有像在 ObjC 中那样的支持变量,例如 _x。在下面的解决方案代码中,您可以看到 xTimesTwo 不存储任何内容,而只是从 x 计算结果。
请参阅有关计算属性的官方文档。
您想要的功能也可能是 Property Observers。
Setter/getter 与 Objective-C 中的不同。
你需要的是:
var x:Int
var xTimesTwo:Int
set
x = newValue / 2
get
return x * 2
您可以修改 setter/getter 中的其他属性,这就是它们的用途
【讨论】:
嗨!感谢您的回答。是的,正如你所说,问题是你不能在扩展中声明 vars(扩展可能不包含存储的属性)所以我想知道是否有办法(很小的机会)使用计算属性:P【参考方案4】:协议和协议扩展非常强大,但它们往往对只读属性和函数最有用。
对于您要完成的任务(使用默认值存储属性),类和继承实际上可能是更优雅的解决方案
类似:
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)")
【讨论】:
嗨!感谢您的回答。我正在寻找一种使用面向协议编程而不是面向对象编程的解决方案。以上是关于Swift - 协议扩展 - 属性默认值的主要内容,如果未能解决你的问题,请参考以下文章
使用 Swift 4 中的 JSONDecoder,缺少的键可以使用默认值而不是可选属性吗?
使用 Swift 4 中的 JSONDecoder,缺少的键可以使用默认值而不是可选属性吗?