尊重知識,轉發請注明出處:Swift閉包實戰
概要
接觸過Swift的小伙伴對“閉包”應該不陌生,相當于OC中的Block,是Swift語法中比較難理解的一塊。
無論蘋果的官方文檔還是由官方文檔衍生出來的一些文章和書籍都比較重視基礎語法知識的講解,對于實戰中的應用提及的都很少,所以當我們想使用“閉包”解決一些問題的時候,會忽然出現看著一堆理論知識卻不知從何下手的尷尬感,這就是理論和時實戰的區別了。
本文不贅述Swift閉包的的基本語法了,百度或者Google下有很多資料,我平時會閱讀極客學院的官方翻譯文檔:閉包。如題所示本文著重講述Swift閉包的一些實戰案例,有需要的小伙伴可以參考下,經驗豐富的大神也請指教。
關于如何理解閉包
學習閉包的第一個難點就是理解閉包,可能很多人用了很久的閉包都還不太清楚閉包到底是什么,我這里提供一種理解思路,僅供參考。
對于很多iOS開發者來說一開始接觸到Swift閉包會試圖用OC中的Block去理解,當然這會對我們的理解有一定幫助,就好比很多人學習英語:tomato->西紅柿->??,而不是tomato-> ??,而一個嬰兒剛開始接觸語言時候就是直接由tomato的發音聯想到??,而后再去學習??的單詞拼寫,這是人類與生俱來的學習語言的邏輯流程,所以我們不妨按照這種思維邏輯去學習和理解Swift的閉包。
1、閉包是什么
這就好比一個嬰兒好奇西紅柿是什么,家長會將一個真實的西紅柿拿到他的面前,看一看,摸一摸,聞一聞,嘗一嘗。對于Swift的閉包,我們首先不需要知道是它在語法上如何定義的,而是要知道閉包的本質。
閉包的本質是代碼塊,它是函數的升級版本,函數是有名稱、可復用的代碼塊,閉包則是比函數更加靈活的匿名代碼塊。
2、為什么需要閉包
當一個嬰兒知道了西紅柿是什么,自然而然就會想到西紅柿有什么用,那么我們自然也會問閉包在Swift中有何用處呢?
函數已經可以滿足我們開發中大部分的需求了,那么為什么還需要閉包呢。在開發中我們經常需要傳遞各種數據,我們習慣了傳遞一個值:Int,一串符號:String,一個對象:Class,但是有時我們需要傳遞一種處理問題的邏輯,我們常用的類型似乎滿足不了這種需求,而函數恰好是一種處理問題的邏輯,為了讓函數像Int、Float、String等常用類型一樣靈活的傳遞和調用,閉包就出現了。
綜上所述,我們可以知道閉包本質上和函數一樣都是代碼塊,而閉包更加靈活。
閉包、嵌套函數、函數
更好地使用閉包前需要理清3者的聯系和區別
首先看3種函數的定義:
//函數
func eatTomatos(a: Int, b: Int) -> Int {
return a + b
}
//嵌套函數
func eatTomatos(a: Int, b: Int) -> Int {
//嵌套函數
func digest(a: Int, b: Int) -> Int {
return 2 * a + b
}
return digest(a: a, b: b)
}
//閉包
var eatTomatos = {(a: Int, b: Int) -> Int in
return a + b
}
從上面的定義可以看出函數和嵌套函數其實是一回事,唯一的區別是,嵌套函數是定義在一個函數內部的函數,對外部是隱藏的,只能在其定義的函數內部有效。而閉包與函數的不同要多一些:1、不需要使用func關鍵字,2、其次函數有名稱如:eatTomatos,而閉包是沒有名稱的,3、閉包的參數和函數體都要使用{ }包起來,在參數后要使用in關鍵字連接函數體,4、閉包可以作為一種類型賦值給一個變量,上面代碼中的閉包類型是:(Int, Int) -> Int。
上面從定義上分析了3者的不同,下面從功能上區分下。
1、函數是全局的,不能捕獲上下文中的變量;而嵌套函數和閉包可以直接嵌套在上下文中使用的,因此可以捕獲上下文中的變量,需要注意的是每一個閉包都只會持有一個它所捕獲的變量的副本,如下:
override func viewDidLoad() {
super.viewDidLoad()
print(eatTomatos(a: 1, b: 2))//③
print(eatTomatos(a: 2, b: 3))//④
}
func eatTomatos(a: Int, b: Int) -> Int {
var numArray: Array<Int> = Array.init()
//嵌套函數
func digest(a: Int, b: Int) -> Int {
numArray.append(a)
numArray.append(b)
print(numArray.count)//②
return 2 * a + b
}
print(numArray.count)//①
return digest(a: a, b: b)
}
//打印的結果依次(①②③④)是:
0
2
4
0
2
7
2、閉包可以作為參數或者返回值,如下:
// 作為參數
override func viewDidLoad() {
super.viewDidLoad()
cookTomates { (a, b) in
print(a)
print(b)
}
}
func cookTomates(tomato: (Int, Int) -> Void){
tomato(1, 2)
}
cookTomates函數將閉包(Int, Int) -> Void作為參數,并且可以在函數內部操作這個閉包 在調用cookTomates函數式需要將給這個閉包參數賦值,并且閉包中的參數名需要調用的時候自行命名。
//作為返回值
override func viewDidLoad() {
super.viewDidLoad()
let tomato = gainTomatos()
print(tomato(2, 3))
}
var eatTomatos: (Int, Int) -> Int = {(a: Int, b: Int) -> Int in
return a + b
}
func gainTomatos() -> (Int, Int) -> Int {
return eatTomatos
}
函數gainTomatos將閉包(Int, Int) -> Int作為返回值,這里返回的是(Int, Int) -> Int的一個實例,調用者便可以利用返回的實例獲取(Int, Int) -> Int閉包處理參數的邏輯,實現代碼的傳遞和復用
為你的閉包類型起別名
閉包類型不像其他常用類型看起來比較簡潔,有參數、返回值、關鍵字、符號構成,影響閱讀和糾錯,因此為常用的閉包類型起一個別名很有必要。
如下,為(Int, Int) -> Int閉包類型起別名
typealias Tomato = (Int, Int) -> Int
因此上面閉包當做返回值使用的代碼便可以改寫如下:
override func viewDidLoad() {
super.viewDidLoad()
let tomato = gainTomatos()
print(tomato(2, 3))
}
var eatTomatos: Tomato = {(a: Int, b: Int) -> Int in
return a + b
}
func gainTomatos() -> Tomato {
return eatTomatos
}
當我們把(Int, Int) -> Int類型抽象為Tomato后,不僅僅是代碼看起來更加簡潔,也更接近我們使用的其他參數類型,更加便于理解
閉包傳值
OC中常用的傳值方法有代理、Block、通知等,對應到Swift Block就由閉包替代。
如下需要使用閉包將B中的a、b值傳遞到A中
override func viewDidLoad() {
super.viewDidLoad()
let a: A = A()
a.fromB()
}
typealias Tomato = (Int, Int) -> Int
class A: NSObject {
let b: B = B()
func fromB() {
b.tomato = {
(x, y) -> Int in
return x + y
}
print(b.toA())
}
}
class B: NSObject {
var tomato: Tomato?
func toA() -> Int {
let a = 3
let b = 4
return tomato!(a, b)
}
}
由上可以總結出閉包傳值的流程: 1??首先為自己的閉包類型起一個別名,便于使用; 2??在需要把值傳遞給另外一個對象的類里聲明一個閉包類型的變量,對應到上面的代碼中就是B; 3??在需要接收值的類里為閉包類型賦值,從而在此閉包內便可以獲取傳遞的值。
注意: 這里著重描述傳值的流程,在開發的時候還需判斷閉包是否為nil,否則會導致崩潰;
閉包作為參數傳值
在使用AFN或者SDWebImage的時候,通過Block獲取請求的數據很方便,那么在Swift中如何使用閉包實現這種效果呢。
其實上面在說閉包作為參數使用的時候,已經實現了這種傳值的方式,這里舉另外一個例子,我們在使用第三方庫的時候通常會將其再封裝一次,避免由于第三方庫不維護或者出現較大更新的時候增加不必要的工作量,這里以簡單封裝Alamofire為例,代碼如下:
import UIKit
import Alamofire
import SwiftyJSON
class ZYLResponse: NSObject {
//接收數據是否成功
var isSuccess: Bool = false
//接收到的字典數據
var dict: Dictionary<String, Any>?
//接收到的數組數據
var array: Array<Any>?
//錯誤信息
var error: Error?
//JSON
var json:JSON?
}
typealias DataReply = (ZYLResponse) -> Void
class ZYLNetTool: NSObject {
///POST請求
open static func post(url: String, parameters: Dictionary<String, Any>?, complete: @escaping DataReply) {
Alamofire.request(url, method: .post, parameters: parameters).responseJSON { (response) in
let myResponse = ZYLResponse()
myResponse.isSuccess = response.result.isSuccess
myResponse.dict = response.result.value as! Dictionary<String, Any>?
myResponse.array = response.result.value as? Array<Any>
myResponse.error = response.result.error
myResponse.json = JSON(data: response.data!)
complete(myResponse)
}
}
///GET請求
open static func get(url: String, parameters: Dictionary<String, Any>?, complete: @escaping DataReply) {
Alamofire.request(url, method: .get, parameters: parameters).responseJSON { (response) in
let myResponse = ZYLResponse()
myResponse.isSuccess = response.result.isSuccess
myResponse.dict = response.result.value as! Dictionary<String, Any>?
myResponse.array = response.result.value as? Array<Any>
myResponse.error = response.result.error
myResponse.json = JSON(data: response.data!)
complete(myResponse)
}
}
}
//調用
ZYLNetTool.post(url: HTTP_ACTIVATE_PORT, parameters: paraDict, complete: { (response) in
if response.isSuccess {
//請求數據成功
} else {
//請求數據失敗
}
})
注意: 1、使用閉包時要注意管理內存; 2、當作閉包為函數參數使用時可以脫離函數獨立使用時,要將此閉包聲明為逃逸閉包,在參數類型前面加上@escaping,否則會報錯。
尾巴
接觸一種新的事物之前總會覺得很難,當我們學會后發現其實很簡單,難的不是這個新事物本身,而是我們的大腦出于習慣很難接受新的事物,總是需要一定的過程。記得學C語言時很難理解指針,學C++時很難理解面向對象,學OC時很難理解Block,而Swift作為一種新的語言,必然會有很多新的事物讓我們難以理解,比如閉包、元組、可選類型、函數式編程等等,本文只對閉包發表一點拙見,還望指正,謝謝。