Day9 挑战了 Swift 中最让人头疼但又极其重要的概念 ——闭包(Closure)。简单说,闭包就是 “可以传递和赋值的匿名函数”,它在 Swift 中无处不在,尤其是在 SwiftUI 里(按钮点击、列表渲染、动画处理都离不开)。今天从基础的 “函数赋值” 到复杂的 “尾随闭包语法”,一步步揭开闭包的神秘面纱,整理了实操代码、简化技巧和应用场景,新手朋友跟着练能慢慢建立 “闭包思维”~

目录

一、闭包基础:从 “函数可以赋值给变量” 说起

1. 函数可以赋值给变量

2. 匿名函数:直接定义 “没有名字的函数”

二、带参数和返回值的闭包:完整语法

三、函数类型:函数和闭包的 “数据类型”

1. 函数类型的表示方法

2. 将函数 / 闭包作为参数传递

四、闭包的简化:从完整语法到极简写法

1. 完整闭包语法(作为参数传入)

2. 简化 1:省略参数类型和返回类型(Swift 可自动推断)

3. 简化 2:尾随闭包语法(闭包作为最后一个参数时)

4. 简化 3:用 $0、$1 代替参数名

5. 极简写法(单句逻辑)

五、闭包的实战应用:filter() 和 map()

1. filter():筛选符合条件的元素

2. map():转换数组中的每个元素

六、自定义接收闭包的函数:让函数更灵活

示例:生成指定大小的数组,元素由闭包生成

多闭包参数:用外部名区分

七、Day9 总结:闭包的核心价值与使用技巧

明日计划


一、闭包基础:从 “函数可以赋值给变量” 说起

闭包的核心是 “代码块可以像数据一样传递”,先从最简单的 “函数赋值” 入手,理解 “函数也是一种数据类型”。

1. 函数可以赋值给变量
// 定义一个普通函数
func hiUser() {
    print("Hello Here!")
}

// 调用函数(常规方式)
hiUser()  // 输出 Hello Here!

// 将函数赋值给变量(注意:函数名后不加括号,否则是调用函数)
let sayHi = hiUser  // 这里是“赋值函数本身”,不是调用

// 通过变量调用函数
sayHi()  // 输出 Hello Here!
  • 关键区别hiUser 是函数本身,hiUser() 是调用函数并获取返回值。赋值时用前者,调用时用后者。

2. 匿名函数:直接定义 “没有名字的函数”

如果函数只需要用一次,没必要单独定义,可直接用 “闭包表达式” 创建匿名函数:

// 定义匿名函数(闭包)并赋值给变量
let sayHello = {
    print("Hello, Here!")
}

// 调用闭包
sayHello()  // 输出 Hello, Here!
  • 特点:没有函数名,用 {} 包裹代码块,本质是 “可以被调用的代码片段”。

二、带参数和返回值的闭包:完整语法

闭包可以像函数一样接收参数、返回值,只是语法略有不同 —— 参数和返回值定义在 {} 内部,用 in 分隔 “参数 / 返回值定义” 和 “代码体”。

// 定义一个接收String参数、返回String的闭包
let greet = { (name: String) -> String in
    return "Hi \(name)!"  // 闭包体:使用参数,返回结果
}

// 调用闭包(和调用函数一样传参)
let message = greet("SunJiaxing")
print(message)  // 输出 Hi SunJiaxing!
  • 语法拆解
    • (name: String) -> String:闭包的参数(一个 String 类型的 name)和返回值(String);
    • in:标记参数 / 返回值定义结束,后面是闭包的执行代码;
    • 闭包体内部可以像函数一样使用参数,用 return 返回结果(单句代码可省略 return)。

三、函数类型:函数和闭包的 “数据类型”

函数和闭包都有 “类型”,由 “参数类型” 和 “返回值类型” 决定。理解函数类型是 “将函数作为参数传递” 的基础。

1. 函数类型的表示方法
// 示例函数:接收Int参数,返回String
func getUserData(for id: Int) -> String {
    id == 1989 ? "Sun Jiaxing" : "XiaoTuo"
}

// 函数类型:(Int) -> String(接收Int,返回String)
// 将函数赋值给变量(变量类型需匹配函数类型)
let userData: (Int) -> String = getUserData

// 调用变量(和调用原函数效果一致,但无需外部参数名)
let user = userData(1989)  // 注意:这里不用写 for: 1989
print(user)  // 输出 Sun Jiaxing
  • 函数类型规则:只看 “参数类型” 和 “返回值类型”,不包含参数名(包括外部参数名)。例如 (Int) -> String 表示 “接收一个 Int,返回一个 String” 的函数 / 闭包。

2. 将函数 / 闭包作为参数传递

很多系统函数(如数组的 sorted())允许传入一个 “函数 / 闭包” 作为参数,用于自定义逻辑。以 sorted() 为例,它可以接收一个 “比较函数” 来定制排序规则。

// 定义一个字符串数组
let team = ["ZhangSan", "LiSi", "SunJiaxing", "ZhuLiu"]

// 需求:自定义排序,让 "SunJiaxing" 排在最前面,其他按字母顺序
func captainFirstSorted(name1: String, name2: String) -> Bool {
    // 如果name1是SunJiaxing,放前面
    if name1 == "SunJiaxing" {
        return true
    }
    // 如果name2是SunJiaxing,name1放后面
    else if name2 == "SunJiaxing" {
        return false
    }
    // 其他情况按字母顺序
    return name1 < name2
}

// 用自定义函数排序(sorted(by: 函数))
let sortedTeam = team.sorted(by: captainFirstSorted)
print(sortedTeam)  // 输出 [SunJiaxing, LiSi, ZhangSan, ZhuLiu]
  • 原理sorted(by:) 要求传入一个 “(String, String) -> Bool” 类型的函数 / 闭包,该函数返回 true 表示第一个参数应该排在前面。

四、闭包的简化:从完整语法到极简写法

闭包的语法可以一步步简化,尤其是在作为参数传递时,Swift 提供了多种简写方式,让代码更简洁。以 sorted(by:) 为例,逐步简化:

1. 完整闭包语法(作为参数传入)
let sortedTeam1 = team.sorted(by: { (name1: String, name2: String) -> Bool in
    if name1 == "SunJiaxing" { return true }
    else if name2 == "SunJiaxing" { return false }
    return name1 < name2
})
2. 简化 1:省略参数类型和返回类型(Swift 可自动推断)

因为 team 是 [String] 类型,sorted(by:) 知道闭包的参数必然是两个 String,返回值必然是 Bool,所以可以省略类型:

let sortedTeam2 = team.sorted(by: { name1, name2 in
    if name1 == "SunJiaxing" { return true }
    else if name2 == "SunJiaxing" { return false }
    return name1 < name2
})
3. 简化 2:尾随闭包语法(闭包作为最后一个参数时)

如果闭包是函数的最后一个参数,可以把闭包写在函数括号外面,甚至省略参数名 by:

let sortedTeam3 = team.sorted { name1, name2 in
    if name1 == "SunJiaxing" { return true }
    else if name2 == "SunJiaxing" { return false }
    return name1 < name2
}
  • 优势:当闭包代码较长时,尾随闭包能让代码结构更清晰(这是 SwiftUI 中最常用的写法)。
4. 简化 3:用 $0$1 代替参数名

Swift 会自动为闭包的参数提供简写名称:$0 是第一个参数,$1 是第二个参数,以此类推,可省略 in

let sortedTeam4 = team.sorted {
    if $0 == "SunJiaxing" { return true }  // $0 代替 name1
    else if $1 == "SunJiaxing" { return false }  // $1 代替 name2
    return $0 < $1
}
5. 极简写法(单句逻辑)

如果闭包体只有一句代码,可省略 return

// 示例:倒序排列数组
let reversedTeam = team.sorted { $0 > $1 }  // $0 > $1 表示“前一个比后一个大则交换”
print(reversedTeam)  // 输出 [ZhuLiu, ZhangSan, SunJiaxing, LiSi]

五、闭包的实战应用:filter() 和 map()

闭包的真正威力体现在数组的高阶函数中,比如 filter()(过滤元素)和 map()(转换元素),用闭包能一行代码实现复杂逻辑。

1. filter():筛选符合条件的元素
// 需求:筛选出所有以 "Z" 开头的名字
let zNames = team.filter { $0.hasPrefix("Z") }
print(zNames)  // 输出 [ZhangSan, ZhuLiu]
// 原理:filter 传入一个 (String) -> Bool 的闭包,返回 true 的元素会被保留
2. map():转换数组中的每个元素
// 需求:将所有名字转为大写
let upperTeam = team.map { $0.uppercased() }
print(upperTeam)  // 输出 [ZHANGSAN, LISI, SUNJIAXING, ZHULIU]
// 原理:map 传入一个 (String) -> String 的闭包,每个元素会被转换后存入新数组
  • 扩展map() 可以转换类型,例如将 [Int] 转为 [String]
    let numbers = [1, 2, 3]
    let numberStrings = numbers.map { "第\($0)个" }
    print(numberStrings)  // 输出 ["第1个", "第2个", "第3个"]
    

六、自定义接收闭包的函数:让函数更灵活

我们可以定义 “接收闭包作为参数” 的函数,让函数的逻辑能被动态定制(这是框架设计的核心思想)。

示例:生成指定大小的数组,元素由闭包生成
// 定义函数:接收数组大小和生成元素的闭包,返回数组
func makeArray(size: Int, using generator: () -> Int) -> [Int] {
    var numbers = [Int]()
    for _ in 0..<size {
        let newNum = generator()  // 调用闭包生成元素
        numbers.append(newNum)
    }
    return numbers
}

// 用法1:用尾随闭包传入生成逻辑(生成1-20的随机数)
let randomNumbers = makeArray(size: 5) {
    Int.random(in: 1...20)
}
print(randomNumbers)  // 输出类似 [5, 12, 3, 19, 8]

// 用法2:传入一个预先定义的函数
func generateNumber() -> Int {
    Int.random(in: 1...20)
}
let moreNumbers = makeArray(size: 5, using: generateNumber)
  • 函数类型分析makeArray 的第二个参数 generator 是 () -> Int 类型的闭包,即 “无参数,返回 Int”。
多闭包参数:用外部名区分

如果函数接收多个闭包参数,可用外部名区分,在调用时用 “参数名:闭包” 的形式:

// 定义接收3个闭包的函数
func doWork(first: () -> Void, second: () -> Void, third: () -> Void) {
    print("开始第一个任务")
    first()
    print("开始第二个任务")
    second()
    print("开始第三个任务")
    third()
}

// 调用:第一个闭包用尾随语法,后面的用“参数名: 闭包”
doWork {
    print("执行第一个任务")
} second: {
    print("执行第二个任务")
} third: {
    print("执行第三个任务")
}
  • SwiftUI 中的常见场景:很多 SwiftUI 组件(如 ListNavigationStack)会接收多个闭包,分别用于内容、标题、样式等,就是这种语法。

七、Day9 总结:闭包的核心价值与使用技巧

概念 / 技巧 核心作用 适用场景
闭包基础 匿名函数,可赋值给变量或作为参数传递 临时使用的简单逻辑(如排序、过滤)
函数类型 描述函数 / 闭包的参数和返回值类型 定义接收函数参数的函数时(如 makeArray
尾随闭包 闭包作为最后一个参数时,可写在括号外 SwiftUI 组件(按钮、列表等)的回调逻辑
$0/$1 简写 简化闭包参数名,减少代码量 短逻辑闭包(如 sorted { $0 > $1 }
filter/map 用闭包实现数组筛选和转换 数据处理(筛选符合条件的元素、格式转换)

学习建议:闭包初期理解起来确实抽象,建议结合实际场景多练 —— 比如用 sorted() 定制不同排序规则,用 filter() 筛选数组,慢慢就能体会到 “代码即数据” 的灵活性。

明日计划

Day10 要学 Swift 的 “结构体(Struct)”—— 这是封装数据和方法的核心方式,和闭结合在一起,能构建更复杂的逻辑(比如 SwiftUI 中的视图都是结构体)。有一起打卡的朋友吗?评论区可以分享你用闭包实现的小功能(比如自定义数组排序、过滤出数组中的偶数)~

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐