【iOS】Swift系列二十八 - 数组

Swift中的数组有一些高级API非常好用(用法类似JavaScript)。

一、Array的常见操作

基础代码:

1
2
var arr = [1, 2, 3, 4]
print(arr) // 输出:[1, 2, 3, 4]

1.1. map(映射)

遍历每一个元素,元素在函数(闭包表达式)中处理完成后把返回值放到新的数组,返回一个新数组。

示例代码:

1
2
3
4
5
6
7
8
9
var arr2 = arr.map {
element -> Int in
return element * 2
}
print(arr2) // 输出:[2, 4, 6, 8]

// 简化写法
// var arr2 = arr.map { $0 * 2 }
// print(arr2) // 输出:[2, 4, 6, 8]

注意:map的闭包表达式返回值类型是泛型,也就意味返回新数组元素的类型可以是任意类型。

1.2. filter(过滤)

遍历每一个元素,满足函数(闭包表达式)条件(true)后把元素放到新的数组,返回一个新数组。

示例代码:

1
2
3
4
5
6
7
8
9
var arr2 = arr.filter {
element -> Bool in
return element % 2 == 0
}
print(arr2) // 输出:[2, 4]

// 简化写法
// var arr2 = arr.filter { $0 % 2 == 0 }
// print(arr2) // 输出:[2, 4]

1.3. reduce(累计)

第一个参数是初始值。第二个参数是闭包表达式(第一个参数是闭包返回值,首次传入的参数值是reduce函数的第一个参数即初始值,第二个参数是数组元素,函数返回值作为下一次遍历时闭包表达式的第一个参数),数组遍历完成后把结果返回。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
var arr2 = arr.reduce(0) {
(result, element) -> Int in
return result + element
}
print(arr2) // 输出:10

// 简化写法一
// var arr2 = arr.reduce(0) { $0 + $1 }
// print(arr2) // 输出:10

// 简化写法二
var arr2 = arr.reduce(0, +)
// print(arr2) // 输出:10

使用reduce实现map功能。

示例代码:

1
2
var arr2 = arr.reduce([]) { $0 + [$1 * 2] }
print(arr2) // 输出:[2, 4, 6, 8]

使用reduce实现filter功能。

示例代码:

1
2
var arr2 = arr.reduce([]) { $1 % 2 == 0 ? $0 + [$1] : $0 }
print(arr2) // 输出:[2, 4]

1.4. flatMap(元素平铺映射)

map是返回一个泛型元素数组,返回值是什么,最终返回的数组元素就是什么。flatMap返回元素值是Sequence类型,而数组就是Sequence类型,如果flatMap传入的是一个数组,函数会把传入的数组所有元素平铺放到一个新的数组中返回。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
// repeating:重复元素  count:重复次数
var arr2 = Array.init(repeating: 3, count: 4);
print(arr2) // 输出:[3, 3, 3, 3]

var arr3 = arr.map {
Array.init(repeating: $0, count: $0)
}
print(arr3) // 输出:[[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]

var arr4 = arr.flatMap {
Array.init(repeating: $0, count: $0)
}
print(arr4) // 输出:[1, 2, 2, 3, 3, 3, 4, 4, 4, 4]

1.5. compactMap(元素压缩映射)

compactMap可以自动解包可选值,非nil元素会被解包放到新的数组中。

示例代码:

1
2
3
4
5
6
var arr = ["123", "idbeny", "hello", "-20"];
var arr2 = arr.map { Int($0) }
print(arr2) // 输出:[Optional(123), nil, nil, Optional(-20)]

var arr3 = arr.compactMap { Int($0) }
print(arr3) // 输出:[123, -20]

1.6. lazy

直接使用map会一次性把数组所有元素都遍历一遍。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let result = arr.map {
(i: Int) -> Int in
print("mapping \(i)")
return i * 2
}
print("begin---")
print("mapped \(result[0])")
print("mapped \(result[1])")
print("mapped \(result[2])")
print("end---")

/*
输出:
mapping 1
mapping 2
mapping 3
mapping 4
begin---
mapped 2
mapped 4
mapped 6
end---
*/

上面示例可以看出,使用数组result前,数组已经被map遍历完成。有没有可能用到result时再去执行map?可以的,使用lazy

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let result = arr.lazy.map {
(i: Int) -> Int in
print("mapping \(i)")
return i * 2
}
print("begin---")
print("mapped \(result[0])")
print("mapped \(result[1])")
print("mapped \(result[2])")
print("end---")

/*
输出:
begin---
mapping 1
mapped 2
mapping 2
mapped 4
mapping 3
mapped 6
end---
*/

上面示例很明显看到只有用到result时才去执行map函数。

二、Optional的map和flatMap

map对可选类型操作时,闭包表达式传入的是解包后的值,map返回的类型也是可选类型。如果传入的可选类型是nil,返回的也是nil

示例代码一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var num1: Int? = 10
print(num1 as Any) // 输出:Optional(10)

var num2 = num1.map {
i -> Int in
print(i) // 输出:10
return i * 2
}
print(num2 as Any) // 输出:Optional(20)

var num3: Int? = nil
print(num3 as Any) // 输出:nil

var num4 = num3.map { $0 * 2}
print(num4 as Any) // 输出:nil

如果map传入的是可选类型,闭包表达式中返回的也是可选类型,最终返回的是双重可选类型(会自动再次包装一层可选类型)。但是flatMap发现返回的是可选类型时不会再次包装,如果不是可选类型就会再次包装一层。

示例代码二:

1
2
3
4
5
6
var num1: Int? = 10
var num2 = num1.map { Optional.some($0 * 2) }
print(num2 as Any) // 输出:Optional(Optional(20))

var num3 = num1.flatMap { Optional.some($0 * 2) }
print(num3 as Any) // 输出:Optional(20)

应用场景一:
下面代码中num2num3是等价的。

1
2
3
var num1: Int? = 10
var num2 = (num1 != nil) ? (num1! + 10) : nil
var num3 = num1.map { $0 + 10 }

应用场景二:
下面代码中date1date2是等价的。

1
2
3
4
5
var fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd"
var str: String? = "2008-08-08"
var date1 = str != nil ? fmt.date(from: str!) : nil
var date2 = str.flatMap(fmt.date)

上面示例中为什么使用flatMap,不使用map?因为fmt.date函数返回值是可选类型,使用map会被再次包装一层可选类型。

应用场景三:
下面代码中str1str2是等价的。

1
2
3
var score: Int? = 98
var str1 = score != nil ? "Score is \(score)" : "No score"
var str2 = score.map { "Score is \($0)" } ?? "No score"

??前面如果是可选类型,后面是非可选类型,当可选类型值非nil时会自动解包。

应用场景四:
根据name找到对应Person实例。getPerson1getPerson2是等价的。

数组的first函数可以根据索引找到第一个符合要求的对应元素,firstIndex可以根据索引找到第一个符合要求的对应元素(这两个函数的闭包表达式入参都是数组元素,返回值都是Bool类型,如果为true就把对应索引或元素返回并且停止遍历。需要注意的是返回值都是可选类型)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Person {
var name: String
var age: Int
}
var items = [
Person(name: "jack", age: 20),
Person(name: "rose", age: 21),
Person(name: "kate", age: 22)
]
func getPerson1(_ name: String) -> Person? {
let index = items.firstIndex { $0.name == name }
return index != nil ? items[index!] : nil
}
func getPerson2(_ name: String) -> Person? {
return items.firstIndex { $0.name == name }.map { items[$0] }
}

应用场景五:
字典转模型。下面示例中p1p2的代码是等价的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Person {
var name: String
var age: Int
init?(_ json: [String : Any]) {
guard let name = json["name"] as? String,
let age = json["age"] as? Int
else {
return nil
}
self.name = name
self.age = age
}
}
var json: Dictionary? = ["name" : "idbeny", "age" : 18]
var p1 = json != nil ? Person(json!) : nil
var p2 = json.flatMap(Person.init)