文章目录
  1. 1. 闭包的概念
    1. 1.0.1. 闭包与Block的对比
      1. 1.0.1.1. 闭包的几种格式
    2. 1.0.2. 闭包的循环引用问题
    3. 1.0.3. 其它

github地址: https://github.com/corderguo
本文地址: http://coderperson.com/2016/03/10/iOS-closure/
欢迎转载,请注明出处,谢谢。

闭包的概念

Swift 中闭包的概念就像是Objective-C中的block。OC中的block类似于匿名函数,闭包是用来定义函数, 同时闭包可以嵌套和作为参数传递。 在 Swift 中,函数也只不过是一种特殊的闭包

闭包与Block的对比

作用:

  • Block是用来保存一段代码,在需要的时候执行
  • 闭包也是用来保存一段代码,在需要的时候执行
  • 一般都是用来做耗时操作;

风格:

我们首先回顾一下OC中的Block写法:

1
2
3
4
5
6
7
8
// 定义block
@property (nonatomic, copy) void(^myBlock)();
// 定义block
self.myBlock = ^{
// 具体的回调操作
}
// 调用block
self.myBlock();

通过回顾,我们可以想起来Block的定义格式为:

Block格式:返回值类型(^block名称)(参数列表)

有的时候我们忘记了Block的书写格式,或者嫌这样写麻烦,我们可以直接在Xcode中敲inlineBlock
然后回车,系统会直接提醒你Block的样式;

Swift中闭包的基本格式:

1
2
3
4
5
{
(形参列表) -> (返回值)
in
需要执行的代码
}

其实这些格式我们都没必要专门去记忆,我们可以直接敲系统的带有闭包或者Block的函数,然后就一目了然了;

闭包的几种格式

第一种情况:

  • 将闭包通过实参传递给函数
  • 如果闭包是函数的最后一个参数,那么闭包可以写到函数{}的后面
  • 如果函数只接受一个参数,并且这个参数是闭包,那么()可以省略

例如下面这三种写法是一样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义函数,参数为闭包
func loadData(finished: () -> ()) {
// 操作
}
// 调用(这是默认写法)
loadData { () -> () in
print("回调了")
}
// 或者写成
loadData ({ () -> () in
print("回调了")
})
// 还可以写成
loadData() { () -> () in
print("回调了")
}

第二种情况:闭包的简写

  • 如果闭包没有参数也没有返回值,那么 in 之前的东西可以删除,包括 in

例如上面闭包,还可以写成下面的形式:

1
2
3
loadData {
print("回调了")
}

其实这些格式,你在开发中只需要掌握一种即可,或者你只需要记住闭包的定义,然后敲回车,系统会为你补齐格式;

闭包的循环引用问题

关于闭包中的 self 问题

在Swift开发中,有一个原则就是能不写self就不写self,但是在闭包中必须写上self;

这是因为闭包是用来保存一段代码,而且系统也不知道这段代码具体的调用时间,所以为了保证闭包中的对象不被释放,需要 self 进行一次强引用;这其实和Block中的原理差不多。

所以以后看到self基本上都和闭包有关系。(这也是闭包中循环引用来源的原因)

下面我举一个简单的关于闭包循环引用的例子:

你定义了两个控制器:OneController和TwoController,OneController只是负责push出TwoController,我们在TwoController中进行一些关于闭包的操作,然后在pop返回的时候查看该控制器是否被销毁了,来验证闭包是否发生了循环引用问题;
在TwoController中我们只需要简单写一些代码即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义一个闭包的属性 
var finished: (() -> ())?
// 定义一个函数,参数为闭包
func loadData(finished: () -> ()) {
print("调用了")
self.finished = finished
finished();
}
// 函数调用
loadData { () -> () in
print("回调了")
// 这句又强引用self,导致循环引用
self.view.backgroundColor = UIColor.redColor()
}
// 判断是否被销毁了
deinit {
print("控制器被销毁了")
}

当我们执行以上代码,并且从TwoController返回到OneController时,TwoController的deinit方法没有被调用,表明TwoController没有被销毁,闭包存在了循环引用的问题;

这是因为:控制器通过闭包属性引用闭包,而闭包中又强引用着self(控制器),所以导致了循环引用的问题;

OC中关于循环引用的解决方案

1
__weak typeof(self) weakSelf = self;

Swift中关于循环引用的解决方案

1
weak var weakSelf = self

那么原先代码中只需要把self改成weakSelf即可

1
2
// 由于weakSelf为可选类型,这里必须保证有值,所以加上!号
weakSelf!.view.backgroundColor = UIColor.redColor()

其它

关于定义闭包属性的问题

在Swift中,如果在某个类中定义一个属性,那么这个属性必须要初始化,否者会报错,如果暂时不想初始化,那么可以在后面写上一个 ? 号

但是在定义闭包的属性时,一定要注意,以下这种写法是最常见的一种错误写法:

1
2
3
4
5
/** 当前写法代表闭包的返回值可以是nil,而不是初始化的闭包 */
var finished: () -> ()?

/** 正确写法:需要在整个闭包的后面加问号 */
var finished: (() -> ())?

我们都是热爱移动开发的程序猿,加入我们,共同提高:

文章目录
  1. 1. 闭包的概念
    1. 1.0.1. 闭包与Block的对比
      1. 1.0.1.1. 闭包的几种格式
    2. 1.0.2. 闭包的循环引用问题
    3. 1.0.3. 其它