從12年底開始接觸C,斷斷續續學習了大半年,直到13年中才直接跳入OC的學習。所以很長一段時間里對閉包的認識只限于OC的Block。但直到Swift的出生,還有在學習其它語言的時候發現OC的Block是一個異類,拿它作為標準來使用其它語言上的閉包是很容易出現無法理解的行為的。
舉個簡單的例子,如下代碼,異步延時執行代碼
void countdown() {
int i = 5;
NSLog(@"countdown");
while (i >= 0) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(i * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"i===%d", 5 - i);
});
i--;
}
}
// countdown
// i===5
// i===4
// i===3
// i===2
// i===1
// i===0
對于只學過OC的朋友看來這里并沒有什么特別的問題,但從JS,Python等跳過來的人就發現有點無法理解了。用JS來舉個例子,而且比較經典,在學習異步的時候教科書都會拿這個例子來說明問題。
function countdown() {
let i;
console.log("Countdown");
for (i = 5; i >= 0; i--) {
setTimeout(function () {
console.log(5 - i);
}, (5-i)*1000);
}
}
countdown();
// Countdown
// 6
// 6
// 6
// 6
// 6
// 6
剛從OC跳過去的時候還想不明白這是為什么。難道這JS是一個異類?其實不然,當自身在找認同感的時候我轉向了Swift。
func countdown() {
var i = 5.0
print("countdown")
while i >= 0 {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + i, execute: {
print("i===\(5 - i)")
})
i = i - 1
}
}
// countdown
// i===6.0
// i===6.0
// i===6.0
// i===6.0
// i===6.0
// i===6.0
在看到這樣的結果的時候自身受到一些打擊,因為以前學的一些概念是不完全正確的,幸運的是我沒有用swift進行工作上的開發,也許可以說不幸,如果用了就會遇到這種問題然后就會更早的糾正這些錯誤的概念。
OC的Block我想大家都已經很熟識了,甚至連底層實現都了如指掌的程度。在默認的情況下,在Block里引用外部的變量,block會將該變量的值拷貝一份并將其設為不可寫。在block調用前改變了變量的值是不會影響到Block內部經過拷貝的變量的值,因為本身是兩個不同的變量。如果OC要實現其它語言的行為則需要在變量聲明前加個__block修飾。這個對于那些看過block實現原理的人來說不用再多廢話敘述了。不了解的話可以搜一下就能找到答案。
這里給出一些驗證,分別對應OC,Swift,JS,Python
void test() {
int value = 10;
void(^block)() = ^{ NSLog(@"%d", value); };
value++;
block();
}
// 10
func test() {
var value = 10
let closure = { print(value) }
value += 1
closure()
}
// 11
function test() {
var value = 10;
var closure = function () {
console.log(value);
}
value++;
closure();
}
// 11
def test():
value = 10
def closure():
print(value)
value = value + 1
closure()
// 11
最后難道無法在swift上或JS上解決這個倒計時問題嗎?當然不是了,答案如下,Swift是直接參考了JS的實現。
func countdown() {
var i = 5.0
print("countdown")
while i >= 0 {
{ _i in
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + i, execute: {
print("i===\(5 - _i)")
})
}(i)
i = i - 1
}
}
function countdown() {
let i;
console.log("Countdown");
for (i = 5; i >= 0; i--) {
(function (_i) {
setTimeout(function () {
console.log(5 - _i);
}, (_i)*1000);
}(i))
}
}
在JS里有一種叫做Immediately-Invoked Function Expression (IIFE) 立即執行函數。實際上就是一個構造出一個匿名函數然后立即調用它。當然的是在ES6后加了let作為變量的修飾詞后它的作用域就起了變法,所以實際上JS有一個更簡單的解法。
function countdown() {
console.log("Countdown");
for (let i = 5; i >= 0; i--) {
setTimeout(function () {
console.log(5 - i);
}, (i)*1000);
}
}
同樣在swift也可以這樣
func countdown() {
print("countdown")
for i in 0...5 {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(i), execute: {
print("i===\(5 - i)")
})
}
}