Swift中的for-in循环教程

1,065 阅读6分钟

通俗地说,当一个东西在循环中运行时,它就会重复相同的事情。例如,一个循环就是迭代博客文章的数量并在主页面上显示它们。

Swift中的控制流有不同类型的循环。这些是for-inforEachwhilerepeat-while 循环。在这篇文章中,我们将通过Swift中for-in 循环的基本概述。然后,我们将用不同数据类型的例子和用例来演示如何使用它们。

我们将重点介绍以下内容。

  • for-in 循环的语法
  • 数组
  • 范围和跨度
  • 字典
  • 枚举

要继续学习,你应该有Swift语言的基本知识。

for-in 循环的语法

语法以单词for 开始,然后是作为常量创建的循环中的特定元素。接着是单词in ,最后是你想循环的序列。

for element in elements {
    // do something with the element
}

例如,我们有一个股票列表,每个股票包括其价格,在一个特定的日期。

struct Stock {
    var name: String
    var price: Double
    var date = Date()
}

我们想在这个数组上循环,并打印每只股票的数据。循环的语法是这样的。

// MARK: - EXAMPLE
func printDetails(for stocks: [Stock]) {
    for stock in stocks {
        print(stock.name)
        print(stock.price)
        print(stock.date)
    }
}

// MARK: - USAGE
let stocks = [Stock(name: "Banana", price: 125),
              Stock(name: "TapeBook", price: 320),
              Stock(name: "Ramalon", price: 3200)]

printDetails(for: stocks)

// MARK: - OUTPUT
Banana
125.0
2021-05-21 22:40:42 +0000
TapeBook
320.0
2021-05-21 22:40:42 +0000
Ramalon
3200.0
2021-05-21 22:40:42 +0000

有了基本语法的知识,让我们继续循环基本数据结构:Array!

数组

来自Swift官方文档,"一个数组在一个有序的列表中存储相同类型的值。同一个值可以在数组中的不同位置多次出现。"

我们使用for-in 循环来迭代存储的值,然后访问数组中的每个值。

基本例子

假设一个应用程序,我们正在跟踪一个用户的慢跑。在每个地点,我们都想跟踪他们的速度。因此,在应用程序中,我们收到一个位置数组。

let locations: [CLLocation] = []

我们循环浏览这个数组,对于每个地点,我们打印该特定地点的速度。

for location in locations {
    print("The speed at location (\(location.coordinate.latitude), \(location.coordinate.longitude) is \(location.speed)")
}

再举个例子,我们创建一个10×10的二维数组,打印每个点的数值。

var board: [[Int]] = Array(repeating: Array(repeating: 0, count: 10), count: 10)

for row in board {
    for number in row {
        // prints 0, hundred times
        print(number)
    }
}

使用where 子句

有些情况下,我们想把序列只限制在符合特定条件的元素上。在这种情况下,我们使用where 关键字。

在一个待办事项应用程序中,我们需要所有目标中已完成目标的子集。假设有这样一个模型。

struct Goal: Identifiable, Hashable {
    var id = UUID()
    var name: String = "Goal Name"
    var date = Date()
    var goalCompleted: Bool = false
}

而我们的应用程序有一个数组,用于Goal 。我们想循环浏览这个数组,只访问那些已经完成的目标。

// MARK: - EXAMPLE
func getCompletedGoals(for goals: [Goal]) {
    for goal in goals where goal.goalCompleted == true {
        /// Access to completed goals only.
        print(goal)
    }
}

// MARK: - USAGE
let goals = [Goal(name: "Learn basic syntax of for-in loops", goalCompleted: true),
             Goal(name: "Read about for-in loops and dictionaries"),
             Goal(name: "Read about for-in loops and enums")]

getCompletedGoals(for: goals)

// MARK: - OUTPUT
Goal(id: B7B148D6-853B-486A-8407-CD03A904B348, name: "Learn basic syntax of for-in loops", date: 2021-05-21 22:50:38 +0000, goalCompleted: true)

使用enumerated()

要同时访问每个元素的索引,我们可以使用实例方法enumerated() 。它返回一个包含索引以及元素值的对的序列。以前面的例子为例,如果我们想列出数组中位置的索引,我们可以这样写。

for (index, location) in locations.enumerated() {
    print("The speed at location (\(location.coordinate.latitude), \(location.coordinate.longitude) is \(location.speed)")

    print("The index for this location is \(index)") 
}

使用indices

如果我们只想要数组中元素的索引,我们可以使用indices 。这表示数组中的有效索引以升序排列。它从0循环到数组中的最后一个元素,即:array.count

for index in array.indices {
    // Access the index
}

使用我们之前创建的二维数组,我们遍历每个点,并给它分配一个随机的整数值。

// MARK: - EXAMPLE
func updateValues(of board: inout [[Int]]) {
    for rowIndex in board.indices {
        for columnIndex in board[0].indices {
            board\[rowIndex\][columnIndex] = Int.random(in: 0..<10)
        }

        print(board[rowIndex])
    }
}

// MARK: - USAGE
var board: [[Int]] = Array(repeating: Array(repeating: 0, count: 10), count: 10)

updateValues(of: &board)

// MARK: - OUTPUT
[9, 4, 1, 7, 5, 2, 6, 4, 7, 4]
[1, 0, 1, 0, 5, 4, 5, 6, 7, 9]
[4, 7, 6, 3, 8, 9, 3, 5, 9, 5]
[8, 0, 9, 9, 6, 1, 2, 0, 2, 7]
[3, 7, 4, 1, 3, 4, 9, 9, 5, 6]
[5, 2, 5, 1, 8, 1, 8, 0, 0, 1]
[0, 4, 3, 4, 0, 6, 1, 8, 7, 5]
[7, 7, 7, 9, 1, 3, 6, 4, 0, 1]
[9, 5, 6, 5, 3, 8, 0, 1, 3, 4]
[1, 7, 7, 3, 1, 0, 7, 4, 5, 6]

使用一个可选模式

在序列包含可选值的情况下,我们可以使用for case let ,过滤掉nil值,只对非nil元素执行循环。

从前面的待办事项应用的例子来看,我们假设一些目标没有值。getCompletedGoals(for goals:) ,现在接受一个数组,可选择Goal

// MARK: - EXAMPLE
func getCompletedGoals(for goals: [Goal?]) {
    for case let goal? in goals where goal.goalCompleted == false {
        /// Access to completed goals only.
        print(goal)
    }
}

// MARK: - USAGE
let goals: [Goal?] = [Goal(name: "Learn something new!", goalCompleted: true),
                      Goal(name: "Read about for-in loops and dictionaries"),
                      nil,
                      Goal(name: "Read about for-in loops and enums"),
                      nil]

getCompletedGoals(for: goals)

// MARK: - OUTPUT
Goal(id: F6CB6D77-9047-4155-99F9-24F6D178AC2B, name: "Read about for-in loops and dictionaries", date: 2021-05-21 23:04:58 +0000, goalCompleted: false)
Goal(id: 822CB7C6-301C-47CE-AFEE-4B17A10EE5DC, name: "Read about for-in loops and enums", date: 2021-05-21 23:04:58 +0000, goalCompleted: false)

范围和stride

我们也可以使用for-in 循环来循环处理硬编码的数字范围。它们可以分为两部分。

  • 使用一个封闭的范围操作符 ()
  • 使用半开范围运算符 (..<)

使用闭合范围运算符

闭合范围运算符创建一个包括两个末端元素的范围。使用该运算符的一个基本例子是打印10个数字。这里,1和10也会被打印出来。

for number in 1...10 {
    print("The number is \(number)")
}

FizzBuzz是一个简单的编程练习,我们可以使用forfor-in 循环。提示的内容是这样的

写一个程序,打印从1到n的数字,3的倍数打印 "Fizz "而不是数字,5的倍数打印 "Buzz",对于3和5的倍数,打印 "FizzBuzz "而不是数字。

我们使用闭合范围操作符在数字1到n之间循环,创建一个ClosedRange<Int> 常数。然后,我们再次循环浏览mapping 中的元组,检查元组中的每个元素。如果这个数字是3的倍数,我们就把Fizz 加到string

当我们检查mapping 中的每个元素时,如果它也是5的倍数,我们将Buzz 附加到字符串中,结果是FizzBuzz

// MARK: - EXAMPLE
func fizzBuzz(for lastNumber: Int) {
    var result = [String]()
    let mapping = [(number: 3, value: "Fizz"), (number: 5, value: "Buzz")]

    for number in 1...lastNumber {
        var string = ""

        for tuple in mapping {
            if number % tuple.number == 0 {
                string += tuple.value
            }
        }

        if string == "" {
            string += "\(number)"
        }

        print(result)
    }
    return result
}

// MARK: - USAGE
fizzBuzz(for: 10)

// MARK: - OUTPUT
["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz"]

使用半开范围运算符

半开范围运算符创建一个不包括最后一个元素的范围。使用这个运算符的一个基本例子是访问一个数组的索引。

for index in 0..<array.count {
    // Access the index
}

使用stride

对于你想在一个循环中跳过某个特定数字的元素的情况,你可以使用stride 。我们也可以用它来在一个循环中往回走,从最后一个元素开始,走到第一个元素。

回到刚才的例子,我们用随机值创建了一个大小为10×10的二维矩阵,我们想打印第一行的每一个备用元素。

// MARK: - EXAMPLE
func printFirstRow(for board: [[Int]]) {
    for rowIndex in stride(from: board.count - 1, through: 0, by: -2) {
        print(board\[rowIndex\][0])
    }
}

// MARK: - USAGE
printFirstRow(for: board)

// MARK: - OUTPUT
7
4
4
4
8

现在,我们想打印第一列中的每一个备用元素,但顺序要相反。

// MARK: - EXAMPLE
func printFirstColumn(for board: [[Int]]) {
    for rowIndex in stride(from: board.count - 1, through: 0, by: -2) {
        print(board\[rowIndex\][0])
    }
}

// MARK: - USAGE
printFirstColumn(for: board)

// MARK: - OUTPUT
8
6
0
6
5

字典

我们也可以使用for-in 循环来遍历一个Dictionary ,尽管结果是无序的。语法与数组类似,每个元素都有其键和值。

// MARK: - EXAMPLE
func printDictionary(for numbers: [Int: Int]) {
    for number in numbers {
        // number is a Dictionary<Int, Int>.Element
        print("The value for key \(number.key) is \(number.value)")
    }
}

// MARK: - USAGE
let numbers: [Int: Int] = [1: 2, 2: 3, 3: 4]

printDictionary(for: numbers)

// MARK: - OUTPUT
The value for key 1 is 2
The value for key 2 is 3
The value for key 3 is 4

我们也可以明确地使用我们自己的关键字来代替。

// MARK: - EXAMPLE
func printStockPrices(for stocks: [String: Int]) {
    for (name, price) in stocks {
        print("\(name) is currently valued at $\(price).")
    }
}

// MARK: - USAGE
let stocks: [String: Int] = ["Banana": 125, "TapeBook": 320, "Ramalon": 3200]

printStockPrices(for: stocks)

// MARK: - OUTPUT
Banana is currently valued at $125.
Ramalon is currently valued at $3200.
TapeBook is currently valued at $320.

我们也可以在字典中使用where

// MARK: - EXAMPLE
func printStockPrices(for stocks: [String: Int]) {
    for (name, price) in stocks where name == "Banana" {
        print("\(name) is currently valued at $\(price).")
    }
}

// MARK: - USAGE
let stocks: [String: Int] = ["Banana": 125, "TapeBook": 320, "Ramalon": 3200]

printStockPrices(for: stocks)

// MARK: - OUTPUT
Banana is currently valued at $125.

如果你想要这个字典中的最高价格,你可以用sorted(by:) 对字典进行排序。

// MARK: - EXAMPLE
func printStockPrices(for stocks: [String: Int]) {
    for (name, price) in stocks.sorted(by: { $0.value > $1.value }) {
        print("\(name) is currently valued at $\(price).")
    }
}

// MARK: - USAGE
let stocks: [String: Int] = ["Banana": 125, "TapeBook": 320, "Ramalon": 3200]

printStockPrices(for: stocks)

// MARK: - OUTPUT
Ramalon is currently valued at $3200.
TapeBook is currently valued at $320.
Banana is currently valued at $125.

使用KeyValuePairs

如前所述,Dictionary 并没有定义的排序。如果你想要有序的键值对,你可以使用KeyValuePairs 。这在你愿意为线性时间牺牲快速、恒定的查找时间的情况下很有用。

// MARK: - EXAMPLE
func printStockPrices(for stocks: KeyValuePairs<String, Int>) {
    for (name, price) in stocks {
        print("\(name) is currently valued at $\(price).")
    }
}

// MARK: - USAGE
let stocks: KeyValuePairs = ["Banana": 125, "TapeBook": 320, "Ramalon": 3200]

printStockPrices(for: stocks)

// MARK: - OUTPUT
Banana is currently valued at $125.
TapeBook is currently valued at $320.
Ramalon is currently valued at $3200.

枚举

你甚至可以在Swift中通过符合一个特定的协议来迭代一个枚举,该协议名为CaseIterable 。这种类型提供了一个其所有值的集合。在我们的例子中,它提供了Enum 中的所有情况。为了访问它们,我们使用allCases 属性。

通过另一个例子,我们正在研究一个超休闲游戏。我们需要在主屏幕上设置不同的游戏模式。我们将创建一个enum ,并对其进行迭代以访问模式名称和图像名称。

enum GameModes: String {
    case arcade
    case challenge
    case casual
    case timed
}

extension GameModes {
    var name: String {
        self.rawValue.capitalized
    }

    var image: String {
        switch self {
            case .arcade: return "🕹"
            case .casual: return "🎮"
            case .challenge: return "🎖"
            case .timed: return "⏳"
        }
    }
}

extension GameModes: CaseIterable {}

// Usage
for mode in GameModes.allCases {
    let gameOptionsView = GameOptionsView()
    gameOptionsStackView.addArrangedSubview(gameOptionsView)

    gameOptionsView.set(name: mode.name, image: mode.image)
}

结论

循环是帮助你更好地掌握Swift的基本知识。在这篇文章中,我们通过不同的例子和用例对for-in 循环进行了概述。

for-in循环在Swift中的应用教程》一文首次出现在LogRocket博客上。