关联对象和 Swift 数组

Associated objects and Swift arrays

我在包含数组(在本例中为可变数组)的视图扩展上有一个关联对象。

var queue: NSMutableArray {
    get {
        if let queue = objc_getAssociatedObject(self, &Key.queue) as? NSMutableArray {
            return queue
        } else {
            let queue = NSMutableArray()
            objc_setAssociatedObject(self, &Key.queue, queue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            return queue
        }
    }
}

我想将 NSMutableArray 转换为 Swift 数组,但我不知道该怎么做。这将阻止我执行非常难看的转换。有什么建议吗?

这里的NSMutableArray是引用类型。这样做的结果是,当您将对视图对象的 queue 的引用交给某人时,您的视图对象就失去了对它的所有控制。引用的接收者可以重新排列项目、删除所有项目等,而您的视图直到下次尝试阅读它时才会知道。

一般来说,这种隐式数据共享已被发现不是一个好主意,因为它破坏了封装,并使系统复杂化,因为你不能再在本地推理代码,因为总是存在另一个线程的威胁引用了您的别名对象,并且正在您的脚下更改它。

此模型与 Swift 的值类型不兼容,例如 Array。如果 queue 是一个 Array<T>,每个访问它的人都会取回他们自己的价值。从语义上讲,这些副本彼此完全隔离,并且通过一个引用完成的突变不可能导致通过另一个引用可以观察到的效果。

如果您想忠实地保留当前代码的引用语义,您将需要一种机制来侦听对 NSMutableArray 的更改,并更新从中派生的每个个体 Array<T> .这不是很实用,也不是一个好主意。

我会这样做:

  1. 使界面更明确地具有事务性。更有可能的是,您可以完全隐藏 queue。将其设为私有,并让您的视图公开 public 方法,例如 pushpop.

    import Foundation
    
    class C: NSObject {
        private enum AssociatedObjectKeys {
            static var queue: Int8 = 0
        }
    
        private var queue: Array<Int> {
            get {
                guard let existingValue = objc_getAssociatedObject(self, &AssociatedObjectKeys.queue) else {
                    self.queue = []
                    return []
                }
                guard let existingArray = existingValue as? Array<Int> else {
                    fatalError("Found an associated object that had the wrong type!")
                }
                return existingArray
            }
            set { 
                objc_setAssociatedObject(self, &AssociatedObjectKeys.queue, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    
        public func printQueueForDebugging() {
            print(self.queue)
        }
    
        public func push(_ newValue: Int) {
            self.queue.append(newValue)
        }
    
        public func pushAll<S: Sequence>(_ newValues: S) where S.Element == Int {
            self.queue.append(contentsOf: newValues)
        }
    
        public func pop() -> Int? {
            if self.queue.isEmpty { return nil }
            return self.queue.removeFirst()
        }
    }
    
    let c = C()
    c.printQueueForDebugging()
    c.pushAll(1...3)
    c.printQueueForDebugging()
    c.push(4)
    c.printQueueForDebugging()
    print(c.pop() as Any)
    print(c.pop() as Any)
    print(c.pop() as Any)
    print(c.pop() as Any)
    print(c.pop() as Any)
    
  2. 我会使用类型安全的 Swift 包装器来隐藏关联对象 sets/gets,它可以在幕后自动进行类型检查和转换。新的 属性 包装器功能非常适合这个。

  3. 提取出单独的Queue<T>数据结构。 Array<T> 本身并不能构成一个好的队列,因为在开始时删除具有 O(n) 时间复杂度。那里有很多数据结构库,我会改用其中一个队列。