Swift3之閉包

Swfit的學(xué)習(xí)大致有幾個重點:
元組可選型函數(shù),閉包
這些是較之OC有很大不同的地方,學(xué)會了這些swift也算是入門了。

閉包有三種形式:

全局函數(shù) 嵌套函數(shù) 閉包表達式
有名字但不能捕獲任何值。 有名字,也能捕獲封閉函數(shù)內(nèi)的值。 無名閉包,使用輕量級語法,可以根據(jù)上下文環(huán)境捕獲值。

捕獲值

  • 閉包可以在其定義的上下文中捕獲常量或變量。 即使定義這些常量和變量的原域已經(jīng)不存在,閉包仍然可以在閉包函數(shù)體內(nèi)引用和修改這些值。我認為可以理解為捕獲增加了原常量或變量的引用計數(shù)。
  • Swift最簡單的閉包形式是嵌套函數(shù),也就是定義在其他函數(shù)的函數(shù)體內(nèi)的函數(shù)。 嵌套函數(shù)可以捕獲其外部函數(shù)所有的參數(shù)以及定義的常量和變量。
    不理解的可以看捕獲值

全局函數(shù)

什么叫全局函數(shù)?全局函數(shù)不能捕獲值?啥意思呢?一陣懵逼。

let value = 1;

func testGlobalFunc()
{
//這不是捕獲了外部的value嗎?
    print(value);
}

testGlobalFunc()

我的理解是這樣的:
全局函數(shù)是定義在類之外的不屬于任何類的函數(shù)。
寫OC的時候常常會用到各種宏定義,但是Swift中沒有宏,通常是通過全局常量或者全局函數(shù)來實現(xiàn)這一效果.我們只需要建一個文件(假設(shè)叫Global.swift),把想用的定義在里面,無須導(dǎo)入頭文件什么的,就可以在全局用啦.
在類中實現(xiàn)的函數(shù)是成員函數(shù),也就是我們一般說的函數(shù)

//
//  Global.swift
//
//  Created by apple on 2017/6/15.
//

import Foundation
import UIKit

/**
 *   替代oc中的#define,列舉一些常用宏
 */
let kScreenWidth = UIScreen.main.bounds.size.width

// 屏幕的物理寬度
func kScreenWidthFun() ->CGFloat
{
    return kScreenWidth
}

func kScreenHeighFun() ->CGFloat
{
    return UIScreen.main.bounds.size.height
}

現(xiàn)在就可以直接調(diào)用kScreenHeighFun了。這就是全局函數(shù),不需要類或?qū)ο笳{(diào)用,就和全局變量kScreenWidth一樣 有人會問kScreenWidthFun不是捕獲了kScreenWidth嗎?我認為全局變量是跟隨項目的生命周期的,所以這里只是調(diào)用,不能算捕獲/。

閉包表達式

語法:
{
(參數(shù)) -> 返回值 in
函數(shù)實現(xiàn)
}
*這里的參數(shù),可以是inout(輸入輸出參數(shù)),但不能設(shè)定默認值 *元組也可以作為參數(shù)或者返回值 *"in"關(guān)鍵字表示閉包的參數(shù)和返回值類型定義已經(jīng)完成,閉包函數(shù)體即將開始。即由in引入函數(shù)

閉包是一種簡化的函數(shù),函數(shù)類型由參數(shù)和返回值組成
例如:(Int) -> Int就是一個函數(shù)類型
閉包類型和函數(shù)一樣,閉包類型也是這個樣子的。
所以:test : (Int) ->(Int);對于變量test可以賦值為一個函數(shù),也可以賦值為一個閉包

//一般形式
let add:(Int,Int)->(Int) = {
    (a:Int,b:Int) -> Int in
    return a + b
}
print(add(10,5))

//Swift可以根據(jù)閉包上下文推斷參數(shù)和返回值的類型,所以上面的例子可以簡化如下
let add2:(Int,Int)->(Int) = {
    a,b in  //省略了返回箭頭和參數(shù)及返回值類型,以及參數(shù)周圍的括號。當(dāng)然你也可以加括號,為了代碼的可讀性: (a,b) in
    return a + b
}
print(add2(10,5))

//Swift 自動為內(nèi)聯(lián)函數(shù)提供了參數(shù)名稱縮寫功能,您可以直接通過$0,$1,$2來順序調(diào)用閉包的參數(shù)。
//如果你在閉包表達式中使用參數(shù)名稱縮寫, 您可以在閉包參數(shù)列表中省略對其定義, 并且對應(yīng)參數(shù)名稱縮寫的類型會通過函數(shù)類型進行推斷。in 關(guān)鍵字同樣也可以被省略
//一般不建議這樣寫,可讀性太差
let add3:(Int,Int) -> Int = {
    return $0+$1;//知道要傳入兩個Int值,所以$0代表第一個參數(shù),$1代表第二個參數(shù)
}
print(add3(10,5));

//單行表達式閉包可以隱式返回,如下,省略return
let add4:(Int,Int)->(Int) = {
(a,b) in 
a + b
}
print(add4(10,5))


//如果閉包沒有參數(shù),可以直接省略“in”
let add5:()->Int = {return 10 + 5}
print(add5())


//這個是既沒有參數(shù)也沒返回值,所以把return和in都省略了
let add6:()->Void = {print("15")}
add6()

//inout參數(shù),不太常用,因為我們直接使用外部值就行,沒必要再作為參數(shù)傳遞一下

var addValue : Int = 10;

let test:(inout Int,Int)->(Int) = {
(a,b)  in
return a + b
}

print(test(&addValue,5))

let test2:(Int,Int)->(Int) = {
    (a,b)  in
    addValue +=(a + b);
    return addValue//能夠直接修改addValue沒必要使用inout傳入
}

print(test2(10,5))

閉包類型

可以用關(guān)鍵字“typealias”聲明一個閉包數(shù)據(jù)類型。類似于OC中的typedef

typealias AddBlock = (Int, Int) -> (Int)
//typedef int (^testBlock)(int,int);//OC中block類型的定義
let Add:AddBlock = {
    (c,d) in
    return c + d
}
 
let Result = Add(10,5)
print("Result = \(Result)")

尾隨閉包

若將閉包作為函數(shù)最后一個參數(shù),可以省略參數(shù)標(biāo)簽,然后將閉包表達式寫在函數(shù)調(diào)用括號后面。尾隨閉包讓代碼更簡潔

func testFunction(testBlock: ()->Void){
    //這里需要傳進來的閉包類型是無參數(shù)和無返回值的
    testBlock()
}
//正常寫法
testFunction(testBlock: {
    print("正常寫法")
})
//尾隨閉包寫法
testFunction(){
    print("尾隨閉包寫法")
}
//若只有一個閉包參數(shù),也可以把括號去掉,也是尾隨閉包寫法。推薦寫法
testFunction { 
    print("去掉括號的尾隨閉包寫法")
}

逃逸閉包

  • 當(dāng)一個閉包作為參數(shù)傳到一個函數(shù)中,需要這個閉包在函數(shù)執(zhí)行結(jié)束之后才被執(zhí)行,我們就稱該閉包從函數(shù)中逃逸。一般如果閉包在函數(shù)體內(nèi)涉及到異步操作,但函數(shù)卻是很快就會執(zhí)行完畢并返回的,閉包必須要逃逸掉,以便異步操作的回調(diào)。
  • 逃逸閉包一般用于異步函數(shù)的回調(diào),比如網(wǎng)絡(luò)請求成功的回調(diào)和失敗的回調(diào)。語法:在函數(shù)的閉包行參前加關(guān)鍵字“@escaping”。
  //逃逸閉包
  //一下函數(shù)模仿了網(wǎng)絡(luò)請求,主線成刷新UI的功能,此時的閉包類似OC中的Block
        var comletionHandle: ((String) -> Void) = {
            str in
            print("逃逸閉包執(zhí)行完畢")
        };
        
        func loadData(completion:@escaping (String)->()) {
            print("當(dāng)前線程\(Thread.current))")

            DispatchQueue.global().async {
                //異步網(wǎng)絡(luò)請求//開啟一個異步線程
                print("當(dāng)前線程\(Thread.current))")
                
                Thread.sleep(forTimeInterval: 3)
                
                let json = "JSON"
                
                DispatchQueue.main.async {
                    //回到了主線程
                    print("當(dāng)前線程\(Thread.current)");
                    
                    completion(json)//函數(shù)結(jié)束后才去調(diào)用的閉包這個就是逃逸閉包
                }
                
                print("異步線程執(zhí)行結(jié)束")
            }

            print("loadData函數(shù)return")
        }
//1.傳入的閉包參數(shù)是定義好的閉包變量
        loadData(completion: comletionHandle);
/*2.傳入的參數(shù)是尾隨閉包
 loadData{
//這也是逃逸閉包
   str in
   print("逃逸閉包執(zhí)行完畢")
 };
*/
控制臺執(zhí)行結(jié)果

可以看到loadData先return了,此時comletionHandle還沒有執(zhí)行,所以comletionHandle就叫做逃逸閉包,它逃離了loadData的生命周期。

自動閉包

語言解釋完全沒有看代碼理解的快,直接上代碼


func test(_ closure: ()-> Bool){
    if closure(){
        print("the result is true")
    }
    
}
//1直接調(diào)用方法
test({ () -> Bool in
    return 2 > 1
})
//2省略參數(shù)說明
test({ return 2 > 1 })
//3:使用尾部閉包方式,閉包體在圓括號之外
test(){ return 2 > 1 }
//4:在 Swift 中對閉包的用法可以進行一些簡化,在這種情況下我們可以省略掉 return,寫成:
test(){ 2 > 1})
//5:還可以更近一步,因為這個閉包是最后一個參數(shù),所以可以使用尾隨閉包 (trailing closure) 的方式把大括號拿出來,然后省略括號,變成:
test{2 > 1}

上面的代碼是開始就講過的 - 尾隨閉包。但是test{2 > 1}總是看起來別扭,因為方法的調(diào)用不是 test()這種嗎?自動閉包就是實現(xiàn)把test{}轉(zhuǎn)變成test()

//////自動閉包autoclosure 標(biāo)記自動閉包
func test(_ closure: @autoclosure ()-> Bool){
    if closure(){
        print("the result is true")
    }
}

test(2>1)
//Swift 將會把 2 > 1 這個表達式自動轉(zhuǎn)換為 () -> Bool。這樣我們就得到了一個寫法簡單,表意清楚的式子。

當(dāng)閉包作為函數(shù)參數(shù)時,可以將參數(shù)標(biāo)記 @autoclosure 來接收自動閉包。 @autoclosure 暗含了非逃逸閉包的特性,如果你想讓這個自動閉包具有逃逸的特性需要更改標(biāo)記為 @autoclosure(escaping)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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