Swift的閉包函數(shù)

閉包函數(shù)的結(jié)構(gòu)

介紹

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)型推斷

    從類(lèi)型推斷可以看出函數(shù)可以作為參數(shù)傳遞,并且每個(gè)函數(shù)都是一個(gè)特殊的閉包擅腰。
    結(jié)果輸出:
    簡(jiǎn)單閉包函數(shù)輸出
  • 有參數(shù)的閉包
    override func viewDidLoad() {
       super.viewDidLoad()
       demo()
    }
    func demo() {
       let sum = {
          (a : Int, b : Int) in
          print("a = \(a), b = \(b)")
       }
       sum(10, 20)
    }
    
    有參數(shù)的閉包傳值輸出

    有參閉包在Xcode中的表現(xiàn)

    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))
    }
    
    有參數(shù)和返回值閉包的輸出結(jié)果

使用閉包回調(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)的返回值形參,例如以下圖片中的方式:

Swift4.0語(yǔ)法強(qiáng)調(diào)

閉包回調(diào)接收值的類(lèi)型推導(dǎo)

打印結(jié)果輸出:
使用閉包回調(diào)的輸出結(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)
      })
   }
}
尾隨閉包的原始寫(xiě)法

以下為尾隨閉包的簡(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ǎn)寫(xiě)

輸出結(jié)果:


尾隨閉包的輸出結(jié)果

閉包中出現(xiàn)循環(huán)引用的解決方式

做蘋(píng)果平臺(tái)軟件的開(kāi)發(fā)者對(duì)于循環(huán)引用都不會(huì)陌生有送,在使用 Objective-CBlock 時(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)引用
    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)毀了")
        }
     }
    
    以上代碼中出現(xiàn)的問(wèn)題與 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)存圖可以看到視圖的引用情況:
    循環(huán)引用的內(nèi)存表現(xiàn)圖

    下面是按照 Objective-C 的解決思路來(lái)解決該問(wèn)題:
    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)毀了")
        }
     }
    
    當(dāng)我們?cè)?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)存占有情況:
    解除循環(huán)引用后的內(nèi)存表現(xiàn)圖
  • Swift 推薦解除循環(huán)引用
    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)毀了")
        }
     }
    
    這份代碼只修改了函數(shù)的調(diào)用部分东揣,在閉包函數(shù)里添加代碼 [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 方法叼风;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末取董,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子无宿,更是在濱河造成了極大的恐慌茵汰,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孽鸡,死亡現(xiàn)場(chǎng)離奇詭異蹂午,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)彬碱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)豆胸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人巷疼,你說(shuō)我怎么就攤上這事配乱。” “怎么了皮迟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵搬泥,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我伏尼,道長(zhǎng)忿檩,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任爆阶,我火速辦了婚禮燥透,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辨图。我一直安慰自己班套,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布故河。 她就那樣靜靜地躺著吱韭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鱼的。 梳的紋絲不亂的頭發(fā)上理盆,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天痘煤,我揣著相機(jī)與錄音,去河邊找鬼猿规。 笑死衷快,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的姨俩。 我是一名探鬼主播蘸拔,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼环葵!你這毒婦竟也來(lái)了调窍?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤积担,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后猬仁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體帝璧,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年湿刽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了的烁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诈闺,死狀恐怖渴庆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雅镊,我是刑警寧澤襟雷,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站仁烹,受9級(jí)特大地震影響耸弄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卓缰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一计呈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧征唬,春花似錦捌显、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至摄闸,卻和暖如春击罪,著一層夾襖步出監(jiān)牢的瞬間哲嘲,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工媳禁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留眠副,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓竣稽,卻偏偏與公主長(zhǎng)得像囱怕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子毫别,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容