Objective-C的Block与其它语言上闭包的不同
从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)")
})
}
}