Block 那些事
概念
Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C语言的扩展,用来实现匿名函数的特性
闭包是一个能够访问其他函数内部变量的函数.
基本语法
^(returnType)(argsList)
body
}
returnType
可以省略,argsList
如果没有可以省略,最简单的block语法形式如:
^
body;
}
定义一个无参,返回值为void
的block。
如果定义一个block变量,则语法形式为:
returnType (^blockVar)(argsList)
body;
}
例如:
int (^blockVar)(NSString* arg)
return @“Hello World”;
}
上述定义一个 返回值为整数,带有一个字符串参数的blockVar
变量。
为了方便使用常常使用typedef
给block类型定义一个别名,如
typedef int (^returnIntBlock)(NSSstring *arg);
returnIntBlock blockVar;
此时returnIntBlock
就代表反回值为整数参数为字符串的block类型。blockVar 代表 该block类型的一个变量
。
基本用法
-
定义普通变量。
//定义一个返回值为整型,带有两个整形参数的block变量 NSInteger (^returnIntVar)(NSInteger, NSInteger); //给block变量赋值 returnIntVar = ^NSInteger(NSInteger left, NSInteger right){ return left + right; };
-
定义属性。
@property (nonatomic, copy) NSInteger (^returnIntBlock)(NSInteger, NSInteger);
-
block作为方法参数。
- (void)methodWithBlockArg:(NSInteger (^)(NSInteger, NSInteger))blockArg{ NSInteger sum ; sum = blockArg(3, 4); }
-
block作为方法的返回值。
- (void (^)(NSString *arg))methodReturnBlock{return ^void(NSString *arg){ NSLog(@"arg: %@", arg); }; }
-
block作为函数的返回值。
void (^functionReturnBlock(NSString *arg))(NSString *arg){return ^void(NSString *arg){ NSLog(@"arg: %@", arg); }; }
block注意事项
修改引用的外部变量。
默认情况下block内部是不能修改应用变量的值的,若要修改,需在定义外部变量时使用__block
关键字修饰。
__block NSInteger a = 3;
void(^blockOp)() = ^(){
a = 5;
};
blockOp();-
循环引用。最常见的情况是对象拥有block,block内部又去访问对象的属性,这时,在block外面定义一个弱引用。如下
__weak typeof(self) weakSelf = self;
self.returnIntBlock = ^(NSInteger left, NSInteger right){weakSelf.sum = left + right ; return weakSelf.sum; };
block原理
为了研究编译器是如何实现 block 的,我们需要使用 clang。clang 提供一个命令,可以将 Objetive-C 的源码改写成 c 语言的,借此可以研究 block 具体的源码实现方式。该命令是:
clang -rewrite-objc block.c
我们先新建一个名为 block1.c 的源文件:
include <stdio.h>
int main()
{
int a = 100;
void (^block2)(void) = ^{
printf("%d\n", a);
};
block2();
return 0;
}
然后在命令行中输入:
clang -rewrite-objc block1.c
如果成功,会在当前目录下生成一个block.cpp
的源文件。该文件中的关键代码如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("%d\n", a);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
int a = 100;
void (*block2)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);
((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
return 0;
}
其中:
isa 指针,所有对象都有该指针,用于实现对象相关的功能。
flags,用于按 bit 位表示一些 block 的附加信息。
reserved,保留变量。
funcPtr,函数指针,指向具体的 block 实现的函数调用地址。
desc, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
-
a,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。
我们修改上面的源码,在变量前面增加__block
关键字:
include <stdio.h>int main()
{
__block int i = 1024;
void (^block1)(void) = ^{
printf("%d\n", i);
i = 1023;
};
block1();
return 0;
}
生成的关键代码如下,可以看到,差异相当大:
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref
printf("%d\n", (i->__forwarding->i));
(i->__forwarding->i) = 1023;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main()
{
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024};
void (*block1)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344);
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
return 0;
}
源码中增加一个名为 __Block_byref_i_0
的结构体,用来保存我们要 capture 并且修改的变量 a。
main_block_impl_0
中引用的是 Block_byref_i_0
的结构体指针,这样就可以达到修改外部变量的作用。
对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。
对于用
__block
修饰的外部变量引用,block 是复制其引用地址来实现访问的。