Block的理解

一、块的基础知识

块于函数类似,只不过是直接定义在另一个函数里的,和定义它的那个函数共享同一个范围内的东西。

void (^someBlock)() = ^{

}

这段代码定义了一个名为someBlock的变量,没有参数,也不返回值。

块类型的语法结构如下:

return_type (^block_name)(parameters)

下面这种写法所定义的块,返回int值,并且接受两个int做参数:

int (^addBlock)(int a, int b) = (int a, int b) {

    return a+b;

}

定义好之后就可以像函数那样使用了:

int add = addBlock(a,b);

1.块和定义它的那个函数共享同一范围内的东西。

2.在声明块的范围里,所有变量都可以为其所捕获。要想修改块所捕获的变量,需要加上__block。

3.块本身也和其他对象一样,有引用计数,当最后一个指向块的引用移走之后,块就回收了。

二、块的内部结构

      每个OC对象都占据着某个内存区域。因为实例变量的个数及对象所包含的关联数据互不相同,所以每个对象所占的内存区域有大有小。块本身也是对象,在存放块对象的内存区域中,首个变量是指向Class对象的指针,该指针叫做isa。其余内存里含有块对象正常运转所需的各种信息。

      在内存布局中,最重要的就是invoke变量,这是个函数指针,指向块的实现代码。函数原型至少要接受一个void*型的参数,此参数代表块。刚才说过,块其实就是一种代替函数指针的语法结构,原来使用函数指针时,需要用“不透明的void指针”来传递状态。而改用块之后,则可以把原来用C语言特性所编写的代码封装成简明且易用的接口。

      descriptor变量是指向结构体的指针,每个块里都包含此结构体,其中声明了块对象的总体大小,还声明了copy和dispose这两个辅助函数所对应的函数指针。辅助函数在拷贝及丢弃块对象时运行,其中会执行一些操作,比方说,前者要保留捕获的对象,而后者则将之释放。

      块还会把它所捕获的所有变量都拷贝一份,这些拷贝放在descriptor变量后面,捕获了多少个变量,就要占据多少内存空间。请注意,拷贝的并不是对象本身,而是指向这些对象的指针变量。invoke函数为何需要把块对象作为参数传进来呢?原因就在于,执行块时,要从内存中把这些捕获到的变量读出来。

三、全局块,栈块,堆块

定义块的时候其所占内存区域是分配在栈中的,其所占的内存区域是分配在栈上的,也就是说块只在定义它的那个范围内有效。例如下面这段代码就有危险:

void (^block)();

if (/*some condition */) {

    block = ^ {

        NSLog(@"Block A");

    }

}else {

    block =^ {

        NSLog(@"Block B");

    }

}

    定义在if 及else语句中的两个块都分配在栈内存中。编译器会给每个块分配好栈内存,然而等离开了相应的范围之后,编译器有可能会把分配给块的内存覆写掉。于是这两个块只能保证在对应的if或else语句范围内有效。这样写出来的代码可以编译,但是运行起来时而正确时而错误。若编译器未覆写待执行的块,则程序照常运行,若覆写,则程序崩溃。

    为解决此问题可给块对象发送copy消息拷贝之。这样就可以把块从栈复制到堆了。拷贝后的块可以在定义它的那个范围外使用,而且一但复制到堆上,块就成了带引用计数的对象了。后续的复制操作都不会真的复制,只是递增块对象的引用计数。如果不再使用这个块,那就应将其释放,在ARC环境下会自动释放,而手动管理引用计数时则需要自己调用release方法。

void (^block)();

if (/*some condition */) {

    block = [ ^ {

        NSLog(@"Block A");

    } copy];

}else {

    block = [^ {

        NSLog(@"Block B");

    } copy];

}

    现在代码就变得安全了,如果手动管理引用计数,用完块之后还需将其释放。

    除了“栈块”和“堆块”之外,还有一种类型叫做“全局块”。这种块不会捕捉任何状态,运行时也无需有状态来参与。块所使用的整个内存区域,在编译期已经完全确定了,因此,全局块可以生命在全局内存里,而不需要在每次用到的时候于栈中创建。另外,全局块的拷贝操作是个空操作,因为全局块绝不可能为系统所回收。这种块实际上相当于单例,下面就是一个全局块:

void(^block)() = {

    NSLog(@"This is a block");

}

总结:块可以分配在栈或者堆上,也可以是全局的。分配在栈上的块可以拷贝到堆里,这样的话,就和标准的OC对象一样,具备引用计数了。

转载于:https://www.cnblogs.com/byxixiblogs/p/8603638.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值