swift中的闭包

本文深入探讨了Swift中的闭包,包括闭包的概念、应用场景、主要特点、格式、逃逸闭包与非逃逸闭包的差异、自动闭包、尾随闭包以及捕获列表的作用和使用,帮助读者理解并掌握Swift闭包的全面知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

闭包概念

Swift 中的闭包是一个在上下文中闭合的独立代码块,可以将 Swift 闭包看作是一个轻量级的函数实例,它可以捕获上下文中的变量和常量,并作为参数传递给其他函数。

闭包的使用场景

在实际应用中,闭包主要用于以下场景:

  • 异步操作和回调:如网络请求和延迟执行等。
  • 高阶函数map, filter, reduce 等函数会接收闭包作为操作的参数。
  • 事件处理:与用户交互、通知等事件处理相关的回调函数。
  • 动画和视图转换:UIKit 和其它 UI 框架中的动画和视图转换操作。
  • 资源管理和枚举:例如文件操作、GCD(Grand Central Dispatch)队列和 autoreleasepool

一、闭包的主要特点:

  • 1.引用类型:闭包是引用类型,像类一样,它们在分配和传递时不复制,而是传递引用。

  • 2.捕获上下文值:闭包可以捕获和存储定义它的上下文中的常量和变量,即使它们在闭包创建之后被修改或消失,闭包仍然可以访问它们。

  • 3.可被传递给函数:闭包可以通过参数的形式传递给函数,或者作为变量存储在其他闭包中以供后续调用。

二、闭包的格式

闭包格式(in是把闭包的参数或者返回值和 代码逻辑 隔开)

1.无参数无返回值
格式: let 闭包名 = { 代码逻辑 }
2.有参数无返回值

格式1: let 闭包名 = {(参数名:参数类型,...) in 代码逻辑 }

格式2: let 闭包名 = {(参数名:参数类型,...) -> Void  in 代码逻辑 }

格式3: let 闭包名 = {(参数名:参数类型,...) -> ()  in 代码逻辑 }

3.有参数有返回值

格式: let 闭包名 = {(参数名:参数类型,...) -> 返回值类型  in 代码逻辑  return 返回值}

三、逃逸闭包(@escaping )与非逃逸闭包(@noescaping)

1.逃逸闭包(Escaping Closures)

概念:逃逸闭包(escaping closure)是 Swift 中闭包的特殊类型,它指的是传递给函数的闭包会在函数返回后执行。

常见的逃逸闭包使用场景有:

  • 异步操作,如网络请求、延时执行等。
  • 存储 closure,在稍后执行。
  • 将闭包传递给其他函数(闭包传递到另一个需要逃逸闭包的函数)。

    // 模拟加载数据,在这个示例中,我们使用 @escaping 标注来声明闭包 loadData执行完后的闭包函数是一个逃逸闭包。
    func loadData(callBack: @escaping (String) -> ()){
        // 子线程异步加载数据
        DispatchQueue.global().async {
            // 模拟消耗时间
            Thread.sleep(forTimeInterval: 2.0)
            // 模拟请求回来的数据, 要传递给viewDidLoad中使用
            let data = "不想睡"
            // 回到主线程, 刷新页面
            DispatchQueue.main.async {
                // 2. 在得到数据的时候,执行闭包
                callBack(data)
            }
        }
    }
//调用
loadData(callBack: closure)

逃逸闭包可能导致循环引用(retain cycle)问题。当闭包在函数之外执行,尤其是在闭包和类实例之间产生相互引用时,需要特别关注内存管理。这时,应使用捕获列表(capture list),指定捕获方式为 weakunowned

在swift5.0之后,只 有 闭 包 在 其 他 线 程 执 行 时 才 需 要 添 加 该 关 键 字。

2.非逃逸闭包(@noescaping)

概念:一个接受闭包作为参数的函数, 闭包是在这个函数结束前内被调用。

class ViewController: UIViewController {
​
    override func viewDidLoad() {
        super.viewDidLoad()
        
        handleData { (data) in
            print("闭包结果返回--\(data)--\(Thread.current)")
        }
    }
    
    func handleData(closure:(Any) -> Void) {
        print("函数开始执行--\(Thread.current)")
        print("执行了闭包---\(Thread.current)")
        closure("4456")
        print("函数执行结束---\(Thread.current)")
    }
​
}

执行结果为:

函数开始执行--<_NSMainThread: 0x600002808080>{number = 1, name = main}
执行了闭包---<_NSMainThread: 0x600002808080>{number = 1, name = main}
闭包结果返回--4456--<_NSMainThread: 0x600002808080>{number = 1, name = main}
函数执行结束---<_NSMainThread: 0x600002808080>{number = 1, name = main}
3.为什么要分逃逸闭包和非逃逸闭包

非逃逸闭包不会产生循环引用,而闭包会强引用它捕获的所有对象,比如你在闭包中访问了当前控制器的属性、函数,编译器会要求你在闭包中显示 self 的引用,这样闭包会持有当前对象,容易导致循环引用。

四、自动闭包(@autoclosure

概念:

自动闭包(autoclosure)是 Swift 中一种特殊的闭包类型,它可以自动将表达式封装在一个没有参数的闭包中。当函数需要延迟求值或执行特定表达式时,可以用自动闭包将表达式传递给函数。这种类型的闭包在调用时不需要使用括号。

 func zidongPrint(_ condition: @autoclosure () -> Bool, message: String) {
        if !condition() {
            print("Assert Failed: \(message)")
        } else {
            print("Assert Passed")
        }
 }
 let x = 10
 let y = 5

 zidongPrint(x > y, message: "x should be greater than y")

因为 condition 参数带有 @autoclosure 标记,所以当我们调用这个函数时,只需传递一个表达式,而不需要显式地创建一个闭包。

在这个示例中,我们传递了表达式 x > y,而不需要将其封装为一个闭包(如 { x > y })。@autoclosure 关键字会自动将表达式封装在一个没有参数的闭包中,在调用 condition() 时执行。

需要注意的是,由于自动闭包的延迟求值特性,可能导致一些不符合预期的情况。例如,在多线程环境中,自动闭包捕获的值在执行时可能已经发生了变化。因此,在使用自动闭包时,请注意程序的逻辑和执行顺序。

五、尾随闭包

尾随闭包就是系统的简写方式,了解即可. 以下两种情况为尾随闭包.

条件1: 定义的函数有且只有一个参数, 并且参数是闭包,函数调用时,( )可以省略。

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        // 1.0 正常写法
        let closure = { (result: String) in
            
        }
        loadData(callback: closure)
        // 1.1 可以演变成
        loadData(callback:  { (result: String) in
            
        })
        
        // 2.0 尾随闭包写法: 直接写loadData方法, 按回车,自动联想, 原理是->()和里面的参数名称可以省略, 参数类型也可以省略
        loadData { (result) in
            
        }
    }
    
    func loadData(callback: (String)->()){
        
    }

条件2: 定义的函数有多个参数, 但是最后一个参数为闭包 , 函数的( )提前关闭.

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        // 1.0 正常写法
        let closure = { (result: String) in
            
        }
        loadData(a: 3, b: "8", callback: closure)
        // 1.1 可以演变成
        loadData(a: 3, b: "8", callback: { (result: String) in
            
        })
        
        // 2.0 尾随闭包写法: 直接写loadData方法, 按回车,自动联想, 原理是->()里面的参数名称可以省略, ()提前闭合, 只留下闭包了,闭包的参数类型也可以省略
        loadData(a: 3, b: "8") { (result) in
            
        }
    }
    
    func loadData(a: Int, b: String, callback: (String)->()){
        
    }

六、捕获列表(Capture List)

在 Swift 闭包中,捕获列表(Capture list)用于控制闭包体内的变量和常量的捕获行为。当一个闭包捕获一个实例或变量时,闭包存储了对该实例或变量的引用。捕获列表可以让你明确地指定闭包如何捕获外部的值,以避免潜在的循环引用或内存泄漏问题。

捕获列表的语法

捕获列表位于闭包参数列表和返回类型之前,用 [] 括起来,并用逗号分隔。

[weak object1, unowned object2, otherValue = someValue] (parameters) -> ReturnType in
    // 闭包体
捕获列表的种类

1.weak:将捕获的引用设为弱引用,允许引用的实例在闭包执行时被释放。当实例被释放时,弱引用变为 nil。这有助于防止引起循环引用。

2.unowned:将捕获的引用设为无主引用,表示引用的实例不会在闭包执行期间被释放。在闭包执行时,无主引用始终有值,如果你试图访问已经被释放的无主引用,会导致运行时错误。

3.按值捕获: 可以用赋值表达式捕获变量的当前值,作为常量存储在闭包中。这样,闭包总是使用捕获时的值,而不是捕获变量最新的值。

class Person {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("Person \(name) is being deinitialized.")
    }
}

class Task {
    var taskDescription: String
    
    lazy var completion: () -> Void = { [weak self] in
        print("Task '\(self?.taskDescription ?? "Unknown")' is completed.")
    }
    
    init(taskDescription: String) {
        self.taskDescription = taskDescription
    }
    
    deinit {
        print("Task '\(taskDescription)' is being deinitialized.")
    }
}

var person: Person? = Person(name: "John Doe")

在上面的例子中,我们使用 [weak self] 捕获列表,使得 Task 类中的 completion 闭包对 self 的引用变成弱引用,以避免循环引用。这样,当 Task 实例被释放时,闭包中的 self(即 Task 实例)将自动变为 nil
捕获列表提供了一种显式地定义闭包捕获行为的方式,有助于防止内存泄漏和解决循环引用问题。

无主引用有什么问题

运行时错误:当试图访问一个已经被释放的无主引用时,程序会触发运行时错误。由于无主引用不会使引用计数增加,你需要确保其引用的实例在需要访问它的整个生命周期内仍然存在。如果实例被意外释放,使用无主引用会导致程序崩溃。

要依据具体情况决定是否使用无主引用。如果你知道其引用的实例在整个生命周期内一定存在,可以使用无主引用。否则,考虑将引用设为弱引用(weak),允许它在不再需要时被释放并自动变为 nil,从而避免运行时错误。在使用无主引用时,确保谨慎评估潜在的运行时风险,以防止崩溃和不稳定的应用程序行为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值