Swift: 子类 NSMutableArray

Swift: subclass NSMutableArray

我正在尝试在 Swift 中子类化 NSMutableArray,以便为 Swift 数组提供一些旧的 Objective-C 代码库的桥接功能。但是,我找不到一个功能示例,到目前为止我的所有尝试都失败了。这是我最接近的功能示例:

import UIKit

class MyArray: NSMutableArray {

    var array: [Int]?

// Adding an initializer triggers the error: 'required' initializer 'init(arrayLiteral:)' must be provided by subclass of 'NSArray'
//    override init() {
//        self.array = []
//        super.init()
//    }

    static func makeArray() -> MyArray {
        let array = MyArray()
        array.array = []
        return array
    }

    override var count: Int {
        get {
            if let count = array?.count {
                return count
            }

            return 0
        }
    }

    override func insert(_ anObject: Any, at index: Int) {
        print("insert called")
    }

    override func removeObject(at index: Int) {
        print("removeObject called")
    }

    override func add(_ anObject: Any) {
        print("Trying to add \(anObject)")
        if let newInt = anObject as? Int {
            array?.append(newInt)
        }
    }

    override func removeLastObject() {
        print("removeLastObject called")
    }

    override func replaceObject(at index: Int, with anObject: Any) {
        print("replaceObject called")
    }
}

func append42(array: NSMutableArray) {
    array.add(42)
}

let myArray = MyArray.makeArray()

print("Before: \(myArray.array)")
myArray.array?.append(42)
// The following two lines generate a SIGABRT
//myArray.add(42)
//append42(array: myArray)
print("After: \(myArray.array)")

但是,使用 add 方法总是会触发 SIGABRT。我在 Catalina 上使用 XCode 12.4。

编辑(澄清):我知道 Swift 桥接 Arrays 到 NSArrays。但是,我需要将对 Swift 数组的引用传递给一些接受 NSMutableArray 并可能更新数组的 Objective-C 代码。

本质上我是在现有项目中添加一些 Swift 代码,我想在新代码中使用 Swift 类型数组,但我仍然将这些数组传递给旧代码。我知道我可以制作 Swift 数组的 NSMutableArray 副本,将此副本传递给旧代码,然后将其复制回 Swift 数组,但它相当复杂且难以维护。我试图做的是将对 Swift 数组的引用封装到 'NSMutableArray' 子类中,这样我就可以透明地将这个子类与遗留代码一起使用。

aware是:

When importing the Foundation framework, the Swift overlay provides value types for many bridged reference types. Many other types are renamed or nested to clarify relationships.

特别是:

Class clusters that include immutable and mutable subclasses, like NSArray and NSMutableArray, are bridged to a single value type.

因此您可以使用 Array 代替 NSArray 如果您想要 NSMutableArray 也是 Swift Array(但是 var). 同样的事情适用于 DictionaryNSDictionary.

免责声明

我可能不会在生产代码中使用它,尽管不禁止子类化 NSArrayNSMutableArray,文档说:

There is typically little reason to subclass NSMutableArray.

这应该足以考虑另一种解决方案恕我直言

编辑

看完这篇answer

我决定检查一下 NSMutableArray documentation:

Methods to Override

NSMutableArray defines five primitive methods:

insert(_:at:)

removeObject(at:)

add(_:)

removeLastObject()

replaceObject(at:with:)

In a subclass, you must override all these methods. You must also override the primitive methods of the NSArray class.

所以我也检查了 NSArray documentation:

Any subclass of NSArray must override the primitive instance methods count and object(at:). These methods must operate on the backing store that you provide for the elements of the collection. For this backing store you can use a static array, a standard NSArray object, or some other data type or mechanism. You may also choose to override, partially or fully, any other NSArray method for which you want to provide an alternative implementation.

现在,有:

@objc class MyArray: NSMutableArray {

    var array: [Int] = []
    static func makeArray() -> MyArray {
        let array = MyArray()
        array.array = []
        return array
    }

    override var count: Int {
        array.count
    }
    
    override func object(at index: Int) -> Any {
        array[index]
    }

    override func insert(_ anObject: Any, at index: Int) {
        print("insert called")
        if let newInt = anObject as? Int {
            array.insert(newInt, at: index)
        }
    }

    override func removeObject(at index: Int) {
        array.remove(at: index)
        print("removeObject called")
    }

    override func add(_ anObject: Any) {
        print("Trying to add \(anObject)")
        if let newInt = anObject as? Int {
            array.append(newInt)
        }
    }
}

func append42(array: NSMutableArray) {
    array.add(42)
}

我有:

let myArray = MyArray.makeArray()

print("Before: \(myArray.array)")
myArray.array.append(42) // [42]
// The following two lines generate a SIGABRT
myArray.add(42) // [42, 42]
append42(array: myArray)
print("After: \(myArray.array)")

// Before: []
// Trying to add 42
// Trying to add 42
// After: [42, 42, 42]

有趣的事实

  • 我在操场上尝试了您的代码并重现了崩溃。我试过 XCTestCase 但它没有崩溃
  • add(_:)中调用super.add(_:)会触发insert(_:at:)
  • NSMutableArrayExpressibleByArrayLiteral 的一致性可能写在扩展中(这很有意义,因为 NSMutableArray 是 Objective-C class),因此覆盖init() forces 你覆盖 init(arrayLitteral:) 并且编译器说 不支持扩展中的覆盖声明 。 :

Automatic Initializer Inheritance

Rule 1

If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.

Rule 2

If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.

我们一般应该遵循开闭原则,即OCP,即类对扩展开放,对修改关闭。这种继承 NSMutableArray 并替换一些关键方法的尝试违反了 OCP。它很可能表现出各种奇怪的行为。

如果你真的想子类化,你会想要覆盖NSArrayNSMutableArray的所有原始方法。从理论上讲,您可以检查 Objective-C 代码并查看它调用了哪些方法,但这是一种非常脆弱的方法。我不建议使用这种模式。

就个人而言,如果我试图与一些需要 NSMutableArray 的 Objective-C 代码集成,我会坚持使用 Swift 数组并在我调用之前创建一个 NSMutableArray我的 Objective-C 库,然后将其复制回数组(与 Swift 数组的值语义一致)。

考虑:

@implementation Foo
- (void)bar:(NSMutableArray *)array {
    [array addObject:@42];
}
@end

我会用垫片代替 Swift Array:

extension Foo {
    func bar(_ values: inout [Int]) {
        let array = NSMutableArray(array: values)
        bar(array)
        values = array as! [Int]
    }
}

那么我可以这样做:

var values = [1, 2]
values.append(3)
let foo = Foo()
foo.bar(&values)
print(values)                       // 1, 2, 3, 42

但我会避免避免子类化 NSMutableArray,因为我碰巧调用了一些使用 NSMutableArray 的 Objective-C 例程。只有在基准测试(或其他考虑因素)认为绝对必要时,我才会这样做。


让我们暂时假设您绝对需要坚持使用 NSMutableArray(例如,您有数百万条记录并且不断调用此 Objective-C 例程)。在那种情况下,我仍然不建议子类化 NSMutableArray,而是直接在我的 Swift 代码中使用 NSMutableArray。如果我想从我的 NSMutableArray 中检索 Int 值并且不想到处投射,我会添加一个 extensionNSMutableArray:

extension NSMutableArray {
    func append(integer value: Int) {
        add(value)
    }

    func intValue(at index: Int) -> Int {
        return self[index] as! Int
    }
}

那么你可以这样做:

let values: NSMutableArray = [1, 2]
values.append(integer: 3)

let foo = Foo()
foo.bar(values)

print(values)                       // 1, 2, 3, 42

let value = values.intValue(at: 3)
print(value)                        // This is `Int` value of 42

现在,我知道这不是您所希望的答案,即您不想在仍然使用此 Objective-C遗留代码。但至少这两种方法为您提供了类型安全的整数值设置和检索。