Effective Objective-C 2.0 - 第三七条:理解“块”这一概念

本文介绍了Objective-C中的Blocks,包括其基础知识、类型和内部结构。块是一种闭包,可以在定义它的作用域内捕获变量。块分为栈上的块和堆上的块,通过`copy`操作可以使栈上的块在堆上持久化。块内部结构包含invoke指针和descriptor,后者包含了块捕获的变量信息。理解Blocks有助于更好地利用Objective-C的这一特性进行编程。
摘要由CSDN通过智能技术生成

前言

  块可以实现闭包,该项特性言语特性是作为“拓展”(extension)而加入GCC编译器中的。Clang 10.4 和 iOS 4.0都含有块正常执行所需的运行期组件,从技术层面江,这是位于C语言层面的特性,可以在C , C++, OC, OC++代码中使用它。

块基础知识

  块和函数相似,只不过块是直接定义在另一个函数内部,和定义它的函数共享同一个范围的东西。块其实就是一个值(对象),可以进行赋值。块的语法结构如下:

return_type (^block_name)(parameters)


//例子

int (^addBlock)(int a, int b) {
    return a + b;
}

//使用
int add = addBlock(2, 5);

  块的优势:在声明它的范围里,所有变量都可被捕获。

int additional = 5;
int (^addBlock) (int a, int b) {
    return a + b + additional;
};

int add = addBlock(2, 5);

  __block:默认情况下,块所捕获的变量是不可在块内部修改值的。除非在变量前加上__block修饰(__block int additional = 3;),

  如果块所捕获的变量是对象类型,则自动保留它,系统在释放块的时候,同时会将其一并释放。这就引出了:块本身可视为对象,那么它和其他对象一样,同样具有引用计数。当最后一个指向块的引用移走之后,同时将块所捕获的变量给释放了。

  如果将块定义在OC实例方法中,那么除了可以访问类的所有实例变量之外,还可以使用self变量。块总能修改实例变量,所以在声明时无需添加__block修饰。不过通过读取或者写入操作,捕获了实例变量,那么也会将self一同捕获了,因为实例变量和self所指的实例是关联在一起的。(类的变量呢?) 注意:一定要记住,self 也是个对象,因为块将其捕获时也会将其保留,如果self所指向的那个对象同样也保留了块,那么这种情况通常会产生“保留环”(40)。

块的类型

  定义块时,其所占的内存是分配在栈中的,即:块只在定义它的那个范围内有效,例如下面代码存在问题:

void (^block) ();
if (something) {
    block = ^{
        NSLog();
    };
else {
    block = ^{
        NSLog();
    };
}
block();

  定义在if 及 else语句中的两个块都分配在栈内存中,出了相应的范围,编译器有可能把分配给块的内存覆写,于是两个块只能保证在if else语句的范围内有效。为了解决该问题,可给块对象发送copy消息,这样就可将block将栈区拷贝到堆区了,一旦复制到堆上,块就成了带引用计数的对象了。后续的copy操作都不会真正执行copy操作,只是增加块的引用计数,在ARC中,当不再使用该块时,会自动释放它,否则需要手动释放。明白这一点只需要添加copy即可:

void (^block) ();
if (something) {
    block = [^{
        NSLog();
    } copy];;
else {
    block = [^{
        NSLog();
    }copy];
}
block();

  还有一种块:全局块(global block),这种块不会捕捉任何状态(比如外围的变量),运行时也无需有状态来参与,块的整个内存在编译期已经完全确定了,全局块是声明在全局内存中,不需要每次使用都去栈中创建,另外,全局块拷贝是个空操作。

块的内部结构

块本身是一个对象,每个OC对象都占据着某个内存区域,因为实例变量的个数以及对象所包含的关联数据不同,每个对象所占用的内存大小也不尽相同。块是对象,块的内存区域中的首个变量是指向Class对象的指针,即isa,内存布局如下

invoke指针:这是个函数指针,指向块的实现代码

descriptor变量:指向结构体的指针,每个块里都包含此结构体,还声明了copy 和 dispose这两个辅助函数所对应的函数指针。


unsigned long int reserved

unsigned long int size

void (*)(void* ,void*) copy

void (*)(void* ,void*)dispose

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

下一篇:块的实战

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值