介紹
Swift官方開(kāi)發(fā)文檔對(duì)于閉包的介紹是這樣的:閉包是可以在代碼中被傳遞和引用的功能性獨(dú)立模塊狞膘。Swift
中的閉包類(lèi)似于其他語(yǔ)言的匿名函數(shù),和 C
Objective-C
中的 Block
很像斟览。主要作用是捕捉和存儲(chǔ)定義在上下文中的任何常量和變量的引用跪另,處理關(guān)于捕獲的內(nèi)存管理的操作(看了這些介紹很懵逼洁段,我只看懂了一句”和 Block
很像“)晌柬。
閉包的應(yīng)用場(chǎng)景
- 異步執(zhí)行完成回調(diào)
- 控制器間回調(diào)
- 自定義視圖回調(diào)
閉包的定義
閉包有特殊的定義方式,定義格式為 {參數(shù)列表 -> 返回值 in 實(shí)現(xiàn)代碼}
绑嘹,如果沒(méi)有參數(shù)也沒(méi)有返回值可以省略前面的定義部分(同時(shí)省略關(guān)鍵字 in
)直接寫(xiě)實(shí)現(xiàn)代碼稽荧。
- 沒(méi)有參數(shù)沒(méi)有返回值的閉包(最簡(jiǎn)單的閉包)
從上面代碼中可以可以看出,override func viewDidLoad() { super.viewDidLoad() demo() } func demo() { let a = { print("這是個(gè)最簡(jiǎn)單的閉包") } // 執(zhí)行閉包 a () }
Swift
中閉包的寫(xiě)法與Objective-C
中的Block
寫(xiě)法基本一致工腋,只是少了一個(gè)^
符號(hào)姨丈。
從類(lèi)型推斷可以看出函數(shù)可以作為參數(shù)傳遞,并且每個(gè)函數(shù)都是一個(gè)特殊的閉包擅腰。
結(jié)果輸出:
- 有參數(shù)的閉包
override func viewDidLoad() { super.viewDidLoad() demo() } func demo() { let sum = { (a : Int, b : Int) in print("a = \(a), b = \(b)") } sum(10, 20) }
在Swift
閉包中蟋恬,參數(shù)、返回值和實(shí)現(xiàn)代碼都要寫(xiě)在{}
中趁冈,需要關(guān)鍵字in
分隔定義和實(shí)現(xiàn)歼争。 - 有參數(shù)也有返回值的閉包
override func viewDidLoad() { super.viewDidLoad() demo() } func demo() { let sum = { (a : Int, b : Int) -> Int in return a + b } print(sum(10, 20)) }
使用閉包回調(diào)傳遞參數(shù)
在異步執(zhí)行任務(wù)獲取結(jié)果通過(guò)閉包回調(diào),與 Objective-C
中的 Block
的用法完全一致渗勘。
override func viewDidLoad() {
super.viewDidLoad()
loadData {
(result) in
print(result)
}
}
func loadData(completion: @escaping ([String]) -> Void) -> () {
DispatchQueue.global().async {
print("此處執(zhí)行耗時(shí)操作\(Thread.current)")
// 模擬耗時(shí)操作沐绒,讓線程休眠10秒鐘
Thread.sleep(forTimeInterval: 10)
// 異步加載網(wǎng)絡(luò)數(shù)據(jù)(這里只做演示)
let jsonData = ["八卦", "頭條", "新聞"]
// 回到主線程更新界面
DispatchQueue.main.async(execute: {
// 回調(diào)執(zhí)行閉包,傳遞異步獲取到的結(jié)果
completion(jsonData)
})
}
}
以上代碼為 Swift4.0
的最新寫(xiě)法旺坠,在早期的 Swift
中閉包可以使用外部參數(shù)乔遮,但在 Swift3.0
更新了以后蘋(píng)果禁止了閉包設(shè)置外部參數(shù),并且拋棄了 @noescaping
的使用改用 @escaping
取刃,如果閉包的回調(diào)需要逃逸必須使用@escaping
修飾蹋肮,閉包默認(rèn)是不逃逸的出刷。需要注意的是在閉包在回調(diào)的時(shí)候需要手動(dòng)鍵入回調(diào)回來(lái)的返回值形參,例如以下圖片中的方式:
打印結(jié)果輸出:
尾隨閉包
如果函數(shù)的最后一個(gè)參數(shù)是閉包那么該函數(shù)參數(shù)可以提前結(jié)束坯辩,最后一個(gè)參數(shù)可以直接使用 {}
包裝(閉包里嵌套尾隨閉包除外)巷蚪。
以下為閉包的完整寫(xiě)法:
override func viewDidLoad() {
super.viewDidLoad()
loadData(completion : {
(result) -> () in
print(result)
})
}
func loadData(completion : @escaping ([String]) -> ()) -> () {
DispatchQueue.Global().async {
print("此處執(zhí)行耗時(shí)操作\(Thread.current)")
// 模擬耗時(shí)操作,讓線程休眠 10 秒鐘
Thread.sleep(forTimeInterval: 10)
// 此處模擬從網(wǎng)絡(luò)獲取獲取數(shù)據(jù)
let resultData = ["八卦", "頭條", "新聞"]
// 回到主線程更新UI
DispatchQueue.main.async(execute : {
// 回調(diào)執(zhí)行閉包濒翻,傳遞異步獲取到的網(wǎng)絡(luò)數(shù)據(jù)
completion(resultData)
})
}
}
以下為尾隨閉包的簡(jiǎn)寫(xiě)版:
override func viewDidLoad() {
super.viewDidLoad()
loadData{
(result) in
print(result)
}
}
func loadData(completion : @escaping ([String]) ->()) {
DispatchQueue.Global().async {
print("此處執(zhí)行耗時(shí)操作\(Thread.current)")
// 模擬耗時(shí)操作,讓線程休眠 10 秒鐘
Thread.sleep(forTimeInterval: 10)
// 此處模擬網(wǎng)絡(luò)獲取到的數(shù)據(jù)
let resultData = ["八卦", "頭條", "新聞"]
// 回到主線程更新 UI
DispatchQueue.main.async {
// 回調(diào)執(zhí)行閉包啦膜,傳遞異步獲取到的網(wǎng)絡(luò)數(shù)據(jù)
completion(resultData)
}
}
}
輸出結(jié)果:
閉包中出現(xiàn)循環(huán)引用的解決方式
做蘋(píng)果平臺(tái)軟件的開(kāi)發(fā)者對(duì)于循環(huán)引用都不會(huì)陌生有送,在使用 Objective-C
的 Block
時(shí),當(dāng)某一個(gè)類(lèi)擁有該 block
塊并且在本類(lèi)中會(huì)使用該 block
塊進(jìn)行操作的時(shí)候僧家,如果在這個(gè) block
塊中使用 self
會(huì)形成循環(huán)引用(這段話寫(xiě)的也是啰嗦到?jīng)]誰(shuí)了)雀摘,不啰嗦了直接上代碼:
#import "ViewController.h"
@interface ViewController ()
// 定義 Block 屬性
@property (nonatomic, copy) void (^testBlock) (void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 調(diào)用方法
[self loadData: {
NSLog(@"%@", self.view);
}];
}
- (void)loadData:(void (^) (void))completion {
// 使用屬性記錄 block
self.testBlock = completion;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"耗時(shí)操作");
[NSThread sleepForTimeInterval: 2.0];
dispatch_async(dispatch_get_main_queue(), ^{
//執(zhí)行block
completion();
});
});
}
@end
以上代碼是最典型的循環(huán)引用代碼,可以看到控制器定義 ```block屬性擁有該
block塊八拱,又在
block實(shí)現(xiàn)中使用強(qiáng)引用
self造成
block引用控制器阵赠,以下為
Objective-C`的循環(huán)引用解決方式:
#import "ViewController.h"
@interface ViewController ()
// 定義 Block 屬性
@property (nonatomic, copy) void (^testBlock) (void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 此處打斷循環(huán)
__weak typeof(self) weakSelf = self;
// 調(diào)用方法
[self loadData: {
NSLog(@"%@", weakSelf.view);
}];
}
- (void)loadData:(void (^) (void))completion {
// 使用屬性記錄 block
self.testBlock = completion;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"耗時(shí)操作");
[NSThread sleepForTimeInterval: 2.0];
dispatch_async(dispatch_get_main_queue(), ^{
//執(zhí)行block
completion();
});
});
}
@end
前面說(shuō)了在 Swift
中閉包與 block
的應(yīng)用場(chǎng)景完全一致,那么在 Swift
中出現(xiàn)循環(huán)引用該如何解決呢肌稻?
Swift 中循環(huán)引用的解決方式
- 使用Objective-C的方法解決Swift循環(huán)引用
以上代碼中出現(xiàn)的問(wèn)題與import UIKit class ViewController: UIViewController { // 定義一個(gè)閉包屬性 var completionBack : (() -> ())? override func viewDidLoad() { super.viewDidLoad() // 調(diào)用函數(shù) loadData { print(self.view) } } // 定義帶有閉包的函數(shù) func loadData(completion : @escaping () -> ()) -> () { // 使用屬性記錄閉包 completionBack = completion DispatchQueue.global().async { print("耗時(shí)操作") Thread.sleep(forTimeInterval: 2.0) DispatchQueue.main.async { // 回調(diào) 執(zhí)行閉包 completion() } } } deinit { print("視圖銷(xiāo)毀了") } }
Objective-C
中的循環(huán)引用一樣清蚀,因?yàn)樾纬闪搜h(huán)引用該視圖控制器中的deinit
永遠(yuǎn)不會(huì)被調(diào)用,這樣就形成了內(nèi)存泄漏從而導(dǎo)致 App 閃退等問(wèn)題爹谭。接下來(lái)通過(guò)內(nèi)存圖可以看到視圖的引用情況:
下面是按照Objective-C
的解決思路來(lái)解決該問(wèn)題:
當(dāng)我們?cè)?import UIKit class ViewController: UIViewController { // 定義一個(gè)閉包屬性 var completionBack : (() -> ())? override func viewDidLoad() { super.viewDidLoad() // 此處打斷循環(huán)鏈條 weak var weakSelf = self // 調(diào)用函數(shù) loadData { print(weakSelf?.view ?? "") } } // 定義帶有閉包的函數(shù) func loadData(completion : @escaping () -> ()) -> () { // 使用屬性記錄閉包 completionBack = completion DispatchQueue.global().async { print("耗時(shí)操作") Thread.sleep(forTimeInterval: 2.0) DispatchQueue.main.async { // 回調(diào) 執(zhí)行閉包 completion() } } } deinit { print("視圖銷(xiāo)毀了") } }
Swift
中使用weak
的時(shí)候要特別注意枷邪,該關(guān)鍵字只能修飾通過(guò)var
定義的變量,原因是系統(tǒng)會(huì)在運(yùn)行時(shí)對(duì)該變量進(jìn)行修改诺凡。
用內(nèi)存表現(xiàn)圖來(lái)說(shuō)明解決后的內(nèi)存占有情況:
-
Swift
推薦解除循環(huán)引用
這份代碼只修改了函數(shù)的調(diào)用部分东揣,在閉包函數(shù)里添加代碼import UIKit class ViewController: UIViewController { // 定義一個(gè)閉包屬性 var completionBack : (() -> ())? override func viewDidLoad() { super.viewDidLoad() // 調(diào)用函數(shù) loadData { [wead self] in print(self?.view ?? "") } } // 定義帶有閉包的函數(shù) func loadData(completion : @escaping () -> ()) -> () { // 使用屬性記錄閉包 completionBack = completion DispatchQueue.global().async { print("耗時(shí)操作") Thread.sleep(forTimeInterval: 2.0) DispatchQueue.main.async { // 回調(diào) 執(zhí)行閉包 completion() } } } deinit { print("視圖銷(xiāo)毀了") } }
[weak self] in
表示該閉包里使用的所有self
都是弱引用,需要注意的是可選值的解包腹泌。
Swift
還提供了另外一種解除循環(huán)引用的方式嘶卧,將上述代碼中的[weak self] in
替換為[unowned self] in
。此時(shí)的閉包中self
不是可選值不用考慮解包的問(wèn)題凉袱,但這行代碼與Objective-C
中的__unsafe_unretained
的功能一樣芥吟。表示該閉包中使用的所有self
都是assign
值,雖然不會(huì)強(qiáng)引用专甩,但在對(duì)象釋放了之后self
的指針地址不會(huì)置空运沦,如果對(duì)象被釋放了以后繼續(xù)調(diào)用就會(huì)出現(xiàn)野指針,操作的時(shí)候會(huì)讓程序crash配深,所以這種方式相比[weak self] in
并不安全携添,只需要簡(jiǎn)單了解一下即可。
總結(jié)
-
Swift
中的閉包與Block
的應(yīng)用場(chǎng)景一樣篓叶; - 每個(gè)函數(shù)都是一個(gè)特殊的閉包烈掠;
- 閉包函數(shù)有特殊的寫(xiě)法
{參數(shù)列表 -> 返回值類(lèi)型 in 實(shí)現(xiàn)代碼}
羞秤,如果沒(méi)有參數(shù)也沒(méi)有返回值可省略前面定義部分; -
Swift4.0
更新了閉包定義的語(yǔ)法格式左敌,拋棄了@noescaping
的使用瘾蛋; - 函數(shù)中的最后一個(gè)參數(shù)是閉包,那么該閉包稱(chēng)為尾隨閉包矫限,尾隨閉包可省略函數(shù)的形參直接使用
{}
包裝編寫(xiě)哺哼。 -
Swift
中提供了兩種解除循環(huán)引用的方式,推薦使用[weak self] in
方法叼风;