什么是Block

  • Block是将函数及其执行上下文封装起来的对象

接下来让我们通过源码来看一看Block的本质

  • 我们在一个方法中写了三行代码,第一行是定义了一个局部变量,第二行是一个Block,第三行是这个Block的调用

这里我们通过一个clang的编译命令clang -rewrite-objc xxx.m来看一下源码的实现

  • 我们的那段代码通过编译器编写后,首先第一行I代表的是一个实例方法后面的是对象和方法名,传了两个参数一个是self,一个是选择器因子

  • 然后我们方法中的第一行代码在编译后没有发生改变,我们着重看一下Block方法编译后的改变

  • 首先我们可以看到__BlockOneObj__testMethod_block_impl_0这样一个结构体,在这个结构体中传递了几个参数,第一个参数(void*)__BlockOneObj__testMethod_block_func_0我们通过名字可以知道这是一个无类型的函数指针,第二个参数&__BlockOneObj__testMethod_block_desc_0_DATA是一个Block相关描述的结构体然后取地址符,第三个参数muIntNum就是我们定义的局部变量。最后取这个结构体地址强制转换赋值给我们定义的这个Block

然后我们来看看__BlockOneObj__testMethod_block_impl_0这个结构体中有什么具体操作,如下图

其中第一个结构体里面又是什么数据结构呢,请看下图

在我们上面介绍的结构体下面还有一个函数,具体解释请看下图

那么什么是Block的调用呢

Block的调用其实就是函数的调用,从源码中我们可以看出来

  • 首先先对这个Block进行一个强制类型转换(__block_impl *)Block

  • 之后又取出它之中的成员变量FuncPtr(函数指针),找到我们上面解析的结构体和函数,在其中拿到对应的函数调用,然后把其中的参数传递进去,一个参数是我们这个Block本身,一个是我们传递的2,然后就回去调用__BlockOneObj__testMethod_block_func_0函数,最终进行调用

Block截获变量

首先我们先来看一段代码

- (void)testMethod {int muIntNum = 6;int(^Block)(int) = ^int(int num){return num *muIntNum;};muIntNum = 4;Block(2);
}复制代码

这段代码执行完Block(2)返回的值是多少呢?-------答案是12 接下来我们看一下为什么是12以及Block截获变量的本质是什么

  • 对于基本数据类型的局部变量截获其值

  • 对于对象类型的局部变量连同其所有权修饰符一起截获

  • 对于局部静态变量是以指针形式去截获

  • 对于全局变量和静态全局变量不截获

下面直接上代码

#import "BlockTwoObj.h"//全局变量
int global_var = 4;
//全局静态变量
static int static_global_var = 5;@implementation BlockTwoObj- (void)testMethodTwo {//基本数据类型的局部变量int var = 1;//对象类型的的局部变量__unsafe_unretained id unsafe_obj = nil;__strong id strong_obj = nil;//局部静态变量static int static_var = 3;void(^Block)(void) = ^{NSLog(@"基本数据类型局部变量:%d", var);NSLog(@"对象类型局部变量(__unsafe_unretained修饰):%@", unsafe_obj);NSLog(@"对象类型局部变量(__strong修饰):%@", strong_obj);NSLog(@"局部静态变量:%d", static_var);NSLog(@"全局变量:%d", global_var);NSLog(@"全局静态变量:%d", static_global_var);};Block();
}@end
复制代码

接下来我们通过clang命令clang -rewrite-objc -fobjc-arc xxx.m来看一下源码

  • 在这张图中可以很清晰的看到Block中的变量截获,其中需要注意的是对于局部的静态变量截获的是指针,也就是说如果后面这个局部静态变量发生了修改,那么Block中使用的是最新的值

__block修饰符

我们在什么情况下使用__block修饰符呢? 一般情况下,对被截获变量进行赋值操作需要添加__block修饰符,这里需要注意的是赋值不等于是使用,切记!!!

例如在下面的代码中是否需要__block修饰符来修饰

NSMutableArray *muArr = [[NSMutableArray alloc] init];void(^Block)(void) = ^{//这里只是做了添加操作,并非赋值,所以不需要用__block进行修饰[muArr addObject:@"111"];};Block();
复制代码

那么在下面的代码段当中呢?

__block NSMutableArray *muArrOther = nil;void(^BlockOther)(void) = ^{//这里做了赋值操作,所以需要用__block进行修饰,否则会出现编译报错muArrOther = [NSMutableArray array];};BlockOther();
复制代码

对变量进行赋值时

  • 需要__block修饰符修饰的是局部变量(包括基本数据类型和对象类型)

  • 不需要__block修饰符修饰的是静态局部变量、全局变量和静态全局变量,因为对于全局变量和静态全局变量不涉及到变量的截获,而对于静态局部变量呢,是通过使用指针来操作对应的变量的,所以也不需要修饰

下面请看一段代码,还是我们上面的那个例子

- (void)testMethod {__block int muIntNum = 6;int(^Block)(int) = ^int(int num){return num *muIntNum;};muIntNum = 4;Block(2);
}
复制代码

此时Block返回的是8,这里是为什么呢,我们只是用了__block来修饰

  • 因为在这里会发生一个非常奇妙的变化,__block修饰的变量变成了对象

请看下面的流程图

  • 首先__block int muIntNum会被转化成第一个这样一个结构体,其中具有isa指针,我们也可以理解成一个对象

  • 从这个角度来看muIntNum经过编译后就会变成一个对象,通过__forwarding指针去找到对应的对象,然后进行赋值

  • 刚才我们看到的代码段是在栈上,在__block变量中有一个__forwarding指针,而这个指针指向的是自己,这里要注意的是前提是在栈上,如果在堆上,这个__forwarding指针指向的就不是自己了,在下面会讲到

  • 所以在栈上我们修改这个变量的值,就会通过__forwarding指针找到自己本省去修改这个变量的值

那么这里有一个问题就是我们在栈上这个__forwarding指向的是自己到底有什么用呢?我们完全可以通过访问成员变量来修改,为什么还需要这个指针呢,请继续往下看

Block的内存管理

Block有三种类型

  • _NSConcreteGlobalBlock 全局Block

  • _NSConcreteStackBlock 栈Block

  • _NSConcreteMallocBlock 堆Block

Block的Copy操作

  • 当我们栈上的Block通过copy在堆上产生一个一样的Block,有相同的Block和__block变量,当变量作用于结束后,栈上的Block对象就会被销毁,而堆上的block依旧存在,所有如果栈上Block不用copy拷贝到堆上,在作用于销毁后会因为找不到Block对象而崩溃

  • 当然我们在这里有一个问题,假如说在MRC环境下,如果在栈上进行了copy操作,会不会产生内存泄漏,答案是肯定的,相当于一个对象alloc出来,但是并没有对应的relese操作一样

  • 当我们栈上的Block经过copy操作后,在堆上会产生一个一样的Block,在栈中的Block中的__forwarding指针指向的事堆上Block的__block变量,并且在堆上Block的__forwarding指针也是指向的它自己的__block变量

参考书籍

Objective - C 高级编程:iOS与OS X多线程和内存管理

Github

Demo

iOS探索:Block解析浅谈相关推荐

  1. iOS实录15:浅谈iOS Crash

    导语:在当前的iOS开发中,虽然ARC为开发者解决了手动内存管理时代 的许多麻烦,但是内存方面的问题依然是产生iOS Crash的元凶之一,本文介绍内存方面,有关僵尸对象.野指针.内存泄漏.废弃内存这 ...

  2. 解析并符号 读取dll_风电场用风功率采集测风塔数据报文格式解析浅谈

    前段时间因为有点事情,好久没有更新了,非常对不住,感谢大家还在关注,下面是正文:看过我前面文章的朋友应该都知道,测风塔上送数据主要包括以下几个环境气象变量:风机轮毂处的风速.风向数据.以及其他不同高处 ...

  3. ios与android功能特点,浅谈iOS与Android的区别

    说在前面:从事UI设计的同行们关于iOS与Android的基本设计规范相信大家都已经非常了解了,以下是我针对这两种设计规范所作的一点点小总结,如果面试官问道此类问题,希望对你们有用!!! 首先设计语言 ...

  4. 上传php文件不能解析,浅谈文件解析及上传漏洞

    中国菜刀 在web渗透中,我最期待两种漏洞,一种是任意命令执行漏洞,如struct2漏洞等:另一种是文件上传漏洞,因为这两种漏洞都是获取服务器权限最快最直接的方法.而对于任意命令执行漏洞,如果是通过内 ...

  5. ios android 上架区别,浅谈iOS与Android的区别

    说在前面:从事UI设计的同行们关于iOS与Android的基本设计规范相信大家都已经非常了解了,以下是我针对这两种设计规范所作的一点点小总结,如果面试官问道此类问题,希望对你们有用!!! 首先设计语言 ...

  6. 关于OPC协议解析浅谈

    转自:微点阅读  https://www.weidianyuedu.com 1    什么是OPC UA 为了应对标准化和跨平台的趋势,为了更好的推广OPC,OPC基金会近些年在之前OPC成功应用的基 ...

  7. 浅谈JVM(六):方法调用过程

    上一篇: 浅谈JVM(一):Class文件解析 浅谈JVM(二):类加载机制 浅谈JVM(三):类加载器和双亲委派 浅谈JVM(四):运行时数据区 浅谈JVM(五):虚拟机栈帧结构 6.方法调用过程 ...

  8. 浅谈API安全的应用

    理论基础 API它的全称是Application Programming Interface,也叫做应用程序接口,它定义了软件之间的数据交互方式.功能类型.随着互联网的普及和发展,API 从早期的软件 ...

  9. iOS 自定义转场动画浅谈

    代码地址如下: http://www.demodashi.com/demo/11612.html 路漫漫其修远兮,吾将上下而求索 前记 想研究自定义转场动画很久了,时间就像海绵,挤一挤还是有的,花了差 ...

最新文章

  1. Facebook AI新架构:全景FPN,同时完成图像实例与语义分割 | 极客头条
  2. 在linux终端远程登陆linux服务器
  3. graphicsmagick 获取图片质量_第 72 期 水稻图片素材
  4. spring --(12)bean的生命周期
  5. A folder failed to be moved——Android SDK的安装问题解决方案
  6. junit测试方法_JUnit测试方法订购
  7. 机器学习——统计学习方法——第1章 统计学习及监督学习概论
  8. C++笔记-const与mutable、static_cast与reinterpret_cast
  9. 刘强东:京东必定会击败阿里巴巴
  10. try-catch-finally的返回值问题
  11. subprocess,类
  12. 全国大学生“高教杯“成图大赛:关于蜗轮蜗杆快速建模研究(二)
  13. 基于51单片机和555定时器的电阻电感电容测量装置设计
  14. Flink 实践教程-进阶(11):SQL 关联:Regular Join
  15. 计算机网络课后习题概略
  16. java中的String和ArrayList类
  17. Q1净亏损同比扩大222% 四通一达业绩垫底百世还能逆袭吗?
  18. MSP430下载程序BSL
  19. 这是一个只有一句话的木MA
  20. 华为手机word插件加载失败_c#调用word的组件时失败解决方法

热门文章

  1. Java 中静态方法 实例方法 具体方法区别与联系
  2. 又拍云张聪谈安全、HTTPS、自定义、CDN的未来趋势
  3. Hibernate入门之关系篇:多对一和一对多映射
  4. 【网摘阅读】舒迅:产品经理必读的九步法
  5. C#:System.Data.Common命名空间(数据库抽象工厂的使用)
  6. 高端ERP软件市场漫谈:崇洋无罪 自重有理
  7. .NET框架程序设计--Globally Deployment Assembly全局部署程序集
  8. Scrapy运行中常见网络相关错误
  9. (SSO)单点登录原理和总结
  10. 当我们在谈论技术时,技术的本质和价值究竟是什么?