博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入理解Block
阅读量:4460 次
发布时间:2019-06-08

本文共 2438 字,大约阅读时间需要 8 分钟。

Block 简介

Mac OS X系统10.4及其iOS 4.0后引入了闭包的概念,这项语言特性是作为扩展而加入GCC编译器的。在Foundation框架中大量使用了Block。

块就是一个实现某个功能的函数闭包,这个函数闭包可以带有参数,也可以没有参数,可以有返回值也可以没有返回值者,用符号’^’来表示。块在声明的范围内,可以调用块外部的全局变量和局部变量。

void (^someBlock) () = ^{    //A simple block    //Implementation: some code    //无返回值,无参数}void (^block) (int a, bool b) = ^(int a, bool b) {   //some code   //无返回值,带参数}int (^block2) (int a, bool b) = ^(int a, bool b) {   //some code   //带参数带返回值   return integerValue;}

需要主意的是,block内部不能改变外部变量,想要在Block中改变变量的值,那么我们只需要在变量声明的时候加上__Block修饰符。

__block int a = 0;void (^block)() = ^{    a = 33;};

Block 的内部结构

每个Object-c变量都占据着某个内存区域,block本身也是一个对象,在存放block对象的内存区域中,首个变量是指向class的指针isa,其余内存里包含着对象的其他所有信息。

这里写图片描述
- isa 指针,所有对象都有该指针,用于实现对象相关的功能。

  • flags,用于按 bit 位表示一些 block 的附加信息,本文后面介绍 block copy 的实现代码可以看到对该变量的使用。
  • reserved,保留block函数代码内的变量。
  • invoke,函数指针,指向具体的 block 实现的函数调用地址。在内存布局中最重要的就是invoke函数指针,指向block的实现代码
  • descriptor,是指向结构体的指针,每个块里都包含此结构体。block将所捕获的变量指针拷贝到descriptor变量后。表示 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
  • variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变 量的地址)复制到了结构体中。

对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。对于用 __block 修饰的外部变量引用,block是复制其引用地址来实现访问

全局block/堆block/栈block

定义block的时候,其所占的内存区域是分配在栈中的。

在 Objective-C 语言中,一共有 3 种类型的 block:

  • _NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量
  • _NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁
  • _NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁

下面的这段代码在执行的时候就很危险:

void (^block)();  int a = 3;  if (a > 0) {     block = ^ { NSLog(@"Block A") };  } else {     block = ^ { NSLog(@"Block B") };  }

在定义if else 语句中的两个block都分配在栈内存区域,编译器会给每个block分配好内存,然而等离开相应的范围后,编译器有可能会把分配给块的内存覆盖掉。于是这两个块只能保障在对应的if else语句范围内有效,这样的代码运行起来就会出现问题。

为解决此问题,可以给block对象发送copy消息以拷贝到堆空间里。一旦复制到堆上,block就成了带引用计数器的对象了。后续的复制操作都不会真的执行复制,只是递增块对象的应用计数器。 以下代码就是安全的:

void (^block)();  int a = 3;  if (a > 0) {     block = [^{ NSLog(@"Block A") } copy];  } else {     block = [^{ NSLog(@"Block B") } copy];  }

与全局变量类似,全局块所使用的内存区域,在编译期就已经完全确定了,全局块可以声明在全局内存里。下面就是一个简单的全局块:

void (^block) () = ^ {NSLog(@"this is a global block");}

使用Block小技巧

Tip 1 为常见block类型创建块,声明变量时,要把名称放在类型中间:

typedef  int (^BLOCKSOME) (bool flag, int value);BLOCKSOME block = ^(bool flag, int value) {//some code};

Tip 2

用块引用及其所属对象时,不要保留闭环,防止出现return cycle。如使用weakself来防止return cycle:

__weak  ViewController *wself = self;

定义一个wself变量并加上__weak修饰符,在Block代码块中,所有需要self的地方都用wself来替代。这样就不会增加引用计数,所以Block持有self对象也就不会造成循环引用,从而造成内存泄漏。

参考文章:

转载于:https://www.cnblogs.com/shujucn/p/7481449.html

你可能感兴趣的文章
jQuery学习记录
查看>>
流程控制2循环
查看>>
oracle inside(1)
查看>>
写时复制
查看>>
常用正则表达式
查看>>
jQuery-对Radio/CheckBox的操作集合
查看>>
C#语言之“string格式的日期时间字符串转为DateTime类型”的方法
查看>>
新手如何快速入门Python
查看>>
EF的乐观并发控制
查看>>
Monitor和Lock以及区别
查看>>
(5.2)mysql高可用系列——测试环境部署
查看>>
移动端真机调试
查看>>
机票预订系统问题定义及可行性分析
查看>>
Java-API:java.util.regex.Pattern
查看>>
Java-Runoob-高级教程-实例-字符串:01. Java 实例 – 字符串比较
查看>>
鱼网:鱼网
查看>>
AngularJS:参考手册
查看>>
Mac下nodeJS初体验
查看>>
Yii 时间日期组件与composer 下载中出现的问题
查看>>
Swoole 源码分析——Server模块之TaskWorker事件循环
查看>>