Objective-C的Block與其它語言上閉包的不同

從12年底開始接觸C,斷斷續(xù)續(xù)學(xué)習(xí)了大半年,直到13年中才直接跳入OC的學(xué)習(xí)。所以很長一段時間里對閉包的認識只限于OC的Block。但直到Swift的出生,還有在學(xué)習(xí)其它語言的時候發(fā)現(xiàn)OC的Block是一個異類,拿它作為標(biāo)準(zhǔn)來使用其它語言上的閉包是很容易出現(xiàn)無法理解的行為的。
舉個簡單的例子,如下代碼,異步延時執(zhí)行代碼

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

對于只學(xué)過OC的朋友看來這里并沒有什么特別的問題,但從JS,Python等跳過來的人就發(fā)現(xiàn)有點無法理解了。用JS來舉個例子,而且比較經(jīng)典,在學(xué)習(xí)異步的時候教科書都會拿這個例子來說明問題。

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是一個異類?其實不然,當(dāng)自身在找認同感的時候我轉(zhuǎn)向了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

在看到這樣的結(jié)果的時候自身受到一些打擊,因為以前學(xué)的一些概念是不完全正確的,幸運的是我沒有用swift進行工作上的開發(fā),也許可以說不幸,如果用了就會遇到這種問題然后就會更早的糾正這些錯誤的概念。

OC的Block我想大家都已經(jīng)很熟識了,甚至連底層實現(xiàn)都了如指掌的程度。在默認的情況下,在Block里引用外部的變量,block會將該變量的值拷貝一份并將其設(shè)為不可寫。在block調(diào)用前改變了變量的值是不會影響到Block內(nèi)部經(jīng)過拷貝的變量的值,因為本身是兩個不同的變量。如果OC要實現(xiàn)其它語言的行為則需要在變量聲明前加個__block修飾。這個對于那些看過block實現(xiàn)原理的人來說不用再多廢話敘述了。不了解的話可以搜一下就能找到答案。

這里給出一些驗證,分別對應(yīng)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上解決這個倒計時問題嗎?當(dāng)然不是了,答案如下,Swift是直接參考了JS的實現(xiàn)。

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) 立即執(zhí)行函數(shù)。實際上就是一個構(gòu)造出一個匿名函數(shù)然后立即調(diào)用它。當(dāng)然的是在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)")
        })

    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容