基本认识

  • block本质上也是一个OC对象,它内部也有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象
  • 以下是一个block:
    1
    2
    3
    4
    5
    6
    int age = 20;

    void (^block)(int, int) = ^(int a , int b){
    NSLog(@"this is a block! -- %d", age);
    };
    block()

底层数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};

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 age;
};

block变量捕获

  • 为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
变量类型 捕获到block内部 访问方式
局部变量 auto √ 值传递
局部变量 static √ 指针传递
全局变量 × 直接访问

代码示例:

auto和static局部变量捕获

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
30
31
32
33
34
35
36
37
38
39
40
void test {
auto int a = 10;
static int b = 10;
block = ^{
NSLog(@"age is %d, height is %d", a, b);
};
}

//prama mark: - 编译后的源码

struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int a;//值传递
int *b;//指针传递
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy,值传递
int *b = __cself->b; // bound by copy 指针传递

NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, a, (*b));
}

static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)};

void test()
{
auto int a = 10;
static int b = 10;
block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a, &b));
}

block类型

  • block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
    • NSGlobalBlock ( _NSConcreteGlobalBlock )(数据区)
    • NSStackBlock ( _NSConcreteStackBlock )(栈区)
    • NSMallocBlock ( _NSConcreteMallocBlock )(堆区)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Global:没有访问auto变量
void (^block1)(void) = ^{
NSLog(@"block1---------");
};

// Stack:访问了auto变量
int age = 10;
void (^block2)(void) = ^{
NSLog(@"block2---------%d", age);
};

// NSMallocBlock(MRC环境下)
//对stack类型的block进行copy操作,就会变成malloc类型的block
int age = 10;
block = [^{
NSLog(@"block---------%d", age);
} copy];
[block release];

block父类层级结构,如下:

1
2
3
4
5
6
7
8
9
// __NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
void (^block)(void) = ^{
NSLog(@"Hello");
};

NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);

block的copy

  • 在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
    • block作为函数返回值时
    • 将block赋值给__strong指针时
    • block作为Cocoa API中方法名含有usingBlock的方法参数时
    • block作为GCD API的方法参数时
  • MRC下block属性的建议写法
    1
    @property (copy, nonatomic) void (^block)(void);
  • ARC下block属性的建议写法
    1
    2
    @property (strong, nonatomic) void (^block)(void);
    @property (copy, nonatomic) void (^block)(void);

对象类型的auto变量

  • 当block内部访问了对象类型的auto变量时,如果block是在栈上,将不会对auto变量产生强引用
  • 如果block被拷贝到堆上,会调用block内部的copy函数
    • copy函数内部会调用_Block_object_assign函数
    • _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
  • 如果block从堆上移除,会调用block内部的dispose函数
    • dispose函数内部会调用_Block_object_dispose函数
    • _Block_object_dispose会自动释放引用的auto变量(release)

代码解析:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
typedef void (^Block)(void);
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;

//__weak Person *weakPerson = person;
block = ^{
NSLog(@"---------%d", person.age);
};
}

//编译后的源代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MJPerson *__strong person;//由于没有使用weakPerson,所以这里是strong
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
MJPerson *__strong person = __cself->person; // bound by copy

NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c41e64_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person,//对block进行一个引用计数的操作,外界是strong,retain就引用计数加1,外界是weak就是一个弱引用。
(void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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*);// 将栈上的bock,复制到椎上
void (*dispose)(struct __main_block_impl_0*);//堆上的block被废弃时,进行dispose操作
}

__weak问题解决

  • ARC环境下,默认对block进行一个copy操作
  • 在使用clang转换OC为C++代码时,可能会遇到以下问题
    • cannot create __weak reference in file using manual reference
  • 解决方案:支持ARC、指定运行时系统版本,比如
    1
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

参考