一、什么是闭包?

  • 在 Swift 中,可以通过 func 定义一个函数,也可以通过闭包表达式定义一个函数,闭包是一个捕获了上下文的常量或者是变量的函数。闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。
  • Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的 匿名函数比较相似,全局函数和嵌套函数其实就是特殊的闭包。
  • 闭包的形式有:
全局函数 嵌套函数 闭包表达式
有名字但不能捕获任何值 有名字,也能捕获封闭函数内的值 无名闭包,使用轻量级语法,可以根据上下文环境捕获值
  • Swift 中的闭包有很多优化的地方:
    • 根据上下文推断参数和返回值类型;
    • 从单行表达式闭包中隐式返回(也就是闭包体只有一行代码,可以省略 return);
    • 可以使用简化参数名,如 $0, $1(从 0 开始,表示第i个参数…);
    • 提供了尾随闭包语法(Trailing closure syntax)。
  • 实现一个加法功能的闭包:
func sum(_ a: Int, _ b: Int) -> Int { a + b }
var add = {(a: Int, b: Int) -> Int inreturn a + b}
add(10, 20)
  • 定义了一个接收参数并返回指定类型的闭包语法:
{(parameters) -> return type instatements
}
  • OC 中的 Block 其实是一个匿名函数,所以这个表达式要具备:
    • 作用域(也就是大括号)
    • 参数和返回值
    • 函数体(in)之后的代码
  • Swift 中的闭包即可以当做变量,也可以当做参数传递:
var closure : (Int) -> Int = { (a: Int) in return a }// 通过 let 关键字将闭包声明位一个常量(也就意味着一旦赋值之后就不能改变)
let closure: (Int) -> Int// 当作函数的参数
func test(param : () -> Int){ print(param()) }
var a = 10
test { () -> Int ina += 1return a
}

二、常用的闭包类型

① 尾随闭包

  • 尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。如果把闭包表达式作为函数的最后一个参数,当前的闭包表达式很长,可以通过尾随闭包的书写方式来提高代码的可读性。
func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) -> Bool) -> Bool{return  by(a, b, c)
}test(10, 20, 30) { item1, item2, item3 inreturn item1 + item2 > item3
}
  • 使用闭包表达式能更简洁的传达信息。当然闭包表达式的好处有很多:
    • 利用上下文推断参数和返回值类型;
    • 单表达式可以隐式返回,既省略 return 关键字;
    • 参数名称的简写(比如 $0);
    • 尾随闭包表达式。
  • 如下所示,写法都是等效的,但是不建议使用最后这种太简写的,不太好理解:
var array = [1, 2, 3]array.sort(by: {(item1 : Int, item2: Int) -> Bool in return item1 < item2 })array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })array.sort(by: {(item1, item2) in return item1 < item2 })array.sort{(item1, item2) in item1 < item2 }array.sort{ return $0 < $1 }array.sort{ $0 < $1 }array.sort(by: <)

② 捕获值

  • 闭包可以在其定义的上下文中捕获常量或变量。即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
  • Swift 最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
  • 在 OC 中,我们捕获值都知道需要在局部变量前加上 __block 修饰符,比如定义一个 Block,修改局部变量的值:
- (void)testBlock {NSInteger a = 1;void(^block)(void) = ^{NSLog(@"block=%ld", a);};a += 1;NSLog(@"before block=%ld", a);block();NSLog(@"after block=%ld", a);
}
  • 运行结果发现 a 的值在 block 里面输出并没有发生改变:
before block=2
block=1
after block=2
  • 改成通过__block 修饰 a:
- (void)testBlock {__block NSInteger a = 1;void(^block)(void) = ^{NSLog(@"block=%ld", a);};a += 1;NSLog(@"before block=%ld", a);block();NSLog(@"after block=%ld", a);
}
  • 运行结果发现 a 的值在 block 里面输出发生改变:
before block=2
block=2
after block=2
  • 在 Swift 中捕获局部变量,首先运行下面的程序:
var a = 10
let closure = {print("closure = ", a)
}a = 20
print("before", a)
closure()
print("after", a)
  • 运行结果:
before 20
closure = 20
after 20
  • 说明 Swift 值的捕获是在执行的时候再捕获,当代码执行到 closure(),对变量 a 进行捕获,捕获到的变量是修改之后的值。假如想实现捕获发生在定义 closure 内部:
var a = 10
let closure = {[a] inprint("closure = ", a)
}a = 20
print("before", a)
closure()
print("after", a)
  • 运行结果:
before 20
closure = 10
after 20
  • 也就是使用 [],实现捕获列表 capturing list,就实现了捕获发生在 closure 内部,这是因为这个时候它已经不是捕获的引用了,而是最初原始值的 copy 副本。
  • OC Block 和 Swift 闭包相互调用:
    • Swift 调用 OC Block,需要在桥接文件.header里面引入 OC 类,然后就可以在 Swift 中直接调用;
    • OC 调用 Swift 闭包,需要在闭包的类中使当前类继承于 NSObject, 并且闭包表达式使用 @objc 修饰,这样就可以调用。

③ 逃逸闭包

  • 当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调用,就可以说这个闭包逃逸。当声明一个接受闭包作为形式参数的函数时,可以在形式参数前写 @escaping 来明确闭包是允许逃逸的。不需要在函数结束前被调用,可以等到特定时机时才被调用。
  • 如下所示,当闭包参数传给属性使用时:

  • 这样就会报错,需要声明称逃逸闭包,添加 @escaping 修饰:
class Closure{var handle: ((Int) -> Void)?func test(_ a: Int, handler: @escaping (Int) -> Void) {self.handle = handler}
}
  • 在原函数的内部异步执行,也必须使用逃逸闭包:
class Closure{var handle: ((Int) -> Void)?func test(_ a: Int, handler: @escaping (Int) -> Void) {var b = 10DispatchQueue.main.async {handler(b)}}
}
  • 逃逸闭包生命周期常于函数,函数退出的时候,逃逸闭包的引用仍被其他对象持有,不会在函数结束时释放。如果在闭包中使用了当前对象,这样就会导致循环引用发生内存泄露。

④ 非逃逸闭包

  • 只能在函数作用域内函数执行结束前被调用:
func testNoEscaping(_ f: () -> Void) {f()
}func test() -> Int {var age = 18testNoEscaping {age += 20}return 30
}test()
  • 这个就是一个非逃逸闭包,当函数调用完之后这个闭包也就消失了。并且非逃逸闭包还有以下优点:
    • 不会产生循环引用,函数作用域内释放;
    • 编译器更多性能优化 (retain, release) ;
    • 上下文的内存保存再栈上,不是堆上。

⑤ 自动闭包

  • 自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。
  • 自动闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。
  • 自动闭包的便利语法能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
  • 自动闭包能够延迟求值,因为直到调用这个闭包,代码段才会被执行。
  • 延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。
var dataArr = ["a", "b", "c", "d", "e"]
print(dataArr.count)    // 打印出“5”let removeStr = { dataArr.remove(at: 0) }
print(dataArr.count)    // 打印出“5”print("debug \(removeStr())!") // Prints "debug a!"
print(dataArr.count)    // 打印出“4”

⑥ 复制代码

  • 在闭包的代码中,dataArr 的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。
  • 将闭包作为参数传递给函数时,获得同样的延时求值行为:
// serve(element:) 函数接受一个返回元素的显式的闭包。
func serve(element elementProvider: () -> String) {print("debug \(elementProvider())!")
}serve(element: { dataArr.remove(at: 0) } )
// 打印出“debug Alex!”
  • 通过将参数标记为 @autoclosure 来接收一个自动闭包,可以将该函数当作接受 String 类型参数(而非闭包)的函数来调用。elementProvider 参数将自动转化为一个闭包,因为该参数被标记了 @autoclosure 特性。
func serve(element elementProvider: @autoclosure () -> String) {print("debug \(elementProvider())!")
}
serve(element: dataArr.remove(at: 0))
  • 可以看出用 @autoclosure 修饰后,这个闭包参数可以传入字符串或者闭包:
serve(element: { dataArr.remove(at: 0) } )
serve(element: dataArr.remove(at: 0))

三、defer 关键字用法

  • defer block 里的代码会在函数 return 之前执行,无论函数是从哪个分支 return 的,还是有 throw,还是自然而然走到最后一行。如果多个 defer 语句出现在同一作用域中,它们执行的顺序和添加的顺序是相反的。

① try catch 结构

func foo() {defer {print("finally")}
do {throw NSError()
print("impossible")} catch {print("handle error")}
}
  • 不管 do block 是否 throw error,有没有 catch 到,还是 throw 出去了,都会保证在整个函数 return 前执行 defer。在这个例子里,就是先 print 出 “handle error” 再 print 出 “finally”。

② 清理工作、回收资源

  • 关闭文件:
func foo() {let fileDescriptor = open(url.path, O_EVTONLY)
defer {close(fileDescriptor)}
// use fileDescriptor...
}
  • 这样就不怕哪个分支忘了写,或者中间 throw 个 error,导致 fileDescriptor 没法正常关闭。
  • 加/解锁:
func foo() {objc_sync_enter(lock)
defer { objc_sync_exit(lock)}
// do something...
}
  • 像这种成对调用的方法,可以用 defer 把它们放在一起,这样结构就特别清晰。

③ 调 completion block

  • 有时候一个函数分支比较多,可能某个小分支 return 之前就忘了调 completion block,结果导致出一个不易发现的 bug,这样写就很好的避免了这个问题:
func foo() {defer {self.completion = nil}
if (succeed) {self.completion(.success(result))} else {self.completion(.error(error))}
}

④ 调 super 方法

  • 可以在 super 方法之前做一些准备工作:
func override foo() {defer {super.foo()}
// some preparation before super.foo()...
}

四、闭包中的循环引用

  • 比如将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了实例,这样会发生引用循环。如下所示,Server 引用 Client ,而 Client 又引用 Server 导致的循环引用:
class Server {var clients : [Client] = []func add(client:Client){self.clients.append(client)}
}class Client {var server : Server!var server : Server!init (server : Server) {self.server = serverself.server.add(client:self)}
}
  • 要想解决这个问题,也是像要打破这种循环,有两种解决办法:
    • 弱引用weak:一个变量可以选择不持有对其引用对象的拥有权,弱引用可以是空(nil);
    • 无主引用unowned:像弱引用,无主引用对引用对象不保持很强的关系。和弱引用不同的是,无主引用总是被设定为一个值。因此,无主引用总是被设定为不可选择的类型。无主引用不可以是空。
class Client {// 或者换成unownedweak var server : Server!init (server : Server) {self.server = serverself.server.add(client:self)}
}

Swift之常见闭包与defer关键字的使用分析和闭包中的循环引用 | CSDN创作打卡相关推荐

  1. 解决Swift中callback循环引用 - Delegated(Library)

    文章目录 前言 使用示例 一般闭包示例 Delegated 示例 源码解析 示例Demo 示例Demo地址 前言 在Swift中我们使用闭包(Callback)的时候经常要写 [weak self] ...

  2. Swift之函数的语法和使用 | CSDN创作打卡

    一.函数简介 Swift 函数用来完成特定任务的独立的代码块. Swift 使用一个统一的语法来表示简单的 C 语言风格的函数到复杂的 Objective-C 语言风格的方法: 函数声明:告诉编译器函 ...

  3. Swift之使用key paths创建自定义查询函数 | CSDN创作打卡

    一.前言 作为一个相当严格,静态编译的语言,Swift 可能不会在语法自定义方面提供许多渠道,但这实际上确正好相反.通过如何在 Swift 中自定义操作符,Swift 中 key paths 的能力, ...

  4. OC和Swift中循环引用的问题

    总结一下自己对OC中和Swift中循环引用的认识. 一.OC中的循环引用分为类中的循环引用和对象之间的循环引用. 1.类的循环引用 造成原因: 在两个类的.h文件中使用了#import "& ...

  5. Swift - defer关键字(推迟执行)

    在一些语言中,有try/finally这样的控制语句,比如Java. 这种语句可以让我们在finally代码块中执行必须要执行的代码,不管之前怎样的兴风作浪. 在Swift 2.0中,Apple提供了 ...

  6. golang学习之四:闭包、defer

    golang闭包.defer 闭包 闭包以引用的方式捕捉外部变量 闭包变量与作用域 传统函数局部变量 闭包的变量 defer 延迟:在函数执行完毕之前调用 多个defer执行顺序 闭包 闭包以引用的方 ...

  7. Swift 学习总结三:数组,字典,枚举,闭包, 函数

    数组 ///创建数组//创建某一类型的含有n个初始元素的数组var array1 = [Int](repeating: 5, count: 3)//创建某一类型空数组var array2 = [Int ...

  8. Go语言defer关键字

    Go语言的defer关键字用于延迟调用,下面是关于Go语言defer关键字的一些基础概念: 1. defer关键字用于注册延迟调用: 2. 这些调用直到包含当前该defer关键字的函数执行完了才会被执 ...

  9. Go 语言编程 — defer 关键字

    目录 文章目录 目录 defer 关键字 defer 的用途之一:释放资源 defer 的用途之二:执行 recover(恢复) defer 特性 多个 defer 的执行顺序 被 deferred ...

最新文章

  1. 《云计算揭秘企业实施云计算的核心问题》——导读
  2. 从零写一个编译器(四):语法分析之构造有限状态自动机
  3. vba判断文件是否存在的两种方法
  4. jQuery-zTree插件使用
  5. 2011年计算机控制期末,2011计算机控制技术期末复习题详解
  6. CANape操作指南
  7. win10 SecoClient连接“提示用户与对方建立连接超时,配置错误或网络故障”
  8. ctf misc 图片题知识点
  9. 公众号实现一键添加联系人到手机通讯录
  10. 被称为偏执的企业家,他成功跻身中国民企500强
  11. Kibana启动常见报错信息的解决方案
  12. 【完美解决】爬虫伪装代理IP方案
  13. Redis设计与实现-读书笔记
  14. Java8 Zip 压缩与解压缩
  15. UnityShader17.1:ESM 阴影技术
  16. SpringBoot 微信点餐开源系统,值得一看
  17. SOLIDWORKS有哪些好用的插件丨慧德敏学
  18. 潮汐观测数据调和分析及预报成图
  19. 配置Kafka发送大消息
  20. Mysql数据库宾馆管理系统_Maven+JSP+Servlet+JDBC+Mysql实现的dbExper宾馆管理系统

热门文章

  1. 关于电子科技大学清水河校区流浪猫狗的调研报告.
  2. macm1安装tensorflow以及pycharm配置
  3. 2023年一次性洗漱用品套装市场调查报告
  4. 整理的Qt相关博客及开源项目的链接(2022-09-13)
  5. Rust 优劣势: v.s. C++ / v.s. Go(持续更新)
  6. 送书|如果可以,我想给这本书打十星!
  7. 消灭星星(PopStar)游戏的自动处理
  8. 程序员,请对自己好一点!
  9. python爬取美女照片
  10. IT精英,必须掌握的命令