何为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 | // 函数是这样写 |
两个调用是完全一样的,不过 Block 的声明有点难理解,来分析下
void
说明函数没有返回值,跟函数的写法完全一样的,
(^blockTest)
拆成三部分,blockTest
是 Block 的名字,前面加一个^
说明这是个 Block 类型,然后再用括号括住,这是固定写法,
()
说明无参数,
^{}
这个就是 Block 内容的固定写法了,当然前提是没有参数,另一种情况稍后补充慢慢来~
有参数无返回值的 Block
1 | void (^blockTest)(int, NSString *) = ^(int count, NSString *word){ |
大括号内的东西大概都明白,不再细说,上面提到,()
代表无参数,那么这次里面有一个 int, NSString *,说明有两个类型的参数,后面原本是^{}
,现在中间多了个 ()
,道理同上。
有参数有返回值的 Block
1 | int (^sum)(int, int) = ^(int x, int y){ |
这个其实我不想多说了,以大家聪明的程度肯定自己能看懂。
Block 中的 typedef
大家都知道 typedef 可以这么用
typedef int BlockInt;
相同的,可以推理到 Block 中
typedef int (^MyBlock)(int);
注意写法,跟普通的不太一样
之后想用的时候,可以直接把 MyBlock 当成一个类型,举个栗子1
2
3
4
5MyBlock square = ^(int x){
return x * x;
};
square(6);
Block 到底(比函数)优越在哪里
1 | void getSomeInfo(NSString *info) { |
用函数写的话,可以这么玩,传入一个已知类型的变量,由这个函数来决定如何处置变量。
我们写程序讲究低耦合,这个函数关心的东西越少越好,这么做管的太宽了,但是我们有 Block,来看一个神奇的效果:1
2
3
4
5
6void getSomeInfo(void(^blockFunc)()) {
NSLog(@"Before Block");
blockFunc();
NSLog(@"After Block");
}
main 函数中1
2
3
4getSomeInfo(^{
NSLog(@"%@", @"You can do anything here");
});
结果是1
2
32015-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.
//
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
11int 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 | struct __main_block_impl_0 { |
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