使用私有全局变量作为扩展“属性”是代码气味吗?

时间:2018-03-10 13:39:53

标签: ios swift swift4

因为'扩展程序可能不包含存储的属性我看到有人通过使用带有objc_getAssociatedObject / objc_setAssociatedObject

的getter / setter来解决这个问题

(见)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是我可以使用的东西,因为扩展名在它自己的源文件中。

2 个答案:

答案 0 :(得分:2)

这通常用字典来完成。 (“通常”意思是“当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
    }
}

答案 1 :(得分:1)

最好避免使用全局变量。我建议将var设为私有实例变量,而不是全局变量。

将对象添加到对象以支持“本机”扩展是合理的。 (与原始对象在同一源文件中定义的扩展实际上只是对代码进行分组的一种方式,因为它被“烘焙”到基础对象中。

使用objc_setAssociatedObjectNSObject子类添加存储是扩展其他类的扩展选项。你需要知道有一个小的时间惩罚,但除非你在一个循环中重复引用一个关联的对象,它不可能是一个问题。它还要求对象是Objective-C对象,的时间损失很小。

使用objc_setAssociatedObject也可以使您的Swift代码仅限Apple。如果您正在构建您打算在Linux上使用的代码,那么它实际上并不是一种选择。