本節(jié)知識點
- 閉包的基本概念
- 閉包基本使用
- 閉包表達式作為回調(diào)函數(shù)
- 閉包的多種寫法(尾隨閉包)
- 閉包表達式優(yōu)化
- 閉包捕獲值
- <a href="#閉包的循環(huán)引用(重點)">閉包的循環(huán)引用(重點)</a>
1. 閉包的基本概念
-
閉包
是一種類似于OC語言的block
匿名函數(shù) -
block
和閉包
都經(jīng)常用于回調(diào) -
閉包
表達式(匿名函數(shù)
) 能夠捕獲上下文中的值
- 閉包格式:
{
(參數(shù)) -> 返回值類型 in
執(zhí)行語句
}
- 閉包表達式的類型和函數(shù)的類型一樣, 是參數(shù)加上返回值, 也就是
in
之前的部分 - 語法:
in
關(guān)鍵字的目的是便于區(qū)分返回值和執(zhí)行語句
2. 閉包基本使用
- ** 有參數(shù)沒有返回值寫法**
let say0:(String) ->Void = {
(name: String) in
print("hi \(name)")
}
say0("cdh")
//輸出結(jié)果: hi cdh
- 沒有參數(shù)沒有返回值寫法
let say1:() ->Void = {
() -> () in // 當沒有參數(shù)也沒有返回值是這行都可以省略
print("hi cdh")
}
say1()
//輸出結(jié)果: hi cdh
- 有參數(shù)有返回值
略; 按照格式, 結(jié)合函數(shù)的寫法即可
- 沒有參數(shù)有返回值
略
3. 閉包表達式作為回調(diào)函數(shù)
3.1 block
的用法回顧
- 定義網(wǎng)絡請求的類
@interface HttpTool : NSObject
- (void)loadRequest:(void (^)())callBackBlock;
@end
@implementation HttpTool
- (void)loadRequest:(void (^)())callBackBlock
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"加載網(wǎng)絡數(shù)據(jù):%@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
callBackBlock();
});
});
}
@end
- 進行網(wǎng)絡請求,請求到數(shù)據(jù)后利用
block
進行回調(diào)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.httpTool loadRequest:^{
NSLog(@"主線程中,將數(shù)據(jù)回調(diào).%@", [NSThread currentThread]);
}];
}
block
寫法總結(jié):
//block的寫法:
//類型定義:
返回值(^block的名稱)(block的參數(shù))
//等號右邊的值:
^(參數(shù)列表) {
// 執(zhí)行的代碼
};
3.2 使用閉包代替block
- 定義網(wǎng)絡請求的類
class HttpTool: NSObject {
func loadRequest(callBack : ()->()){
dispatch_async(dispatch_get_global_queue(0, 0)) { () -> Void in
print("加載數(shù)據(jù)", [NSThread.currentThread()])
dispatch_async(dispatch_get_main_queue(), { () -> Void in
callBack()
})
}
}
}
- 進行網(wǎng)絡請求,請求到數(shù)據(jù)后利用閉包進行回調(diào)
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// 網(wǎng)絡請求
httpTool.loadRequest ({ () -> () in
print("回到主線程", NSThread.currentThread());
})
}
- 閉包寫法總結(jié):
//閉包的寫法:
// 類型:(形參列表)->(返回值)
// 技巧:初學者定義閉包類型,直接寫()->().再填充參數(shù)和返回值
//值:
{
(形參) -> 返回值類型 in
// 執(zhí)行代碼
}
3.3 閉包的簡寫
- 如果閉包沒有參數(shù),沒有返回值.in和in之前的內(nèi)容可以省略
httpTool.loadRequest({
print("回到主線程", NSThread.currentThread());
})
4. 閉包的多種寫法(尾隨閉包)
- 如果閉包是函數(shù)的最后一個參數(shù),則可以將閉包寫在
()
后面 - 如果函數(shù)只有一個參數(shù),并且這個參數(shù)是閉包,那么
()
可以不寫 - 這樣可以提高閱讀性. 稱之為尾隨閉包
httpTool.loadRequest() {
print("回到主線程", NSThread.currentThread());
}
// 開發(fā)中建議該寫法
httpTool.loadRequest {
print("回到主線程", NSThread.currentThread());
}
5. 閉包表達式優(yōu)化
- 類型優(yōu)化, 由于函數(shù)中已經(jīng)聲明了閉包參數(shù)的類型, 所以傳入的實參可以不用寫類型
- 返回值優(yōu)化, 同理由于函數(shù)中已經(jīng)聲明了閉包的返回值類型, 所以傳入的實參可以不用寫類型
- 參數(shù)優(yōu)化, swift可以使用$索引的方式來訪問閉包的參數(shù), 默認從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<Int> = [31, 13, 52, 84, 5]
print("排序前 \(arr)")
//輸出結(jié)果: 排序前 [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)")
//輸出結(jié)果: 排序后 [84, 52, 31, 13, 5]
- 如果只有一條語句可以省略return
let hehe = {
"我是cdh"
}
6. 閉包捕獲值
- 被捕獲的值會和與之對應的方法綁定在一起, 下一次需要用到這個方法這繼續(xù)使用之前捕獲到的值
- 同一個方法中的變量, 不會被綁定到不同的方法變量(或常量中)
func getIncFunc() -> (Int) -> Int {
var max = 10
func incFunc(x :Int) ->Int{
print("incFunc函數(shù)結(jié)束")
max++ // ++ 這種寫法將在 swift3.0 移除, 推薦寫成 += 1
return max + x
}
//當執(zhí)行到這一句時incFunc 的參數(shù) x 就應該被釋放了
//但是由于在內(nèi)部函數(shù)中使用到了它, 所以它被捕獲了
//同理, 當執(zhí)行完這一句時max變量就被釋放了
//但是由于在內(nèi)部函數(shù)中使用到了它, 所以它被捕獲了
print("getIncFunc函數(shù)結(jié)束")
return incFunc
}
let incFunc = getIncFunc()
print("---------")
print(incFunc(5))
print("---------")
print(incFunc(5))
//輸出結(jié)果:
//getIncFunc函數(shù)結(jié)束
//---------
//incFunc函數(shù)結(jié)束
//16
//---------
//incFunc函數(shù)結(jié)束
//17
let incFunc2 = getIncFunc()
print(incFunc2(5))
//輸出結(jié)果:
//getIncFunc函數(shù)結(jié)束
//incFunc函數(shù)結(jié)束
//16
7. <a name = "閉包的循環(huán)引用(重點)"></a>閉包的循環(huán)引用(重點)
- 閉包形成的循環(huán)引用與 block 的循環(huán)引用是一樣的原因?qū)е? 如下閉包形成的循環(huán)引用的示例:
強引用 loadData函數(shù)的參數(shù)(閉包). png
loadData函數(shù)中參數(shù)(閉包)強引用 self(ViewController).png
// ViewController.swift
// 03-13-閉包的循環(huán)引用
//
// 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<UITouch>, withEvent event: UIEvent?) {
httpTool?.loadData(){ (jsonData) in
print("在 viewController中拿到數(shù)據(jù): \(jsonData)")
self.view.backgroundColor = UIColor.grayColor()
// self 強引用這類屬性 httpTool, httpTool 中的函數(shù) loadData 中的閉包參數(shù)在這里引用者 self (ViewController)
// 如果此次該閉包參數(shù)在 HttpTool 類中沒有被強引用, 則此時將不形成循環(huán)引用,因為閉包是臨時存儲, 用完就被釋放
// 也就只有self (ViewController) 強引用這類屬性 httpTool, 而 HttpTool 并沒有強引用 self
// 但是如果在 HttpTool 類中強引用了 loadData函數(shù)中的閉包這個參數(shù), 則此時就會形成循環(huán)引用
}
}
// 該對象被釋放都會調(diào)用這個函數(shù)
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) -> ())?
// 閉包的類型寫法: (參數(shù)列表) -> (返回值類型)
func loadData(finishedCallback : (jsonData : String) -> ()) {
// 此處對函數(shù)中的閉包參數(shù)通過屬性強引用
self.finishedCallBack = finishedCallback
dispatch_async(dispatch_get_global_queue(0, 0)) { () -> Void in
print("正在發(fā)送網(wǎng)絡請求: \(NSThread.currentThread())")
dispatch_sync(dispatch_get_main_queue(), {
() -> Void in
print("回調(diào)主線程, 見數(shù)據(jù)回傳出去, \(NSThread.currentThread())")
finishedCallback(jsonData: "json數(shù)據(jù)")
})
}
}
}
- 通過以上兩段代碼已經(jīng)形成了強引用, 這樣一來就會形成內(nèi)存泄漏問題
7.1 解決循環(huán)引用
- 未免出現(xiàn)以上的循環(huán)引用導致內(nèi)存泄漏的問題, 有多種解決辦法
- 方法一: 刪除對 loadData 函數(shù)中的閉包參數(shù)做強引用即可
// 刪除這個即可
// 此處對函數(shù)中的閉包參數(shù)通過屬性強引用
self.finishedCallBack = finishedCallback
- 方法二(重點掌握): 將 self (ViewController) 使用弱引用關(guān)鍵之修飾
// 寫法一: 定義一個 weak 修飾的新的臨時變量
weak var weakSelf: ViewController? = self
httpTool?.loadData({ (jsonData) ->() in
weakSelf?.view.backgroundColor = UIColor.orangeColor()
print("在 viewController中拿到數(shù)據(jù): \(jsonData)")
})
// 寫法二: 直接使用 [weak self] 寫在閉包的大括號中
// 尾隨閉包 : 如果在調(diào)用方法時,該方法的最后一個參數(shù)是一個閉包.那么該閉包可以寫成尾隨閉包
// 尾隨閉包寫法一: (推薦將 [weak self]寫在閉包的大括號中的寫法)
httpTool?.loadData(){ [weak self] (jsonData) in
print("在 viewController中拿到數(shù)據(jù): \(jsonData)")
self?.view.backgroundColor = UIColor.grayColor()
}
// 尾隨閉包寫法二:
httpTool?.loadData { [unowned self] (jsonData) in
print("在 viewController中拿到數(shù)據(jù): \(jsonData)")
self.view.backgroundColor = UIColor.lightGrayColor()
}
// 注意 weak 和 unowned 的區(qū)別, 查看<<內(nèi)存管理>>一文有詳細說明