Swift基础知识:`for` 循环介绍及应用

163 阅读6分钟

Swift 提供了许多不同的内置方式来迭代集合(如数组、集合和字典)--其中最基本的是for 循环,它让我们为在特定集合中找到的每个元素运行一段代码。例如,在这里,我们在一个names 的数组中进行循环,然后通过打印到控制台来输出每个名字。

let names = ["John", "Emma", "Robert", "Julia"]

for name in names {
    print(name)
}

完成同样事情的另一种方法是在我们的names 数组上调用forEach 方法,这让我们传递一个闭包,对每个元素都要运行。

names.forEach { name in
    print(name)
}

不过,for 循环和forEach 的一个关键区别是,后者不能让我们break 迭代,以便在满足给定的条件后停止它。例如,当再次使用for 循环时,我们可以决定在遇到Robert这个名字时停止迭代。

let names = ["John", "Emma", "Robert", "Julia"]

for name in names {
    if name == "Robert" {
        break
    }

    print(name) // Will only print "John" and "Emma"
}

在Swift中,还有很多其他的方法来使用for 循环。例如,如果我们想获得我们目前正在处理的index ,作为我们迭代的一部分,那么我们可以选择将我们的循环建立在一个范围上,从零到我们集合中的元素数量。然后我们可以使用Array 类型的下标功能来检索该索引的当前元素--像这样:

for index in 0..<names.count {
    print(index, names[index])
}

另一种写出完全相同的循环的方法是在我们的数组的indicies ,而不是手动构建一个范围:

for index in names.indices {
    print(index, names[index])
}

还有一种方法是使用enumerated 方法将我们的数组转换为一个包含图元的序列,将每个索引与相关元素配对:

for (index, name) in names.enumerated() {
    print(index, name)
}

请注意,enumerated 方法总是使用基于Int 的偏移量,这在Array 的情况下是一个完美的匹配,因为该集合也使用Int 的值作为其索引。

接下来,让我们看看while 循环,它为我们提供了一种方法来重复运行一个代码块,只要给定的布尔条件保持true 。例如,我们可以使用while 循环来不断地将我们的names 数组中的每个名字追加到一个字符串中,只要该字符串包含少于8个字符。

let names = ["John", "Emma", "Robert", "Julia"]
var index = 0
var string = ""

while string.count < 8 {
    string.append(names[index])
    index += 1
}

print(string) // "JohnEmma"

另一种构建while 循环的方法(在Swift中可能不像其他语言那样常用)是使用一个单独的repeat 块,只要我们的while 条件评估为true ,该块也会被重复运行。

let names = ["John", "Emma", "Robert", "Julia"]
var index = 0
var string = ""

repeat {
    string.append(names[index])
    index += 1
} while string.count < 8

print(string) // "JohnEmma"

repeat 和独立的while 循环之间的关键区别是,repeat 块总是至少被运行一次,即使附加的while 条件最初被评估为false

不过,在使用while 循环时,有一件重要的事情要记住,那就是我们要确保每个循环在适当的时候结束--可以通过手动使用break (就像我们之前使用for 循环时那样),或者通过确保我们的循环的布尔条件在迭代应该终止时得到满足。

例如,当构建我们基于名字的string 值时,我们可能想确保当前的index 不会超出我们的names 数组的范围--因为否则我们的应用程序在下标到该数组时将崩溃。做到这一点的一个方法是在我们的while 语句中附加第二个布尔条件--像这样:

while string.count < 8, index < names.count {
    string.append(names[index])
    index += 1
}

另一种方法是在循环本身中执行上述索引检查,例如,通过使用一个guard 语句,在其else 子句中打破我们的循环:

while string.count < 8 {
    guard index < names.count else {
        break
    }

    string.append(names[index])
    index += 1
}

当使用循环时,通常有许多不同的方法来模拟相同的逻辑,而且通常对一些不同的方法进行原型化,以找出在每种情况下最有效的方法,这是很有用的。作为一个例子,我们实际上可以选择用一个for 循环来实现上述迭代--因为我们可以通过使用where 关键字将我们基于string.count 的条件附加到这样一个循环中:

let names = ["John", "Emma", "Robert", "Julia"]
var string = ""

for name in names where string.count < 8 {
    string.append(name)
}

print(string) // "JohnEmma"

这并不意味着上述基于for 的版本在客观上比基于while 的版本更好。选择使用哪种类型的循环往往是一个品味和代码结构的问题,尽管我个人认为,只要循环是基于一个实际的元素集合,使用for 循环往往是最简单的方法。

最后,让我们把注意力转移到字典上,它也可以使用与我们在数组和范围中的循环完全相同的工具来进行迭代。不过,当在字典上迭代时,我们不只是访问单个元素,而是访问(key, value) 元组对。例如,这里是我们如何迭代一个包含我们的names 数据集的分类版本的字典。

let namesByCategory = [
    "friends": ["John", "Emma"],
    "family": ["Robert", "Julia"]
]

for (category, names) in namesByCategory {
    print(category, names)
}

但有时,我们可能不需要访问一个给定的字典所包含的键和值,所以也可以使用_ 符号来忽略我们迭代中的一个给定的元组成员。这里我们可以这样做,忽略我们的 dictionary 的值,而只在我们的循环中处理它的键(或类别)。

for (category, _) in namesByCategory {
    print(category)
}

然而,虽然上面的代码是完全有效的 Swift 代码,但实际上有两个专门的 API 使我们能够通过访问我们想迭代的 dictionary 上的keysvalues 属性来执行仅有键或只有值的 dictionary 迭代。

for category in namesByCategory.keys {
    print(category)
}

for names in namesByCategory.values {
    print(names)
}

当处理字典、集合或其他不提供保证元素顺序的集合时,重要的是要记住我们的循环也不会以可预测的顺序进行。因此,例如,如果我们想确保上面的category 迭代总是以相同的顺序发生,那么我们可以通过首先将我们字典的keys 排序到一个数组中来实现这一点--像这样。

for category in namesByCategory.keys.sorted() {
    print(category)
}

我们的类别现在将总是按照字母顺序进行迭代。当然,只有当元素的顺序很重要时,才需要执行上述的排序操作,这可能只适用于某些迭代。

谢谢你的阅读!