簡介
在函數式編程時,經常會使用到閉包。在很多編程語言中,都有類似閉包的概念。比如Objective-C,Ruby中有Block,C++ 11、LISP、Python和Java中有lambda表達式,Lua和JavaScript中有閉包。
和普通函數相比,Lambda、closure和block是一個東西,只是不同語言的不同稱呼,它們都是匿名函數。若匿名函數捕獲了一個外部變量,那么它就是一個closure。
用一句話來表示Blocks的擴充功能:帶有自動變量(局部變量)的匿名函數。顧名思義,所謂匿名函數就是不帶有名稱的函數。
作用
簡單說,引入它們的作用有2個:
簡潔
首先說簡潔,匿名函數可以在其他函數內部聲明與定義,不用另外寫個命名函數。比如一個方法只在這一處使用,我們就沒有必要創建方法然后調用,我們可以直接在其他函數內部聲明定義,這樣也無需在其他地方查找。
捕獲變量
捕獲變量就是讓匿名函數可以使用匿名函數外定義的變量,但是匿名函數內的函數外變量是外部變量的一個克隆。Objective-C 中有__block,也可以直接改變外部變量的值,
Objective-C 中的 block
下面從 OC 的角度理解一下 block,在《Objective-C高級編程》中詳細介紹了 block 的實現原理。我們可以利用clang這個工具將 block 代碼轉換成C++源碼來分析。我們在 main.m 文件中實現一段簡單的 block 代碼:
int main () {
void (^blk)() = ^() {
int a = 0;
};
blk();
}
接下來,我們使用 clang -rewrite-objc main.m 命令轉換為C++源碼:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// block 的結構體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block 實現的方法,impl.FuncPtr 指向該結構體
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = 0;
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
// main 函數
int main () {
// 創建了一個__main_block_impl_0結構體的一個實例
void (*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 通過函數指針 blk 調用函數 FnucPtr,傳入的參數為指針 blk 本身
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
從源碼中可以看到,block 本質是一個指針結構體,執行block,相當于執行block 里面 FnucPtr 里面的函數指針。
詳細的資料參考《Objective-C高級編程》或者blog https://www.cnblogs.com/chenxianming/p/5554395.html
其他語言使用
匿名函數、lambda、closure在各個語言中的使用方式:
Objective-C
在ObjectC中,匿名函數被稱為blocks(塊),即可以改變捕獲的原值、又可以捕獲克隆、但不能改變克隆值的值。捕獲并改變外部值,需要用__block,否則復制語句會報錯,使用代碼如下:
__block int a = 10;
int (^blockFunc)(int p) = ^(int p) {
a += p;
return a;
};
NSLog(@"blockFunc:%d, a:%d", blockFunc(4), a);
閉包可能會存在循環引用,需要使用弱引用.
MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler = ^(NSInteger result) {
MyViewController *strongMyController = weakMyController;
if (strongMyController) {
// ...
[strongMyController dismissViewControllerAnimated:YES completion:nil];
// ...
}
else {
// Probably nothing...
}
};
Swift
Swift 中的閉包與 C 和 Objective-C 中的代碼塊(blocks)以及其他一些編程語言中的匿名函數比較相似。
閉包可以捕獲和存儲其所在上下文中任意常量和變量的引用。被稱為包裹常量和變量。 Swift 會為你管理在捕獲過程中涉及到的所有內存操作。
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// 閉包表達式語法
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
// 根據上下文推斷類型
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
// 單表達式閉包隱式返回
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
// 參數名稱縮寫
reversedNames = names.sorted(by: { $0 > $1 } )
// 運算符方法
reversedNames = names.sorted(by: >)
閉包中也可能存在循環引用,Swift 提供了兩種辦法用來解決你在使用類的屬性時所遇到的循環強引用問題:弱引用(weak reference)和無主引用(unowned reference)。
弱引用和無主引用允許循環引用中的一個實例引用而另外一個實例不保持強引用。這樣實例能夠互相引用而不產生循環強引用。
- 如果捕獲(比如 self)可以被設置為 nil,也就是說它可能在閉包前被銷毀,那么就要將捕獲定義為 weak。
- 如果它們一直是相互引用,即同時銷毀的,那么就可以將捕獲定義為 unowned。
本質上來說,閉包作為一個引用類型,解決循環引用的方式和解決類對象之間的循環引用是一樣的,如果引起循環引用的"捕獲"不能為nil,就把它定義為unowned,否則,定義為weak。而指定“捕獲”方式的地方,叫做閉包的薄或列表。
Javascript
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
JavaScript中的函數會形成閉包。 閉包是由函數以及創建該函數的詞法環境組合而成。這個環境包含了這個閉包創建時所能訪問的所有局部變量。在我們的例子中,myFunc 是執行 makeFunc 時創建的 displayName 函數實例的引用,而 displayName 實例仍可訪問其詞法作用域中的變量,即可以訪問到 name。
Python
在Python中的匿名函數被稱為lambda,只能捕獲值,且不能改變值。以map()函數為例,計算f(x)=x^2時,除了定義一個f(x)的函數外,還可以直接傳入匿名函數:
list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
匿名函數lambda x: x * x實際上就是:
def f(x):
return x * x
匿名函數也是一個函數對象,也可以把匿名函數賦值給一個變量,再利用變量來調用該函數:
f = lambda x: x * x
同樣,也可以把匿名函數作為返回值返回,比如:
def build(x, y):
return lambda: x * x + y * y