Swift3之閉包

本篇簡書地址

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

閉包有三種形式:

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

捕獲值

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

全局函數

什麼叫全局函數?全局函數不能捕獲值?啥意思呢?一陣懵逼。

let value = 1;

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

testGlobalFunc()

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

//
//  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
}


現在就可以直接調用kScreenHeighFun了。這就是全局函數,不需要類或對象調用,就和全局變量kScreenWidth一樣
有人會問kScreenWidthFun不是捕獲了kScreenWidth嗎?我認爲全局變量是跟隨項目的生命週期的,所以這裏只是調用,不能算捕獲/。

閉包表達式

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

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

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

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

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

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


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


//這個是既沒有參數也沒返回值,所以把returnin都省略了
let add6:()->Void = {print("15")}
add6()
//inout參數,不太常用,因爲我們直接使用外部值就行,沒必要再作爲參數傳遞一下

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))

閉包類型

可以用關鍵字“typealias”聲明一個閉包數據類型。類似於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)")

尾隨閉包

若將閉包作爲函數最後一個參數,可以省略參數標籤,然後將閉包表達式寫在函數調用括號後面。尾隨閉包讓代碼更簡潔

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

逃逸閉包

  • 當一個閉包作爲參數傳到一個函數中,需要這個閉包在函數執行結束之後才被執行,我們就稱該閉包從函數中逃逸。一般如果閉包在函數體內涉及到異步操作,但函數卻是很快就會執行完畢並返回的,閉包必須要逃逸掉,以便異步操作的回調。
  • 逃逸閉包一般用於異步函數的回調,比如網絡請求成功的回調和失敗的回調。語法:在函數的閉包行參前加關鍵字“@escaping”。
  //逃逸閉包
  //一下函數模仿了網絡請求,主線成刷新UI的功能,此時的閉包類似OC中的Block
        var comletionHandle: ((String) -> Void) = {
            str in
            print("逃逸閉包執行完畢")
        };

        func loadData(completion:@escaping (String)->()) {
            print("當前線程\(Thread.current))")

            DispatchQueue.global().async {
                //異步網絡請求//開啓一個異步線程
                print("當前線程\(Thread.current))")

                Thread.sleep(forTimeInterval: 3)

                let json = "JSON"

                DispatchQueue.main.async {
                    //回到了主線程
                    print("當前線程\(Thread.current)");

                    completion(json)//函數結束後纔去調用的閉包這個就是逃逸閉包
                }

                print("異步線程執行結束")
            }

            print("loadData函數return")
        }
//1.傳入的閉包參數是定義好的閉包變量
        loadData(completion: comletionHandle);
/*2.傳入的參數是尾隨閉包
 loadData{
//這也是逃逸閉包
   str in
   print("逃逸閉包執行完畢")
 };
*/

控制檯執行結果
可以看到loadData先return了,此時comletionHandle還沒有執行,所以comletionHandle就叫做逃逸閉包,它逃離了loadData的生命週期。

自動閉包

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


func test(_ closure: ()-> Bool){
    if closure(){
        print("the result is true")
    }

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

上面的代碼是開始就講過的,尾隨閉包。但是test{2 > 1}總是看起來彆扭,因爲方法的調用不是 test()這種嗎?自動閉包就是實現從{}()的轉變

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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章