Swift 中的 Collections (一):Array

这篇文章重点不是介绍Array Dictonary Set Range的简单使用的,文章重点是 Swift Collection中你可能不知道的东西,文章既有如何创建一个数组,也有ArraySlice(数组切片)的说明。之所以有一些很基础的东西,是看到很多Swift使用者在一知半解,在项目里写了很多怪异的写法,就算掌握了,跟着过一遍,也花不了几分钟。

Array : 数组

创建一个数组

  • 创建一个空数组
let array1: Array<Int> = Array<Int>()
let array2: [Int] = []
  • 创建带有初始值的数组
let array1 = [1, 2, 3] // [1, 2, 3, 4, 5]
let array2 = [Int](repeating: 0, count: 3) // [0, 0, 0]

可变性

在上边,我们声明数组是用let关键字,也就是我们把数组声明成常量了,如果像下边这么写,会得到一个编译错误:

let array = [1, 2]
array.append(3)
error: cannot use mutating member on immutable value: 'array' is a 'let' constant
array.append(3)

如果需要改变数组中的元素,需要使用var关键词定义:

var array = [1, 2]
array.append(3)    //[1, 2, 3]
array.append(contentsOf: [4, 5])    //[1, 2, 3, 4, 5]

数组和标准库中的所有集合类型一样,是具有值语义的。当你创建一个新的数组变量并且把一个已经存在的数组赋值给它的时候,这个数组的内容会被复制。举个例子,在下面的代码中,x 将不会被更改:

var x = [1,2,3]
var y = x
y.append(4)
y // [1, 2, 3, 4]
x // [1, 2, 3]

var y = x 语句复制了 x,所以在将 4 添加到 y 末尾的时候,x 并不会发生改变,它的值依然是 [1,2,3]。当你把一个数组传递给一个函数时,会发生同样的事情;方法将得到这个数组的一份本地复制,所有对它的改变都不会影响调用者所持有的数组。

Array常用操作

添加删除元素

var array = [1, 2]

// 向数组添加一个元素
array.append(3)    //[1, 2, 3]

// 拼接数组
array.append(contentsOf: [4, 5])    //[1, 2, 3, 4, 5]

// 插入元素
array.insert(6, at: array.endIndex)   // [1, 2, 3, 4, 5, 6]

// 删除对应index的元素
array.remove(at: 5) // [1, 2, 3, 4, 5]

// 删除最后一个元素
array.removeLast() // [1, 2, 3, 4]

count & isEmtpy & forEach

let array = [1, 2]

// 数组元素个数
array.count    // 2
// 数组是否为空
if array.isEmpty {
    print("array is empty")
}
// 遍历数组
array.forEach {element in
    print(element)
}

map & flatmap & filter & reduce

map & flatmap & filter & reduce 的用法,我之前写过,
点击这里

访问数组中元素

定义一个数组

var array = [1, 2, 3, 4, 5]

使用下标访问元素:

array[0]    // 1
array[1]    // 2

array[10]    //超出长度,carsh

使用range operator访问范围内元素:

array[0...2] // [1, 2, 3]
array[0..<2] // [1, 2]

使用下标访问数组元素会有一个严重的问题,就是下标超出数组范围会carsh,说好的Swift更安全呢? 其实,使用下边访问数组元素是Swift不推荐的方法Swift 3 中传统的 C 风格的 for 循环被移除了,这是 Swift 不鼓励你去做索引计算的一个标志。手动计算和使用索引值往往可能带来很多潜在的 bug,所以最好避免这么做。
但是有些时候我们又不得不使用索引,为什么不在使用索引时提供可选值呢,比如:

let i = array[10] 
i: Optional<Int> // 让取到的值是一个optional,这样可以避免崩溃

因为当你使用数组索引的时候,Swift默认你应该已经深思熟虑,对背后的索引计算逻辑进行过认真思考。不提供可选值。Swift默认你信任你的代码,在这个前提下,如果每次都要对获取的结果进行解包的话就显得多余了。而且如果提供可选值,一方面十分麻烦,每次都要解包,另一方面也是一个坏习惯。当强制解包变成一种习惯后,很可能你会不小心强制解包了本来不应该解包的东西。所以,为了避免这个行为变成习惯,数组根本没有给你可选值的选项。
如果你真的想要通过下标访问元素,同时还想防止崩溃的产生,可以通过给Array 进行拓展来实现这个功能:

extension Array {
    subscript(safe idx: Int) -> Element? {
        return idx < endIndex ? self[idx] : nil
    }
}

取出数组元素的时候像这样:

array[safe: 100]  // nil ,不会崩溃
array[safe: 1]  //Optional<Int> ,需要解包

Swift 不鼓励你去做索引计算主要原因是我们可以使用数组切片。什么叫数组切片,下边再解释。先看在数组中,不使用下标直接访问元素,如何取到数组中的元素:

// 迭代数组
for x in array {
    ...
}
// 迭代除了第一个元素以外的数组其余部分
for x in array.dropFirst() {
   ...
}
// 迭代除了最后 5 个元素以外的数组
for x in array.dropLast(5) {
   ...
}
// 列举数组中的元素和对应的下标
for (num, element) in collection.enumerated() {
    ...
}
// 寻找一个指定元素的位置
if let idx = array.index { someMatchingLogic($0) } {
    ...
}
// 对数组中的所有元素进行变形
array.map { someTransformation($0) } 
// 筛选出符合某个标准的元素
array.filter { someCriteria($0) }

ArraySlice : 数组切片

在上边的例子中,我们发现array.dropLast(5) 或者 array[0...2] 得到的并不是一个 Array<Int> 类型的数组,而是一个 ArraySliceArraySlice 也就是数组切片,切片类型只是数组的一种表示方式,它背后的数据仍然是原来的数组,只不过是用切片的方式来进行表示。这意味着原来的数组并不需要被复制。ArraySlice 具有的方法和 Array 上定义的方法是一致的,因此你可以把它们当做数组来进行处理。简单来说,就是Array某一段内容的view,它不真正保存数组的内容,只保存这个view引用的数组的范围:

ArraySlice

(图自:《Swift 进阶》)
图中,fibs 表示一个斐波那契数列数组,即 [0, 1, 1, 2, 3, 5 ,...]
slice表示fibsArraySlice
fibs中包含了数组的长度 6 和一个指向 数组内存地址的指针prt
slice包含了一个同样指向 数组内存地址的指针prt,同时还有view起始Index 1,和结束Index6,我们可以通过改变这个位置的起止区间,来表示fibs这个数组的不同view

如果你需要将切片转换为数组的话,你可以通过将切片传递给 Array 的构建方法来完成:

Array(slice)

Array 与 NSArray

我们知道在Swift中,Array是按照值语义实现的,当我们这么写的时候,其实是拷贝了一份新的Array

var array1 = [0, 1, 2] // [0, 1, 2]
let array2 = array1 // [0, 1, 2]

// 对array1进行操作不会影响array2
array1.append(3) // array1: [0, 1, 2, 3] ; array2: [0, 1, 2]

// 因为array2 被用let 声明为常量,所以是不可变的
array2.append(3)  // Compile error 

Swift对值类型的拷贝处理其实是使用copy on write的方式,当你复制Array时,真正的复制是不会发生的,两个数组同样引用同一个内存地址。只有当你修改了其中一个Array的内容时,才会复制一份Array并更改指针指向位置。

Foundatioin中,数组包括 NSArrayNSMutableArray
NSArrayNSMutableArray都是类对象,数组是否可以被修改是通过NSArrayNSMutableArray这两个类型来决定的,我们知道,在Objective-C中,复制它们执行的是引用语义,如果在Swift中使用 NSArrayNSMutableArray :会产生让人很奇怪的感觉:

let array = NSMutableArray(array: [1, 2, 3])

在上边的一句中:我们明明使用了 letarray 声明成一个常量,但是其实是可以通过:array.add(3) 来向 array 添加一个元素的,编译器并不会报错。

当我们用var声明一个NSArray时,我们希望他是可变数组,其实情况呢:

var mutableArray = NSArray(array: [1, 2, 3])

因为mutableArrayNSArray 类型的,你根本无法调用改变 mutableArray 元素的方法。

结论: 当我们使用 NSArrayNSMutableArray 时,Swift中的 varlet 只控制对应的变量是否可以被赋值成新的对象,并不能控制数组元素是否可以修改。

因为 NSArrayNSMutableArray 复制时执行的是引用语义,他们指向同一个内存地址,改变其中一个的元素时,另一个的也会改变:

let array1 = NSMutableArray(array: [1, 2, 3])
let array2: NSArray = array1

array1.insert(0, at: 0) // array1:[0, 1, 2, 3] ;array2:[0, 1, 2, 3]

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

let array1 = NSMutableArray(array: [1, 2, 3])
let array2: NSArray = array1
let deepCopyArray = array1.copy() as! NSArray

array1.insert(0, at: 0) // array1:[0, 1, 2, 3] ;array2:[0, 1, 2, 3] ; deepCopyArray:[1, 2, 3]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容