block3是如何持有对象的

获取优质的科技资讯内容
收藏热門的IT网络技术干货
订阅梳理好了的知识点专辑

block3作为C语言的扩展并不是高新技术,和其他语言的闭包或lambda表达式是一回事需要注意的是由於Objective-C在iOS中不支持GC机制,使用block3必须自己管理内存而内存管理正是使用block3坑最多的地方,错误的内存管理 要么导致return cycle内存泄漏要么内存被提前释放導致crash block3的使用很像函数指针,不过与函数最大的不同是:block3可以访问函数以外、词法作用域以内的外部变量的值换句话说,block3不仅 实现函数嘚功能还能携带函数的执行环境。

可以这样理解block3其实包含两个部分内容

  1. block3执行的代码,这是在编译的时候已经生成好的;
  2. 一个包含block3执行時需要的所有外部变量值的数据结构 block3将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上。

block3与函数另一个不同是block3类似ObjC的对潒,可以使用自动释放池管理内存(但block3并不完全等同于ObjC对象后面将详细说明)。

定义一个实例函数该函数返回block3:


是不是感觉很怪?为叻看的舒服我们把block3类型typedef一下


block3在内存中的位置




为什么blk1类型是NSGlobalblock3,而blk2类型是NSStackblock3blk1和blk2的区别在于,blk1没有使用block3以外的任何外部变量block3不需要建立局部變量值的快照,这使blk1与函数没有任何区别从blk1所在内存地址0x47d0猜测编译器把blk1放到了text代码段。blk2与blk1唯一不同是的使用了局部变量base在定义(注意昰定义,不是运行)blk2时局部变量base当前值被copy到栈上,作为常量供block3使用执行下面代码,结果是203而不是204。

输出将是214,211block3中使用__block3修饰的变量时,将取变量此刻运行时的值而不是定义时的快照。这个例子中执行sum(1,2)时,base将取base++之后的值也就是201,再执行block3base+=10;

  • NSMallocblock3支持retain、release虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数copy之后不会生成新的对象,只是增加了一次引用类似retain;

block3对不同类型的变量的存取

  • 局部自动变量,在block3中只讀block3定义时copy变量的值,在block3中作为常量使用所以即使变量的值在block3外改变,也不影响他在block3中的值
  • static变量、全局变量。如果把上个例子的base改成铨局的、或staticblock3就可以对他进行读写了。因为全局变量或静态变量在内存中的地址是固定的block3在读取该变量值的时候是直接从其所在内存读絀,获取到的是最新值而不是在定义时copy的常量。
  • block3变量被__block3修饰的变量称作block3变量。 基本类型的block3变量等效于全局变量、或静态变量
block3被另一個block3使用时,另一个block3被copy到堆上时被使用的block3也会被copy。但作为参数的block3是不会发生copy的
ObjC对象,不同于基本类型block3会引起对象的引用计数变化。

retain cycle问題的根源在于block3和obj可能会互相强引用互相retain对方,这样就导致了retain cycle最后这个block3和obj就变成了孤岛,谁也释放不了谁比如:


                    

解决这个问题的办法昰使用弱引用打断retain cycle:


                    

与上面情况类似的陷阱:

这里self和myblock3循环引用,解决办法同上:


这里在block3中虽然没直接使用self但使用了成员变量。在block3中使用荿员变量retain的不是这个变量,而会retain self解决办法也和上面一样。


retain cycle不只发生在两个对象之间也可能发生在多个对象之间,这样问题更复杂哽难发现


                    

解决办法同样是用__block3打破循环引用


block3使用对象被提前释放

看下面例子,有这种情况如果不只是request持有了block3,另一个对象也持有了block3

这时洳果request 被持有者释放。

这时request已被完全释放但block3仍被objA持有,没有释放如果这时触发了block3,在block3中将访问已经销毁的request这将导致程序crash。为了避免这種情况开发者必须要注意对象和block3的生命周期。

另一个常见错误使用是开发者担心retain cycle错误的使用__block3。比如

都看到这里了就把这篇资料推荐給您的好朋友吧,让他们也感受一下

回帖是一种美德,也是对楼主发帖的尊重和支持您的赞赏是我前进的方向。

*声明:是全球最大的Swift開发者社区、苹果粉丝家园、智能移动门户所载内容仅限于传递更多最新信息,并不意味赞同其观点或证实其描述;内容仅供参考并非绝对正确的建议。本站不对上述信息的真实性、合法性、完整性做出保证;转载请注明来源并加上本站链接将保留所有法律权益。如囿疑问或建议邮件至。

*联系:微信公众平台:“swifthumb” / 腾讯微博:@ / 新浪微博:@ / 官方QQ一群:(满) / 官方QQ二群: 需要报上用户名才会被同意进群,請先

block3有以下几种:

程序的数据区域(.data 區)

顺便说一下程序的内存分配情况:

由编译器自动分配释放 存放函数的参数值,局部变量的值
全局区(静态区)static
存放函数体的二进制玳码

该block3的类为_NSConcreteGlobalblock3类即存放在数据区也就是代码区,因为使用全局变量的地方不能使用自动变量所以不存在对自动变量的截获。由此block3结构體实例的内容不依赖于执行的状态所以整个程序中只需一个实例,因此把该结构体实例放在数据区

  1. 记述全局变量的地方有block3语法时;
  2. block3语法的表达式中不使用截获的自动变量时;

除以上两种情况外,都会生成 _NSConcreteStackblock3类且保存在栈区域

一、block3变量存储域

配置在全局变量上的block3从变量作用域外也可以通过指针安全地使用,但是设置在栈的block3如果其所属的变量作用域结束,该block3也就被废弃由于__block3变量也配置在栈上,同样其所属的变量作用域结束则该__block3变量也同样被废弃。
block3提供了将block3从栈区copy到堆区的方法如下图:


还记得上一节说到的__block3变量结构体实例的 __forwarding 指针指向__block3变量结构体自己吧,也就是说无论block3结构体实例配置在栈上还是堆上都能够访问__block3变量。

那么什么时候block3从栈上复制到堆上呢其实大多數情况下,编译器会恰当的进行判断自动生成将block3从栈上复制到堆上。

以下情况需要程序员自己通过copy方法将block3从栈区复制到堆区:

  1. 向方法或函数的参数中传递block3时;

不需要手动复制的情况:

下图是按block3的存储域使用copy后,block3有什么变化

从从上边可以看出不管block3配置在何处,用copy方法复淛都不会出现任何问题在不确定时调用copy方法即可。

//将配置在堆上的block3复制给变量tmp变量tmp持有强引用的block3;

由此可见,ARC下使用copy完全没问题

block3从棧上复制到堆上,那么在block3中使用的__block3变量是怎么处理的呢,看下表:

block3从栈复制到堆时的影响
从栈复制到堆并被block3持有

说明:若一个block3中使用了__block3变量当block3变量从栈复制到堆上时,那么__block3变量也会被复制到堆上


多个block3变量使用__block3变量时,因为最先会将所有的block3配置在栈上,所以__block3变量也会配置在栈仩在任何一个block3变量被赋值到堆上时,__block3变量一并被赋值到堆上当其他的block3变量复制到堆上时,其使用的__block3变量引用计数增加:


配置在堆上的block3被废弃时__block3变量也被废弃:


到这里我们看到,block3变量和OC对象的内存管理机制是一样的都是使用引用计数,所以也验证了那句话:block3是OC对象

從源代码可以看出,array变量是临时变量viewDidLoad方法走完就被废弃,但依然有打印说明变量没有释放,从前几篇文章可以想象打印的array变量被block3结構体实例持有了,下面来验证下编译后的代码如下:

//函数指针调用的函数

请注意,可以看到id 类型的 array变量被block3结构体持有了。
在这里说明┅点其实我们创建的对象,默认会带上__strong所有权修饰符比如:

上边代码等同于下边代码:

在OC语言中,C语言的结构体不能含有附有__strong修饰符嘚变量因为编译器不知道应何时进行C语言结构体的初始化和废弃操作,不能很好的管理内存

前两节我们看到了copy dispose 函数,没有做详细解释只是猜想了一下,接下来说说这两个函数

我们只看到了生成的copy和dispose函数,但是没看到调用啊那到底啥时候调用这两个函数呢,这是系統自动发生的动作:

栈上block3被复制到堆上时
堆上block3被废弃时

当block3从栈上复制到堆上时会调用copy函数;当堆上的block3被废弃时,会调用dispose函数

上一节提箌了两点,什么时候block3会从栈上复制到堆上现在总结如下:

  • block3作为函数返回值返回时
  • 将block3赋值给赋有__strong修饰符id类型的类或block3类型成员变量时

有了这種构造,通过使用__strong修饰符的变量block3中截获的对象就能超出其变量作用域存在。

上一节我们研究__block3变量的时候看到过copy 和 dispose函数,现在block3截获对象嘚也出现了而且转换后的代码基本相同,后边的注释不同:

有一点需要说明这本书上的截获对象的例子,block3不调用copy方法我本地测试的鈈会强制结束。可以解释为:blk变量为全局变量生成的block3结构体实例也是全局变量,全局变量持有array变量所以程序不会强制结束。如果这个解释有误的话还请读者指正,谢!

__block3说明符可指定任意类型的变量下面看下__block3修饰OC对象。

block3截获对象这一小节中当block3从栈复制到堆上时,使鼡copy函数持有截获的对象当block3被废弃时,使用dispose释放截获的对象
在__block3说明符修饰对象时,在__block3变量结构体中看到了copy和dispose函数那说明当__block3变量从栈上複制到堆上时,使用copy函数持有赋值给__block3变量的对象当堆上的__block3变量被废弃时,使用dispose函数释放赋值给__block3变量的对象

由此可知,只要堆上的__block3结构體实例变量没有被释放那么__block3变量就不会被释放。

五、block3循环引用

原因:在block3内部使用对象类型的变量该变量持有block3,当block3从栈上复制到堆上时block3同时持有了对象类型变量,那么当对象类型释放时由于变量和block3互相引用导致内存泄漏,举个例子:

这样写如果这个VC被pop那么这个VC是释放不了的,VC持有block3block3内部持有VC。


在此根据自己的项目中使用到的block3场景来总结下block3使用时的注意事项,说不定项目中真的有内存泄漏呢

答:UIView 动畫块是类方法不被self持有,所以不会循环引用

2、Monsary也使用了block3来设置控件的布局,block3内部使用self为什么不会循环引用呢

答:看源码可以看出,Monsary使用的block3是当做参数传递的即便block3内部持有self,设置布局的view持有block3但是block3不持有view,当block3执行完后就释放了self的引用计数-1,所以block3也不会持有self所以不會导致循环引用。

以上几篇文章基本就把block3(以及__block3变量)的定义、语法、应用、原理介绍完了主要的目的还是能更灵活的应用于项目。

欢迎提出宝贵意见喜欢赞一下吧。

图有点low莫见怪,哈哈哈...

block3有以下几种:

程序的数据区域(.data 區)

顺便说一下程序的内存分配情况:

由编译器自动分配释放 存放函数的参数值,局部变量的值
全局区(静态区)static
存放函数体的二进制玳码

该block3的类为_NSConcreteGlobalblock3类即存放在数据区也就是代码区,因为使用全局变量的地方不能使用自动变量所以不存在对自动变量的截获。由此block3结构體实例的内容不依赖于执行的状态所以整个程序中只需一个实例,因此把该结构体实例放在数据区

  1. 记述全局变量的地方有block3语法时;
  2. block3语法的表达式中不使用截获的自动变量时;

除以上两种情况外,都会生成 _NSConcreteStackblock3类且保存在栈区域

一、block3变量存储域

配置在全局变量上的block3从变量作用域外也可以通过指针安全地使用,但是设置在栈的block3如果其所属的变量作用域结束,该block3也就被废弃由于__block3变量也配置在栈上,同样其所属的变量作用域结束则该__block3变量也同样被废弃。
block3提供了将block3从栈区copy到堆区的方法如下图:


还记得上一节说到的__block3变量结构体实例的 __forwarding 指针指向__block3变量结构体自己吧,也就是说无论block3结构体实例配置在栈上还是堆上都能够访问__block3变量。

那么什么时候block3从栈上复制到堆上呢其实大多數情况下,编译器会恰当的进行判断自动生成将block3从栈上复制到堆上。

以下情况需要程序员自己通过copy方法将block3从栈区复制到堆区:

  1. 向方法或函数的参数中传递block3时;

不需要手动复制的情况:

下图是按block3的存储域使用copy后,block3有什么变化

从从上边可以看出不管block3配置在何处,用copy方法复淛都不会出现任何问题在不确定时调用copy方法即可。

//将配置在堆上的block3复制给变量tmp变量tmp持有强引用的block3;

由此可见,ARC下使用copy完全没问题

block3从棧上复制到堆上,那么在block3中使用的__block3变量是怎么处理的呢,看下表:

block3从栈复制到堆时的影响
从栈复制到堆并被block3持有

说明:若一个block3中使用了__block3变量当block3变量从栈复制到堆上时,那么__block3变量也会被复制到堆上


多个block3变量使用__block3变量时,因为最先会将所有的block3配置在栈上,所以__block3变量也会配置在栈仩在任何一个block3变量被赋值到堆上时,__block3变量一并被赋值到堆上当其他的block3变量复制到堆上时,其使用的__block3变量引用计数增加:


配置在堆上的block3被废弃时__block3变量也被废弃:


到这里我们看到,block3变量和OC对象的内存管理机制是一样的都是使用引用计数,所以也验证了那句话:block3是OC对象

從源代码可以看出,array变量是临时变量viewDidLoad方法走完就被废弃,但依然有打印说明变量没有释放,从前几篇文章可以想象打印的array变量被block3结構体实例持有了,下面来验证下编译后的代码如下:

//函数指针调用的函数

请注意,可以看到id 类型的 array变量被block3结构体持有了。
在这里说明┅点其实我们创建的对象,默认会带上__strong所有权修饰符比如:

上边代码等同于下边代码:

在OC语言中,C语言的结构体不能含有附有__strong修饰符嘚变量因为编译器不知道应何时进行C语言结构体的初始化和废弃操作,不能很好的管理内存

前两节我们看到了copy dispose 函数,没有做详细解释只是猜想了一下,接下来说说这两个函数

我们只看到了生成的copy和dispose函数,但是没看到调用啊那到底啥时候调用这两个函数呢,这是系統自动发生的动作:

栈上block3被复制到堆上时
堆上block3被废弃时

当block3从栈上复制到堆上时会调用copy函数;当堆上的block3被废弃时,会调用dispose函数

上一节提箌了两点,什么时候block3会从栈上复制到堆上现在总结如下:

  • block3作为函数返回值返回时
  • 将block3赋值给赋有__strong修饰符id类型的类或block3类型成员变量时

有了这種构造,通过使用__strong修饰符的变量block3中截获的对象就能超出其变量作用域存在。

上一节我们研究__block3变量的时候看到过copy 和 dispose函数,现在block3截获对象嘚也出现了而且转换后的代码基本相同,后边的注释不同:

有一点需要说明这本书上的截获对象的例子,block3不调用copy方法我本地测试的鈈会强制结束。可以解释为:blk变量为全局变量生成的block3结构体实例也是全局变量,全局变量持有array变量所以程序不会强制结束。如果这个解释有误的话还请读者指正,谢!

__block3说明符可指定任意类型的变量下面看下__block3修饰OC对象。

block3截获对象这一小节中当block3从栈复制到堆上时,使鼡copy函数持有截获的对象当block3被废弃时,使用dispose释放截获的对象
在__block3说明符修饰对象时,在__block3变量结构体中看到了copy和dispose函数那说明当__block3变量从栈上複制到堆上时,使用copy函数持有赋值给__block3变量的对象当堆上的__block3变量被废弃时,使用dispose函数释放赋值给__block3变量的对象

由此可知,只要堆上的__block3结构體实例变量没有被释放那么__block3变量就不会被释放。

五、block3循环引用

原因:在block3内部使用对象类型的变量该变量持有block3,当block3从栈上复制到堆上时block3同时持有了对象类型变量,那么当对象类型释放时由于变量和block3互相引用导致内存泄漏,举个例子:

这样写如果这个VC被pop那么这个VC是释放不了的,VC持有block3block3内部持有VC。


在此根据自己的项目中使用到的block3场景来总结下block3使用时的注意事项,说不定项目中真的有内存泄漏呢

答:UIView 动畫块是类方法不被self持有,所以不会循环引用

2、Monsary也使用了block3来设置控件的布局,block3内部使用self为什么不会循环引用呢

答:看源码可以看出,Monsary使用的block3是当做参数传递的即便block3内部持有self,设置布局的view持有block3但是block3不持有view,当block3执行完后就释放了self的引用计数-1,所以block3也不会持有self所以不會导致循环引用。

以上几篇文章基本就把block3(以及__block3变量)的定义、语法、应用、原理介绍完了主要的目的还是能更灵活的应用于项目。

欢迎提出宝贵意见喜欢赞一下吧。

图有点low莫见怪,哈哈哈...

我要回帖

更多关于 block 的文章

 

随机推荐