{ (parameters) -> returnType in
statements
}複製代碼
這裏的參數(parameters),能夠是in-out(輸入輸出參數),但不能設定默認值。若是是可變參數,必須放在最後一位,否則編譯器報錯。元組也能夠做爲參數或者返回值。
"in"關鍵字表示閉包的參數和返回值類型定義已經完成,閉包函數體即將開始。即由in引入函數。api
閉包有三種形式:安全
閉包表達式的優勢:bash
簡單實例:閉包
let sum = {(a: Int, b: Int) -> Int in
return a + b
}
let s = sum(10, 15)
print(s)
// 從上下文中推導參數和返回值類型
let sum1 = { (a, b) -> Int in
return a + b
}
print(sum1(25, 25))複製代碼
概括
閉包類型是由參數類型和返回值類型決定的,和函數是同樣的。閉包的類型是(parameters) -> returnTypeide
Swift使用typealias關鍵字能夠爲類型起一個別名函數
typealias block = (Int, Int) -> Int
let sum: block = {(a, b) in
return a + b
}
print(sum(10, 15))複製代碼
閉包表達式的用法,以sorted方法使用爲例ui
// 尾隨閉包
let num = [1, 2, 3, 4, 5, 6, 7]
let sortNum = num.sorted(by: {
(a: Int, b: Int) -> Bool in
return a > b
})
print("第一次排序: \(sortNum)")
// 從上下文中推導參數和返回值類型
let sortNum1 = sortNum.sorted(by: {
(a, b) in
return a < b
})
print("第二次排序: \(sortNum1)")
// 單表達式的閉包隱式返回,能夠省略return關鍵字
let sortNum2 = sortNum1.sorted(by: {
(a, b) in
a > b
})
print("第三次次排序: \(sortNum2)")
// 速記參數名,和隱式返回。Swift提供速記參數名關聯閉包參數(須要省略參數列表,並根據預期的函數類型推斷速記參數名的數量和類型),從$0開始
let sortNum3 = sortNum2.sorted(by: {
$0 < $1
})
print("第四次次排序: \(sortNum3)")
// sorted方法還可使用函數進行排序
func sortNumArr(a: Int, b: Int) -> Bool
{
return a > b
}
let sortNum4 = sortNum3.sorted(by: sortNumArr)
print("第五次次排序: \(sortNum4)")
/*
輸出結果
第一次排序: [7, 6, 5, 4, 3, 2, 1]
第二次排序: [1, 2, 3, 4, 5, 6, 7]
第三次次排序: [7, 6, 5, 4, 3, 2, 1]
第四次次排序: [1, 2, 3, 4, 5, 6, 7]
第五次次排序: [7, 6, 5, 4, 3, 2, 1]
*/複製代碼
sorted的使用,也能夠將閉包寫在括號以外。例如:spa
let num = [1, 2, 3, 4, 5, 6, 7]
let sortNum = num.sorted() {
(a: Int, b: Int) -> Bool in
return a > b
}
print("第一次排序: \(sortNum)")複製代碼
若將閉包做爲函數參數最後一個參數,能夠省略參數標籤,將閉包表達式寫在函數調用括號後面指針
func sum(by: (Int, Int) -> Int) -> Int {
return by(10, 20)
}
let num = sum() {
(a, b) in
return a + b
}
print("和爲 \(num)")
func sum1(a: Int, b: Int, by: (Int, Int) -> Int) -> Int{
return by(a, b)
}
let num1 = sum1(a: 10, b: 30) {
(a, b) in
return a + b
}
print("和爲 \(num1)")複製代碼
閉包能夠在其定義的上下文中捕獲常量和變量。及時定義這些常量和變量的原始範圍再也不存在了,閉包也能夠引用並修改這些常量和變量的值。code
func sjzFunc(a: Int) -> () -> Int {
var sjz = 0
func addSjz() -> Int {
sjz += a
return sjz
}
addSjz()
print("sjz的值爲:\(sjz), a的值爲:\(a)")
return addSjz
}
let sjzF = sjzFunc(a: 100)
print("值爲:\(sjzF())")
print("值爲:\(sjzF())")
print("值爲:\(sjzF())")
print("值爲:\(sjzF())")
/*
sjz的值爲:100, a的值爲:100
值爲:200
值爲:300
值爲:400
值爲:500
*/複製代碼
func sjzFunc(a: Int) -> () -> Int {
var sjz = 0
// 閉包
let addSjz: () -> Int = {
sjz += a
return sjz
}
addSjz()
print("sjz的值爲:\(sjz), a的值爲:\(a)")
return addSjz
}
let sjzF = sjzFunc(a: 100)
print("值爲:\(sjzF())")
print("值爲:\(sjzF())")
print("值爲:\(sjzF())")
print("值爲:\(sjzF())")
/*
sjz的值爲:100, a的值爲:100
值爲:200
值爲:300
值爲:400
值爲:500
*/複製代碼
能夠看出,當調用addSjz的時候,sjz的值也改變了
addSjz捕獲對變量sjz和參數a的引用,經過引用捕獲確保sjzFunc結束時,sjz和a不會消失。而且確保了下次調用sjzF時,sjz可用。當它們再也不須要時,Swift處理變量的全部內存管理。
函數和閉包是引用類型。也就是說,當給變量和常量賦值閉包或函數時,變量和常量被賦值爲閉包或函數的引用。
例如:
let sjzFF = sjzF // sjzFF和sjzF引用的是同一個閉包複製代碼
當閉包做爲參數傳遞給函數時,閉包被稱爲轉義函數,在函數返回後調用。
聲明一個將閉包做爲一個參數的函數時,能夠在參數類型以前編寫@escaping,以指示容許閉包被轉義。
閉包能夠逃避的方法之一是存儲在函數外定義的變量中。
var sjzBlock: () -> Void = {
}
func someEscapingFunc(com: @escaping () -> Void){
print("執行函數")
sjzBlock = com
}
func someNoneFunc(com: () -> Void){
com()
}
class someClass {
var x = 10
func doSomeThint() -> Void {
someEscapingFunc {
self.x = 20
}
someNoneFunc {
x = 30
}
}
}
let some = someClass()
some.doSomeThint()
print(some.x)
sjzBlock()
print(some.x)
/*
輸出
執行函數
30
20
*/複製代碼
用@escaping標記一個閉包意味着你必須在閉包中self引用。 例如,someEscapingFunc的閉包是一個轉義閉包,這意味着它須要self引用。 相反,封閉傳遞給someNoneFunc是一個非轉義的閉包,這意味着它能夠隱式引用。
@escaping和@non-escaping修飾符是用來修飾閉包的。閉包默認爲@non-escaping類型
@escaping:轉義閉包,閉包的生命週期不在傳入的函數範圍內管理。由函數外部變量持有。當函數結束以後,閉包纔去執行
@non-escaping:閉包在函數內執行完後,函數纔去執行,閉包銷燬。
當使用@escaping時,要考慮self被循環引用。
緣由參考OC的block,解決方法也相似。
Dog類
import Cocoa
class sjzDog: NSObject {
var eatFull: ((String) -> Void) = {(String) in
}
var food: String?
override init() {
super.init()
}
deinit {
print("狗掛了")
}
func eat(food: String, by: @escaping (String) -> Void) -> Void {
self.food = food
print("我吃了:\(food)")
eatFull = by
}
func eatFinish() -> Void {
eatFull(food!)
}
}複製代碼
People類
import Cocoa
class sjzPeople: NSObject {
let dog: sjzDog
var name: String?
init(name: String) {
self.name = name
dog = sjzDog()
super.init()
}
deinit {
print("人掛了")
}
func feed(food: String) -> Void {
// 循環應用
// 輸出:
// 我吃了:milk
// 我吃了milk, 我吃飽了,我想和主人Tom玩耍
// dog.eat(food: food) { (a) in
// print("我吃了\(a), 我吃飽了,我想和主人\(self.name!)玩耍")
// }
/*如下輸出:
我吃了:milk
我吃了milk, 我吃飽了,我想和主人Tom玩耍
人掛了
狗掛了
*/
// 1. 解決循環引用
// weak var weakSelf = self
// dog.eat(food: food) { (a) in
// print("我吃了\(a), 我吃飽了,我想和主人\(weakSelf!.name!)玩耍")
// }
// 2. 解決循環引用
// dog.eat(food: food) { [unowned self] (a) in
// print("我吃了\(a), 我吃飽了,我想和主人\(self.name!)玩耍")
// }
// 3. 解決循環引用
dog.eat(food: food) { [weak self] (a) in
print("我吃了\(a), 我吃飽了,我想和主人\(self!.name!)玩耍")
}
dog.eatFinish()
}
}複製代碼
調用
var people: sjzPeople? = sjzPeople.init(name: "Tom")
people!.feed(food: "milk")
people = nil複製代碼
循環引用緣由:
dog類中引用了閉包eatFull,people類中引用了dog類,閉包eatFull又引用了people,造成了閉環
在dog類中繼續聲明變量和方法:
var play: (() -> Void)?
func playOrFood() -> Void {
play = {
print("我要玩, 我不想吃 \(String(describing: self!.food))")
}
play!()
}複製代碼
在調用playOrFood方法後,將dog對象置空,dog對象不能釋放,造成了循環引用
緣由:
dog類引用了paly閉包,paly閉包又引用了dog類。解決方法和上面dog類和people互相引用相似
解決方法:
weak表示可用範圍內self爲弱引用unowned表示可用範圍內self都爲assign,不會強引用,若是對象釋放,指針地址依然存在,不安全