您的位置:首页 > 其它

Block

2016-05-07 12:28 330 查看

block的定义

block和函数有很多相同点:

可以保存代码

有返回值

有形参

调用方式一样

没有返回值,没有形参的block:

// 定义block
void (^block)() = ^{
NSLog(@"------block----");

};

// 直接调用
block();


有形参,有返回值的block:

// 求和block
int (^sumBlock)(int ,int) = ^(int a, int b){
return a + b;
};


其实由此可以看出,block的定义和指向函数的指针非常像:

// sum函数
int sum(int a, int b)
{
return a + b;
}

- (void)test
{
// 指针p指向sum函数
int (*p)(int, int) = sum;
}


block对变量的访问

局部变量

block在访问局部变量的时候,首先会拷贝它,然后内部会增加一个该类型的成员变量,用来存储这个拷贝进来局部变量,但是这次拷贝只是一次“值传递”,block 默认是将其复制到其数据结构中来实现访问的,这就意味着我们不能修改该局部变量!





因此我们可以通过指针来进行地址传递:

- (void)test
{
int a;
int *p = &a;
void (^block)() = ^(){
*p = 10;
};
}


但需要注意的是:变量a的生命周期是和方法test相关联的,当test运行结束,栈随之销毁,变量a也会销毁,这个时候p就变成了野指针。如果block是作为参数或者返回值,这些类型就会跨栈,再次调用就会引发野指针错误!

因此可以在声明变量的时候加上__block修饰符,这样就可以在块内修改了!当加上__block修饰符后,block内部就会拷贝其引用地址来实现访问的。



(静态)全局变量

因为全局变量都是在“静态数据存储区”,在程序结束前不会销毁,所以block直接访问了对应的变量(可以直接修改变量值),并没有给它预留位置。

静态局部变量

其实它和全局变量是一样的,唯一的不同就是作用域!

block的内部结构

block本身也可视为对象,在存放block的内存区域中,首个变量是指向Class对象的指针,该指针叫做isa。其余内存里含有块对象正常运行所需的各种信息,其内存布局如下(该图在Effective Objective-C 2.0中的第37条):



该结构在apple的开源代码中也有写。

/* Revised new layout. */
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size; // 块内存大小
void (*copy)(void *dst, void *src); // 复制函数
void (*dispose)(void *); // 销毁函数
};

struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};


isa:指向该block的类Class,同时isa也是OC对象的标识,这就说明block也是对象

flags:按bit位表示一些block的附加信息,比如判断block类型、判断block引用计数、判断block是否需要执行辅助函数等

reserved:保留变量,留给以后升级使用

invoke:函数指针,指向block的实现代码

descriptor:指向结构体的指针,每一个block都包含此结构体,其中声明了block的总体大小,copy和dispose两个辅助函数所对应的函数指针,copy函数会retain住已经拷贝的对象,而dispose函数则是release对象。

其次,block还会把它所访问的所有变量都拷贝一份,这个变量在descriptor变量后面,捕获了多少个变量,就要占据多少内存空间。

需要注意的是:invoke函数为何要把block对象当做参数传进来呢?原因就在于,执行block时,要从内存中把这些访问到的变量读出来。

block的类型

在OC中,一共有三种类型的block:

_NSConcreteGlobalBlock:全局的静态block,这种block不会访问任何状态(比如外部的变量等),运行时也无需有任何状态来参与。block所使用的整个内存区域,在编译期就已经完全确定了,因此全局block是声明在全局内存中。同时,全局block的copy操作是一个空的操作,因为全局块不可能被系统所回收,相当于一个单例。

_NSConcreteStackBlock:栈上的block,在函数返回时会销毁,但是给栈block发送copy消息,就可以把block从栈上拷贝到堆上去了,一旦拷贝到堆上,block就成了带引用计数的对象了,而后续的拷贝操作都不会真的执行,只是递增block的引用计数罢了。

_NSConcreteMallocBlock:堆上的block,引用计数为0时销毁。

ARC下的block



apple官方文档中有说明,在ARC模式下,在栈间传递block时,不需要手动copy栈中的block,即可让NSConcreteStackBlock 的 block 被 NSConcreteMallocBlock 类型的 block 替代,因为ARC会自动执行copy操作。

参考文献

谈Objective-C block的实现

Block技巧与底层解析

Objective-C中的Block
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: