使用私有全局变量作为扩展“属性”是一种代码味道吗?
Posted
技术标签:
【中文标题】使用私有全局变量作为扩展“属性”是一种代码味道吗?【英文标题】:Is it a code smell to use private global variables as extension 'properties'? 【发布时间】:2018-03-10 13:39:53 【问题描述】:因为'Extensions may not contain stored properties'我见过人们通过使用getter / setter和objc_getAssociatedObject
/ objc_setAssociatedObject
来解决这个问题
(见)How to have stored properties in Swift, the same way I had on Objective-C?
我发现那里讨论的解决方案非常“不灵活”,但仍然希望将变量保持在靠近它们使用的位置。
这就是我最近想在扩展中使用“属性”时开始执行以下操作的原因。
private var lastValue: Int = 0
extension ViewController
func checkIfBiggerThanLastNumber(_ number: Int) -> Bool
let savedLast = lastValue
lastValue = number
return number > savedLast
因为我在其他地方没有找到这个解决方案,所以我想知道这是代码异味还是它有什么缺点。
该源文件中的其他扩展名可以访问lastValue
是我可以忍受的,因为该扩展名在它自己的源文件中。
【问题讨论】:
想一想:如果您有该类的两个实例,并为每个实例设置一个值,会发生什么? 嗯,这是一个好点...... 是什么原因让我们不能轻易的给扩展添加属性? 因为添加属性会改变对象的大小。在另一个模块中创建的 ViewController 无法知道要分配多少内存。添加该功能需要在 Swift 中创建类似 objc_getAssociatedObject 之类的东西,这可能有朝一日可以用于类(尽管我怀疑结构)。 比较***.com/a/45495616/1187415: " 因为添加实例属性会改变该类型实例的大小" 【参考方案1】:这通常使用字典来完成。 (“Commonly”的意思是“当 ObjC 运行时不可用时。”正如 Charles Srstka 所指出的,这里用于视图控制器的正确工具绝对是 objc_getAssociatedObject
。一旦你已经继承了避免运行时,没有什么特别的优点来自NSObject
。)
它会泄漏一些内存(因为没有自动清除未使用值的方法),但成本通常很小(或者您必须添加一种机制来“垃圾收集”,这并不难)。
private var lastValues: [ObjectIdentifier: Int] = [:]
extension ViewController
private var lastValue: Int
get
return lastValues[ObjectIdentifier(self)] ?? 0
set
lastValues[ObjectIdentifier(self)] = newValue
func checkIfBiggerThanLastNumber(_ number: Int) -> Bool
let savedLast = lastValue
lastValue = number
return number > savedLast
我没有对此进行过广泛的测试,但这是一个示例,说明如果您有很多来来去去的对象,您可以如何构建一个自动垃圾收集版本来清理内存。每次修改它都会进行垃圾收集,但您可以根据需要以其他方式进行:
// A simple weak dictionary. There are probably better versions out there.
// I just threw this together.
struct WeakDict<Key: AnyObject, Value>: ExpressibleByDictionaryLiteral
struct Box<T: AnyObject>: Hashable
let identifier: ObjectIdentifier
weak var value: T?
init(_ value: T)
self.identifier = ObjectIdentifier(value)
self.value = value
static func ==(lhs: Box<T>, rhs: Box<T>) -> Bool
return lhs.identifier == rhs.identifier
var hashValue: Int return identifier.hashValue
private var dict: [Box<Key>: Value]
init(dictionaryLiteral elements: (Key, Value)...)
dict = Dictionary(uniqueKeysWithValues: elements.map (Box($0), $1) )
private mutating func garbageCollect()
dict = dict.filter (key, _) in key.value != nil
subscript(_ key: Key) -> Value?
get
return dict[Box(key)]
set
garbageCollect()
dict[Box(key)] = newValue
这样,用法几乎相同:
private var lastValues: WeakDict<ViewController, Int> = [:]
extension ViewController
private var lastValue: Int
get return lastValues[self] ?? 0
set lastValues[self] = newValue
func checkIfBiggerThanLastNumber(_ number: Int) -> Bool
let savedLast = lastValue
lastValue = number
return number > savedLast
【讨论】:
如果我理解正确的话,这就是objc_setAssociatedObject
所做的,但后者确实能够在对象被释放时自动清理内存。我不知道,如果你正在使用像NSViewController
这样的NSObject
子类,我会使用它。你可以说它不是 Swifty,因为它使用了 Objective-C 运行时,但是,NSViewController
.........
是的,是的。对于 NSObject 子类,我肯定会(并且确实)使用objc_setAssociatedObject
。这就是您在没有运行时以及未桥接到 ObjC 的对象时的做法。【参考方案2】:
最好避免使用全局变量。我建议将您的 var 设为私有实例变量而不是全局变量。
将私有变量添加到对象以支持“本机”扩展是合理的。 (在与原始对象相同的源文件中定义的扩展实际上只是对代码进行分组的一种方式,因为它是“烘焙到”基础对象中的。
使用objc_setAssociatedObject
向NSObject
子类添加存储是扩展其他类的扩展选项。您需要注意会有一点时间损失,但除非您在循环中重复引用关联对象,否则这不太可能成为问题。它还要求对象是一个 Objective-C 对象,也有一个小的时间损失。
使用objc_setAssociatedObject
也会使您的 Swift 代码仅适用于 Apple。如果您正在构建打算在 Linux 上使用的代码,那么这不是一个真正的选择。
【讨论】:
以上是关于使用私有全局变量作为扩展“属性”是一种代码味道吗?的主要内容,如果未能解决你的问题,请参考以下文章