本节知识点
- 闭包的基本概念
- 闭包基本使用
- 闭包表达式作为回调函数
- 闭包的多种写法(尾随闭包)
- 闭包表达式优化
- 闭包捕获值
- 闭包的循环引用(重点)
1. 闭包的基本概念
-
闭包
是一种类似于OC语言的block
匿名函数
-
block
和闭包
都经常用于回调
-
闭包
表达式(匿名函数
) 能够捕获上下文中的值
{
(参数) -> 返回值类型 in
执行语句
}
- 闭包表达式的类型和函数的类型一样, 是参数加上返回值, 也就是
in
之前的部分
- 语法:
in
关键字的目的是便于区分返回值和执行语句
2. 闭包基本使用
let say0:(String) ->Void = {
(name: String) in
print("hi \(name)")
}
say0("cdh")
//输出结果: hi cdh
let say1:() ->Void = {
() -> () in // 当没有参数也没有返回值是这行都可以省略
print("hi cdh")
}
say1()
//输出结果: hi cdh
略; 按照格式, 结合函数的写法即可
略
3. 闭包表达式作为回调函数
3.1 block
的用法回顾
@interface HttpTool : NSObject
- (void)loadRequest:(void (^)())callBackBlock;
@end
@implementation HttpTool
- (void)loadRequest:(void (^)())callBackBlock
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"加载网络数据:%@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
callBackBlock();
});
});
}
@end
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.httpTool loadRequest:^{
NSLog(@"主线程中,将数据回调.%@", [NSThread currentThread]);
}];
}
//block的写法:
//类型定义:
返回值(^block的名称)(block的参数)
//等号右边的值:
^(参数列表) {
// 执行的代码
};
3.2 使用闭包代替block
class HttpTool: NSObject {
func loadRequest(callBack : ()->()){
dispatch_async(dispatch_get_global_queue(0, 0)) { () -> Void in
print("加载数据", [NSThread.currentThread()])
dispatch_async(dispatch_get_main_queue(), { () -> Void in
callBack()
})
}
}
}
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
// 网络请求
httpTool.loadRequest ({ () -> () in
print("回到主线程", NSThread.currentThread());
})
}
//闭包的写法:
// 类型:(形参列表)->(返回值)
// 技巧:初学者定义闭包类型,直接写()->().再填充参数和返回值
//值:
{
(形参) -> 返回值类型 in
// 执行代码
}
3.3 闭包的简写
- 如果闭包没有参数,没有返回值.in和in之前的内容可以省略
httpTool.loadRequest({
print("回到主线程", NSThread.currentThread());
})
4. 闭包的多种写法(尾随闭包)
- 如果闭包是函数的最后一个参数,则可以将闭包写在
()
后面
- 如果函数只有一个参数,并且这个参数是闭包,那么
()
可以不写
- 这样可以提高阅读性. 称之为尾随闭包
httpTool.loadRequest() {
print("回到主线程", NSThread.currentThread());
}
// 开发中建议该写法
httpTool.loadRequest {
print("回到主线程", NSThread.currentThread());
}
5. 闭包表达式优化
- 类型优化, 由于函数中已经声明了闭包参数的类型, 所以传入的实参可以不用写类型
- 返回值优化, 同理由于函数中已经声明了闭包的返回值类型, 所以传入的实参可以不用写类型
- 参数优化, swift可以使用$索引的方式来访问闭包的参数, 默认从0开始
// 比较大小排序
func bubbleSort(inout array:[Int], cmp: (Int, Int) -> Int) {
let count = array.count;
// i = 1; i < count; i++ 推荐写成 i in 1..< count
// 在Swift3.0 也可能就只有后者这种写法
for var i = 1; i < count; i++ {
for var j = 0; j < (count - i); j++ {
if cmp(array[j], array[j + 1]) == -1 {
let temp = array[j]
array[j] = array[j + 1]
array[j + 1] = temp
}
}
}
}
var arr:Array = [31, 13, 52, 84, 5]
print("排序前 \(arr)")
//输出结果: 排序前 [31, 13, 52, 84, 5]
bubbleSort(&arr){
// (a , b) -> Int in
// (a , b) in
if $0 > $1{
return 1;
}else if $0 < $1 {
return -1;
}else {
return 0;
}
}
print("排序后 \(arr)")
//输出结果: 排序后 [84, 52, 31, 13, 5]
let hehe = {
"我是cdh"
}
6. 闭包捕获值
- 被捕获的值会和与之对应的方法绑定在一起, 下一次需要用到这个方法这继续使用之前捕获到的值
- 同一个方法中的变量, 不会被绑定到不同的方法变量(或常量中)
func getIncFunc() -> (Int) -> Int {
var max = 10
func incFunc(x :Int) ->Int{
print("incFunc函数结束")
max++ // ++ 这种写法将在 swift3.0 移除, 推荐写成 += 1
return max + x
}
//当执行到这一句时incFunc 的参数 x 就应该被释放了
//但是由于在内部函数中使用到了它, 所以它被捕获了
//同理, 当执行完这一句时max变量就被释放了
//但是由于在内部函数中使用到了它, 所以它被捕获了
print("getIncFunc函数结束")
return incFunc
}
let incFunc = getIncFunc()
print("---------")
print(incFunc(5))
print("---------")
print(incFunc(5))
//输出结果:
//getIncFunc函数结束
//---------
//incFunc函数结束
//16
//---------
//incFunc函数结束
//17
let incFunc2 = getIncFunc()
print(incFunc2(5))
//输出结果:
//getIncFunc函数结束
//incFunc函数结束
//16
7. 闭包的循环引用(重点)
- 闭包形成的循环引用与 block 的循环引用是一样的原因导致. 如下闭包形成的循环引用的示例:
强引用 loadData函数的参数(闭包). png
loadData函数中参数(闭包)强引用 self(ViewController).png
// ViewController.swift
// 03-13-闭包的循环引用
//
// Created by chendehao on 16/3/2.
// Copyright © 2016年 CDH. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
var httpTool : HttpTools?
override func viewDidLoad() {
super.viewDidLoad()
httpTool = HttpTools()
}
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
httpTool?.loadData(){ (jsonData) in
print("在 viewController中拿到数据: \(jsonData)")
self.view.backgroundColor = UIColor.grayColor()
// self 强引用这类属性 httpTool, httpTool 中的函数 loadData 中的闭包参数在这里引用者 self (ViewController)
// 如果此次该闭包参数在 HttpTool 类中没有被强引用, 则此时将不形成循环引用,因为闭包是临时存储, 用完就被释放
// 也就只有self (ViewController) 强引用这类属性 httpTool, 而 HttpTool 并没有强引用 self
// 但是如果在 HttpTool 类中强引用了 loadData函数中的闭包这个参数, 则此时就会形成循环引用
}
}
// 该对象被释放都会调用这个函数
deinit{
print("ViewController -- deinit")
}
}
// HttpTools.swift
// 03-13-闭包的简单使用
//
// Created by chendehao on 16/3/2.
// Copyright © 2016年 CDH. All rights reserved.
//
import UIKit
class HttpTools: NSObject {
var finishedCallBack :((jsonData : String) -> ())?
// 闭包的类型写法: (参数列表) -> (返回值类型)
func loadData(finishedCallback : (jsonData : String) -> ()) {
// 此处对函数中的闭包参数通过属性强引用
self.finishedCallBack = finishedCallback
dispatch_async(dispatch_get_global_queue(0, 0)) { () -> Void in
print("正在发送网络请求: \(NSThread.currentThread())")
dispatch_sync(dispatch_get_main_queue(), {
() -> Void in
print("回调主线程, 见数据回传出去, \(NSThread.currentThread())")
finishedCallback(jsonData: "json数据")
})
}
}
}
- 通过以上两段代码已经形成了强引用, 这样一来就会形成内存泄漏问题
7.1 解决循环引用
- 未免出现以上的循环引用导致内存泄漏的问题, 有多种解决办法
- 方法一: 删除对 loadData 函数中的闭包参数做强引用即可
// 删除这个即可
// 此处对函数中的闭包参数通过属性强引用
self.finishedCallBack = finishedCallback
- 方法二(重点掌握): 将 self (ViewController) 使用弱引用关键之修饰
// 写法一: 定义一个 weak 修饰的新的临时变量
weak var weakSelf: ViewController? = self
httpTool?.loadData({ (jsonData) ->() in
weakSelf?.view.backgroundColor = UIColor.orangeColor()
print("在 viewController中拿到数据: \(jsonData)")
})
// 写法二: 直接使用 [weak self] 写在闭包的大括号中
// 尾随闭包 : 如果在调用方法时,该方法的最后一个参数是一个闭包.那么该闭包可以写成尾随闭包
// 尾随闭包写法一: (推荐将 [weak self]写在闭包的大括号中的写法)
httpTool?.loadData(){ [weak self] (jsonData) in
print("在 viewController中拿到数据: \(jsonData)")
self?.view.backgroundColor = UIColor.grayColor()
}
// 尾随闭包写法二:
httpTool?.loadData { [unowned self] (jsonData) in
print("在 viewController中拿到数据: \(jsonData)")
self.view.backgroundColor = UIColor.lightGrayColor()
}
// 注意 weak 和 unowned 的区别, 查看<<内存管理>>一文有详细说明