代码之家  ›  专栏  ›  技术社区  ›  Felipe Ferri

Swift中的键值观察不显示数组中的插入和删除

  •  12
  • Felipe Ferri  · 技术社区  · 11 年前

    我创建了一个包含数组的类。我在视图控制器中向该阵列添加了一个观察者,并对该阵列进行了一些修改。

    问题是,当我打印observeValueForKeyPath()方法返回的更改字典时,我只能看到NSKeyValueChangeSetting类型的更改。换句话说,该方法告诉我数组已更改,为我提供了旧数组和新数组(包含所有元素),但我希望接收添加或删除了哪些特定项的信息。

    下面是一些示例代码。

    这是将观察其数组的类。

    private let _observedClass = ObservedClass()
    
    class ObservedClass: NSObject {
        dynamic var animals = [String]()
        dynamic var cars = [String]()
    
        class var sharedInstance: ObservedClass {
            return _observedClass
        }
    }
    

    这是视图控制器的代码。

    class ViewController: UIViewController {
        var observedClass = ObservedClass.sharedInstance
    
        required init(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            observedClass.addObserver(self, forKeyPath: "animals", options: .New | .Old, context: nil)
        }
    
        deinit {
            observedClass.removeObserver(self, forKeyPath: "animals")
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            observedClass.animals.insert("monkey", atIndex: 0)
            observedClass.animals.append("tiger")
            observedClass.animals.append("lion")
            observedClass.animals.removeAtIndex(0)
        }
    
        override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
            println(change)
        }
    }
    

    当我运行上面的代码时,我在控制台上得到了这样的结果:

    [kind: 1, old: (
    ), new: (
        monkey
    )]
    [kind: 1, old: (
        monkey
    ), new: (
        monkey,
        tiger
    )]
    [kind: 1, old: (
        monkey,
        tiger
    ), new: (
        monkey,
        tiger,
        lion
    )]
    [kind: 1, old: (
        monkey,
        tiger,
        lion
    ), new: (
        tiger,
        lion
    )]
    

    在本例中,更改字典不应该在将每个新项添加到数组时使用更改类型NSKeyValueChangeInsertion来显示吗?

    1 回复  |  直到 11 年前
        1
  •  13
  •   Paul Patterson    11 年前

    根据Swift指南:

    对于数组,只有在执行可能修改数组长度的操作时,才会进行复制。这包括附加、插入或删除项,或使用范围下标替换数组中的一系列项。

    append 活动在后台,当您向数组追加时,Swift会创建一个 数组,从现有的 animals 数组到这个新数组中-加上新项-然后将这个新数组分配给 动物 变量这种花招就是为什么你只能得到 1 ( Setting ),因为事实上每个' 编辑 '实际上会创建一个新数组。

    这与 NSMutableArray 因为行为更直观一点-编辑 创建到现有数组(没有后台复制到新数组),因此编辑后存在的数组与之前存在的数组相同,因此存储在 change 带钥匙的字典 NSKeyValueChangeKindKey 可以是以下之一 .Insertion , .Removal

    然而,这并不是全部,因为您对KVO兼容的更改 NS可变阵列 取决于您使用的是Swift还是Objective-C。在Objective-C中,苹果强烈建议实现他们称之为可选的 mutable indexed accessors 这些只是具有标准签名的方法,以非常有效的方式进行符合KVO的更改。

    // Mutable Accessors ///////////////////////////////////////////
    
    // This class has a property called <children> of type NSMutableArray
    
    // MUST have this (or other insert accessor)
    -(void)insertObject:(NSNumber *)object inChildrenAtIndex:(NSUInteger)index {
        [self.children insertObject:object atIndex:index];
    }
    
    // MUST have this (or other remove accessor)
    -(void)removeObjectFromChildrenAtIndex:(NSUInteger)index {
        [self.children removeObjectAtIndex:index];
    }
    
    // OPTIONAL - but a good idea.
    -(void)replaceObjectInChildrenAtIndex:(NSUInteger)index withObject:(id)object {
        [self.children replaceObjectAtIndex:index withObject:object];
    }
    

    Swift似乎不具备这些功能,因此进行KVO兼容更改的唯一方法是通过可变代理对象-如 NSKeyValueCoding 页下面是一个非常简单的例子:

    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate {
    
        dynamic var children: NSMutableArray = NSMutableArray()
    
    
        ////////////////////////////////////
    
        func applicationDidFinishLaunching(aNotification: NSNotification) {
    
            addObserver(self,
                forKeyPath: "children",
                options: .New | .Old,
                context: &Update)
    
            // Get the KVO/KVC compatible array
            var childrenProxy = mutableArrayValueForKey("children")
    
            childrenProxy.addObject(NSNumber(integer: 20)) // .Insertion
    
            childrenProxy.addObject(NSNumber(integer: 30)) // .Insertion
    
            childrenProxy.removeObjectAtIndex(1)  // .Removal
        }
    }