ios Block

何为Block

Block 是iOS在4.0之后新增的程式语法,严格来说block的概念并不算是基础程式设计的范围,对初学者来说也不是很容易了解,但是在iOS SDK 4.0之后,block几乎出现在所有新版的API之中,换句话说,如果不了解block这个概念就无法使用SDK 4.0版本以后的新功能,因此虽然block本身的语法有点难度,但为了使用iOS的新功能我们还是得硬着头皮去了解这个新的程式概念。

操蛋的Block语法

Block 首先复制操蛋的Block语法上面的代码
1、作为一个本地变量(local variable)

returnType (^blockName)(parameterTypes) = ^returnType(parameters) {…};

2、作为@property

@property (nonatomic, copy) returnType (^blockName)(parameterTypes);

3、作为方法的参数(method parameter)

- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;

4、作为方法参数的时候被调用

[someObject someMethodThatTakesABlock: ^returnType (parameters) {...}];

5、使用typedef来定义block,可以事半功倍

typedef returnType (^TypeName)(parameterTypes); TypeName blockName = ^returnType(parameters) {…};

这篇可能会比较长,先列个提纲

  • 一个最简单的 Block
  • 有参数无返回值的 Block
  • 有参数有返回值的 Block
  • Block 中的 typedef
  • Block 到底(比函数)优越在哪里
  • Block 的进阶使用

Objective-C 的 Block 非常神奇,相当于一个类,里面保存的是一段代码(代码块),别的语言可能还没有这个特性,同时,Apple 官方也推荐开发者使用 Block 来写程序。

一个最简单的 Block

Block 跟函数挺像的,不过用法比函数要高大上。举个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 函数是这样写
void blockTest() {
NSLog(@"Hello, World!");
}

// 这样调用
blockTest();

// block 这样写
void (^blockTest)() = ^{
NSLog(@"Hello, World!");
};

// 这样调用
blockTest();

两个调用是完全一样的,不过 Block 的声明有点难理解,来分析下

void 说明函数没有返回值,跟函数的写法完全一样的,

(^blockTest) 拆成三部分,blockTest 是 Block 的名字,前面加一个^说明这是个 Block 类型,然后再用括号括住,这是固定写法,

() 说明无参数,

^{} 这个就是 Block 内容的固定写法了,当然前提是没有参数,另一种情况稍后补充慢慢来~

有参数无返回值的 Block

1
2
3
4
5
6
7
8
void (^blockTest)(int, NSString *) = ^(int count, NSString *word){

for (int i = 0; i<count; i++) {
NSLog(@"%@", word);
}
};

blockTest(5, @"Hello, World!");

大括号内的东西大概都明白,不再细说,上面提到,() 代表无参数,那么这次里面有一个 int, NSString *,说明有两个类型的参数,后面原本是^{},现在中间多了个 (),道理同上。

有参数有返回值的 Block

1
2
3
4
5
6
int (^sum)(int, int) = ^(int x, int y){

return x + y;
};

NSLog(@"%d", sum(13, 14));

这个其实我不想多说了,以大家聪明的程度肯定自己能看懂。

Block 中的 typedef

大家都知道 typedef 可以这么用

typedef int BlockInt;

相同的,可以推理到 Block 中

typedef int (^MyBlock)(int);

注意写法,跟普通的不太一样

之后想用的时候,可以直接把 MyBlock 当成一个类型,举个栗子

1
2
3
4
5
MyBlock square = ^(int x){  
return x * x;
};

square(6);

Block 到底(比函数)优越在哪里

1
2
3
4
void getSomeInfo(NSString *info) {

NSLog(@"%@", info);
}

用函数写的话,可以这么玩,传入一个已知类型的变量,由这个函数来决定如何处置变量。

我们写程序讲究低耦合,这个函数关心的东西越少越好,这么做管的太宽了,但是我们有 Block,来看一个神奇的效果:

1
2
3
4
5
6
void getSomeInfo(void(^blockFunc)()) {

NSLog(@"Before Block");
blockFunc();
NSLog(@"After Block");

}

main 函数中

1
2
3
4
getSomeInfo(^{

NSLog(@"%@", @"You can do anything here");
});

结果是

1
2
3
2015-05-21 22:52:23.498 Block-Test[5427:277676] Before Block  
2015-05-21 22:52:23.499 Block-Test[5427:277676] You can do anything here
2015-05-21 22:52:23.499 Block-Test[5427:277676] After Block

当然这么搞不太严谨的,如果传进去一个空,程序会报错(坏访问),解决方法也很简单,判断一下是否为空即可,我就不写了

Block 的进阶使用

1.修改外部变量的时候,外部变量需要加一个 __block 来修饰

__block int a = 50;

2.为什么加上 __block 就能修改了

先贴 main.m 的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
// main.m
// Block-Test
//
// Created by JieLee on 15/5/21.
// Copyright (c) 2015年 PUPBOSS. All rights reserved.
//

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
@autoreleasepool {

__block int a = 10;

void (^blockTest)() = ^{

a = 50;
NSLog(@"%d", a);
};

blockTest();

}
return 0;
}

在 Terminal 中,输入 clang -rewrite-objc main.m,居然转换了 10 万多行 C++ 代码,拉到最下面分析

有一段是这样的

1
2
3
4
5
6
7
8
9
10
11
int main(int argc, const char * argv[]) {  
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};


void (*blockTest)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);

((void (*)(__block_impl *))((__block_impl *)blockTest)->FuncPtr)((__block_impl *)blockTest);

}
return 0;
}

来分析下,一般括号内的东西是强转,那就好办了,重点看这句

1
void (*blockTest)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);

删掉各种括号,就变成了

1
void (*blockTest)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &a, 570425344);

__main_block_impl_0 应该是一个类,或者结构体,我们往上找,还真有一个

1
2
3
4
5
6
7
8
9
10
11
struct __main_block_impl_0 {  
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

C++ 的结构体比较有意思,他可以调用自己的 __main_block_impl_0函数,然后把需要的东西传进去,最后 Desc = desc; 返回一个结构体。

再转回来&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA,&a,570425344); 是什么意思呢,返回一个结构体,然后取地址,所以 block 的本质,就是一个结构体!

至于为什么能改变 a 的值,我贴段代码,大家自己理解吧,我是有点蒙。反正就是各种地址各种传进去,然后就把值给改了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};

struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref


(a->__forwarding->a) = 50;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_f__kvyhp79j5wbcb2d7t24v3v_c0000gn_T_main_f979f0_mi_2, (a->__forwarding->a));
}

文章最后有完整的代码,想研究的可以去pupboss的github

文章目录
  1. 1. 何为Block
    1. 1.1. 操蛋的Block语法
    2. 1.2. 一个最简单的 Block
    3. 1.3. 有参数无返回值的 Block
    4. 1.4. 有参数有返回值的 Block
    5. 1.5. Block 中的 typedef
    6. 1.6. Block 到底(比函数)优越在哪里
    7. 1.7. Block 的进阶使用