【问题标题】:Associated objects and Swift arrays关联对象和 Swift 数组
【发布时间】:2020-01-02 19:19:49
【问题描述】:

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

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 数组,但我不知道该怎么做。这将阻止我进行非常丑陋的铸造。有什么建议吗?

【问题讨论】:

  • 你有什么问题?它的功能会略有不同,因为 Swift 数组是值类型,但将 swift 数组存储为关联对象没有问题
  • 没有问题。 Swift 数组是值类型的事实是我噩梦的根源。我正在绕着头……@dan

标签: arrays objective-c swift nsmutablearray associated-object


【解决方案1】:

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

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

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

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

我会这样做:

  1. 使界面更明确地事务性。很有可能,您可以完全隐藏queue。将其设为私有,并让您的视图公开诸如 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 包装器来隐藏关联的对象集/获取,它可以在幕后自动进行类型检查和强制转换。新的属性包装器功能非常适合这一点。

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

【讨论】:

  • 您好亚历山大,谢谢您的回复。 queue 属性被声明为 private,因此从外部看不到。自清洁以来,我同意您的回复/代码。
猜你喜欢
  • 2012-08-30
  • 1970-01-01
  • 2015-03-27
  • 2010-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-24
  • 1970-01-01
相关资源
最近更新 更多