理解Array和NSArray的差异

2018-02-28  本文已影响0人  Lorne_coder

按值语义实现的Array

在Swift中,Array是按照值语义实现的,当我们复制一个Array对象时,会拷贝整个Array的内容:

var a = [1, 2, 3] // [1, 2, 3]
let copyA = a     // [1, 2, 3]

a.append(4)
// a  [1, 2, 3, 4]
// copyA [1, 2, 3]
// copyA.append(4)   // Compile error

上面的代码中,有两点值得说明。

首先,Swift数组是否可以被修改完全是通过varlet关键字来决定的,Array类型自身并不解决它是否可以被修改的问题;

其次,从结果可以看到,复制a并向a添加内容之后,copyA的内容并不会修改。但是,Swift在复制Array时,同样对Array的性能有所考量,它使用了copy on write的方式。即如果你仅仅复制了Array而不对它修改时,真正的复制是不会发生的,两个数组仍旧引用同一个内存地址。只有当你修改了其中一个Array的内容时,才会真正让两个Array对象分开。为了看到这个过程,我们先来实现一个方法,把保存Array内容的地址变成一个字符串:

func getBufferAddress<T>(of array: [T]) -> String {
    return array.withUnsafeBufferPointer { buffer in
        return String(describing: buffer.baseAddress)
    }
}

其中,withUnsafeBufferPointerArray的一个方法,它可以把保存Array内容的地址,传递给它的closure参数。在我们的例子里,这个closure只是把Array的地址,变成了一个String对象。

然后,我们在a.append(4)前后,分别观察acopyA的内容:

getBufferAddress(of: a)
getBufferAddress(of: copyA)

a.append(4)

getBufferAddress(of: a)
getBufferAddress(of: copyA)

Array VS NSArray

就如同图中显示的,只有在给a添加内容后,它才被重新分配了内存地址。

了解了Swift Array之后,我们再来看Foundation中NSArray的情况。

按引用语义实现的NSArray

在Foundation中,数组这个类型有两点和Swift Array是不同的:

当把这两个因素放在一起的时候,Foundation中的“常量数组”这个概念就会在一些场景里表现的很奇怪。因为你还可以通过对一个常量数组的非常量引用去修改它,来看下面的例子:

// Mutable array [1, 2, 3]
let b = NSMutableArray(array: [1, 2, 3])
// Const array [1, 2, 3]
let copyB: NSArray = b

// [0, 1, 2, 3]
b.insert(0, at: 0)
// [0, 1, 2, 3]
copyB

从上面的代码可以看到,尽管我们在创建copyB时,使用了NSArray,表明我们不希望它的值被修改,由于这个赋值执行的是引用拷贝,因此,实际上它和b指向的是同一块内存空间。因此,当我们修改b的内容时,copyB也就间接受到了影响。

为了在拷贝NSArray对象时,执行值语义,我们必须使用它的copy方法复制所有的元素:

let b = NSMutableArray(array: [1, 2, 3])
let copyB: NSArray = b
let deepCopyB = b.copy() as! NSArray

b.insert(0, at: 0) // [0, 1, 2, 3]
copyB              // [0, 1, 2, 3]
deepCopyB          // [1, 2, 3]

从注释中的结果,你就能很容易理解deep copy的含义了。

当我们使用NSArrayNSMutableArray时,Swift中的varlet关键字就和数组是否可以被修改没关系了。它们只控制对应的变量是否可以被赋值成新的NSArrayNSMutableArray对象。

上一篇 下一篇

猜你喜欢

热点阅读