Swift中的闭包
Swift中的闭包和OC中的block大致相同,OC中的block相当于匿名函数,而Swift中的闭包也相当于特殊的函数。 闭包的数据类型格式为: (参数列表) -> (返回值类型)
下面用一个异步网络请求的案例来简单使用一下闭包:
首先创建模拟网络请求类⬇️
import UIKit
// 模拟网络工具类
class HWHttpTools: NSObject {
// 模拟网络工具类提供的网络请求接口函数
// 注意点: 1.Swift中闭包的参数必须设置为内部参数(在参数名前加上下划线)
// 2.闭包中多个参数用逗号隔开
// 3.Swift3.0中GCD异步函数的写法发生改变 DispatchQueue.global().async { 异步执行代码块 }
// 4.Swift3.0中查看当前线程 Thread.current
// 5.如下,Swift3.0中如果在当前函数内直接调用闭包就不需要加 @escaping 来修饰闭包;但是如果在其他闭包内调用,就必须在闭包前面添加 @escaping(逃逸)关键字
func requestWith(url : String, _ finishedCallBlock : @escaping (_ succeed : Bool, _ data : Data?)->()) {
// 发起异步请求
DispatchQueue.global().async {
print("发起异步请求 - \(Thread.current)")
// 回到主线程
DispatchQueue.main.async {
print("请求成功回到主线程 - \(Thread.current)")
// 闭包回调
finishedCallBlock(true, nil)
}
}
}
}
在控制器中点击view利用网络工具类发起网络请求
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
// 点击屏幕 发送网络请求
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
HWHttpTools().requestWith(url: "服务器url字符串") { (succeed : Bool, data : Data?) in
if succeed {
print("网络请求成功 - \(Thread.current)")
}
}
}
}
点击view打印结果⬇️
发起异步请求 - <NSThread: 0x6080002613c0>{number = 3, name = (null)}
请求成功回到主线程 - <NSThread: 0x60000007a9c0>{number = 1, name = main}
网络请求成功 - <NSThread: 0x60000007a9c0>{number = 1, name = main}
闭包衍生的循环引用问题
在上面的案例中,我们故意在控制器闭包回调中添加一句更改view视图背景颜色的代码,看看是否会发生循环引用问题。(我们把网络工具类对象设置成控制器的一个强引用属性)
import UIKit
class ViewController: UIViewController {
// 网络工具类属性
var httpTools : HWHttpTools?
override func viewDidLoad() {
super.viewDidLoad()
}
// 点击屏幕 发送网络请求
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 网络工具类属性赋值
httpTools = HWHttpTools()
httpTools?.requestWith(url: "服务器url字符串") { (succeed : Bool, data : Data?) in
// 更改了view视图的背景颜色
// 在闭包内调用当前类的属性,必须加 self.
// 那么在这个闭包中调用了self会不会发生循环引用?
// *** 答案是否定的,当前控制器强引用工具类对象,闭包强引用当前控制器,但是工具类对象并没有强引用闭包,所以并不会发生循环引用。***
self.view.backgroundColor = UIColor.red
}
}
}
但是万一在闭包中发生了循环引用问题,在Swift当中我们应该怎么处理呢?首先我们先让工具类对象强引用闭包。⬇️
import UIKit
// 模拟网络工具类
class HWHttpTools: NSObject {
var finishedCallBlock : ((_ succeed : Bool, _ data : Data?)->())?
func requestWith(url : String, _ finishedCallBlock : @escaping (_ succeed : Bool, _ data : Data?)->()) {
// 强应用闭包
self.finishedCallBlock = finishedCallBlock
// 发起异步请求
DispatchQueue.global().async {
print("发起异步请求 - \(Thread.current)")
// 回到主线程
DispatchQueue.main.async {
print("请求成功回到主线程 - \(Thread.current)")
// 闭包回调
finishedCallBlock(true, nil)
}
}
}
}
然后再控制器中出现循环引用,我们采取使用在闭包外部将控制器self赋值给weak修饰的变量来阻断闭包对控制器的强引用。⬇️
import UIKit
class ViewController: UIViewController {
// 网络工具类属性
var httpTools : HWHttpTools?
override func viewDidLoad() {
super.viewDidLoad()
}
// 点击屏幕 发送网络请求
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 网络工具类属性赋值
httpTools = HWHttpTools()
// 注意: weak修饰的标识符可能为nil,所以weakSelf为可选类型
// 注意: weak只能修饰变量
weak var weakSelf = self
httpTools?.requestWith(url: "服务器url字符串") { (succeed : Bool, data : Data?) in
weakSelf?.view.backgroundColor = UIColor.red
}
}
}
闭包循环引用问题另一种解决写法(简便写法)
import UIKit
class ViewController: UIViewController {
// 网络工具类属性
var httpTools : HWHttpTools?
// 点击屏幕 发送网络请求
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 网络工具类属性赋值
httpTools = HWHttpTools()
// 解决循环引用的方式二 在闭包的{}大括号内的开头 写上[weak self] 然后再比包内使用 self 就不会发生循环引用,切记这时闭包内使用的 self 为可选类型。
// 以上写法相当于在闭包外部写上 weak var weakSelf = self,然后在闭包内部使用 weakSelf 替代 self 原理一样。
httpTools?.requestWith(url: "服务器url字符串") { [weak self] (succeed : Bool, data : Data?) in
self?.view.backgroundColor = UIColor.red
}
}
// 析构函数
deinit {
print("控制器销毁")
}
}
PS : 还有一种写法是将上面的 [weak self] 改写成 [unowned self] 效果是一样的,但是使用 unowned 是很危险的容易出现操作野指针,unowned 的意思跟OC当中的__unsafe_unretained一样 只不过保证 self 不被销毁就可以安全使用。
尾随闭包
当函数参数中的闭包参数是最后一个,在函数调用时闭包参数整个大括号可以写在参数列表小括号外面(紧跟在后面)
// 写法一
HWHttpTools().function(name: "参数一", {
// 闭包代码块
})
// 写法二 尾随闭包
HWHttpTools().function(name: "参数一") {
// 闭包代码块
}
当函数只有一个参数,且是闭包参数,那么可以在函数调用的时候省去()小括号,如下3种写法均可:
HWHttpTools().function({ //普通写法
})
HWHttpTools().function() { // 尾随闭包写法
}
HWHttpTools().function { // 省略小括号的尾随闭包写法
}
Swift中的懒加载
懒加载特性 : 1.用到时才会加载;2.程序运行过程中只会创建一次
在OC中的懒加载我们通常是通过重写其getter方法来实现的,但是在Swift中专门提供了一个关键字 lazy 来修饰懒加载属性,具体写法如下:
lazy var names : [String] = Array() // 懒加载数组写法一
lazy var items : [String] = { // 懒加载数组写法二
let items : [String] = Array()
return items
}()
在开发中,很多控件也使用懒加载,用到即加载到内存中。
lazy var btn : UIButton = UIButton() // 只是为了初始化控件 不进行其他设置的懒加载写法
lazy var btn : UIButton = {
let btn = UIButton()
// 懒加载设置其他属性写法
btn.setTitle("按钮", for: .normal)
btn.setBackgroundImage(UIImage(named: "xx.png"), for: .normal)
return btn
}()
Swift中的访问权限
在Swift3.0中规定访问权限的关键词有 internal 、 open、 private、 fileprivate。
这些访问权限关键词不仅可以修饰属性,也可以修饰方法和类。
internal : 默认修饰词(属性、类、方法默认权限修饰词),表示在同一个模块(target、项目、资源包)中都能够访问。
open : 表示跨项目(target、项目、资源包)都能够使用。
private : 表示只能在当前类中访问。
fileprivate : 表示只能在当前文件中使用。
Swift中零散知识点
1.在Swift开发中,并不是所有的方法都写在一个类中,这样可读性也不会很好。如果能够灵活运用延展,就可以有效提高代码的可读和美观性。但是注意 : 在延展中的方法不能用private修饰,不然在当前类中也无法访问,只能在延展中使用,但是可以用fileprivate修饰。
2.在Swift开发中,监听方法若用 private 或 fileprivate 修饰必须前面加上 @objc 修饰词。(什么是监听方法 : 比如写在#selector()里面的按钮的监听方法)
3.在Swift中分栏注释(提高代码阅读性) // MARK: info ,类似于OC项目中的 #pragma mark - info 的作用。
Swift3.0小案例
小案例目的在于综合运用Swift3.0写一个页面,中间设计网络请求、控件搭建、图片加载、转换数据模型等。
源代码地址 : https://github.com/IMLoser/Swift3.0_Demo