一个众所周知的概念: Block就是Objective-C 对于闭包的实现。
一、iOS中内存相关
1、iOS内存分区
- 栈区(stack)
*1.由系统自动分配,一般存储函数参数值,局部变量等。
*2.由编译器自动创建和释放,一旦出了作用域就会被销毁,不需要程序员管理栈区变量内存。
*3.操作方式类似于数据结构中的栈,即后进先出,先进后出原则。
*4.栈区地址从高到低分配。 - 堆区(heap)
*1.一般由程序员申请并指明大小,最终需要由程序员释放;如果程序员不回收,程序结束时可能由OS回收。
*2.堆区(iOS)分配内存使用alloc ,C中是malloc函数。
*3.堆区的管理采用链表式管理,操作系统有一个记录空闲内存地址的链表,当接受到程序分配的内存申请时,OS就会遍历该链表,遍历到一个记录的内存地址大于申请内存的链表节点,并将该节点从该链表中删除,然后该节点记录的内存地址分配成程序。
*4.堆区的地址是从低到高分配 。 - 全局区/静态区(static)
*1.全局变量和静态变量存储在这个区域。
*2.包括未初始化(eg:int a;),初始化(eg: int b = 10;),就是说初始化的全局变量和静态常量存储在一块区域,未初始化全局变量和静态常量存储在一块。程序结束后由系统释放。 - 常量区
*存储字符串常量。程序结束后由系统释放
- 程序代码区
*主要存放函数体的二进制代码
2、闭包(Closure)
闭包就是一个函数,或者一个指向函数的指针,加上这个函数执行的非全局变量;通俗点说,就是闭包允许一个函数访问声明该函数运行上下文中的变量,甚至可以访问不同运行上下文中的变量。
闭包 = 一个函数[或指向函数的指针] + 该函数执行的外部的上下文变量[自由变量]
3、Block基础
1、Block可以认为是一个匿名函数,使用如下语法
声明Block类型:
return_type (^block_name)(parameters)
定义Block:
^(parameters){return return_type};
这种写法省略了返回值类型,也可以显示地指出返回值类型。
2、Block结构图如下:
3、Block使用
声明并定义完一个Block之后,便可以向函数一样使用它,同时,Block是一种Objective-C对象,可以赋值,当做参数传递,也可以放在NSArray或NSDictionary中。
注意:当用于函数参数时,Block应该放在参数列表的最后一个。
Block语法:
- 作为变量:
return_type (^blockName)(parameterTypes) = ^returnType(parameters){...}
- 作为属性:
@property (nonatomic ,copy) returnType (^blockName)(parameterTypes)
- 作为函数声明中的参数:
- (void)someMethodThatTakesABlock:(returnType)(^)(parameterTypes)blockName;
- 作为函数调用中的参数:
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
- 作为typedef:
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters){...}
4、Block和外部变量
- 1.默认情况
对于Block外的变量引用,Block默认是将其复制到其他数据结构中来实现访问的。也就是说Block的自动获取变量只针对Block内部使用的自动变量,不使用则不获取,因为获取的自动变量会自动存在于Block的结构体内部,会导致Block体积变大。特别注意:默认情况下Block只能访问变量不能修改局部变量的值。
- 2.Block修改外部变量的值
对于用__block修饰的外部变量的引用,block是复制其引用地址来实现访问的。Block可以修改__block修饰的外部变量的值。
?为什么使用__block修饰的外部变量可以被Block修改呢?
*使用clang将OC代码转换成C++代码,会发现一个局部变量加上__block修饰符会变成了和Block一样的一个__Block_byref_val_0
结构体类型的自动变量实例,此时我们在Block内部访问val变量则需要通过一个叫__forwarding的成员变量间接访问val变量。
5、__block变量与__forwarding
关系如图:
通过_forwarding ,无论是在Block中还是Block外访问__block变量,也不管该变量是在堆上还是栈上,都能够顺利访问同一个__block变量。
6、Block的存储
- 1.Block不访问外界变量(包括栈中和堆中的变量) *Block不管在ARC还是MRC下都存储在全局区
- 2.Block访问外部变量 *MRC环境下:访问外部变量的Block默认存储在栈区,如果有copy操作,则Block存储在堆区。
*ARC环境下:访问外界变量的Block都(不管默认还是copy)存储在堆区(实际是存放在栈区,但是ARC下默认copy到堆区),自动释放。
四、Block知识点
1、在Block内部如何修改Block外部的变量?
即:写操作不对原变量生效, 加上__block,原因上面解释了。
!!!注意:Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。栈区是红灯区,堆区才是绿灯区。ps:Block内部的变量是可以修改的。
2、使用Block id类型变量的问题
对于id类型的变量,在MRC情况下,使用__block id x不会retain变量,而在ARC情况下则会对变量进行retain(相当于默认的copy操作)。如果不想在Block中进行retain可以使用__unsafe_unretain __block id x
,不过这样可能会造成野指针出现,更好的方法是使用__weak 的临时变量,或者把使用 __block 修饰的变量设为nil,打破引用循环。
3、Block使用copy和strong问题
在非ARC的情况下,对于Block类型的属性应该使用copy,因为操作Block需要维持其作用域中捕获的变量。在ARC下编译器会自动对Block进行copy操作,因此使用strong 或者copy都可以,没有什么区别,但是Apple建议使用copy来指明编译器的行为。
4、Block循环引用问题
☀︎ ARC
*Block在捕获外部变量的时候,会保持 一个强引用,当在Block中捕获self时,由于对象对Block进行了copy,于是就形成了强引用循环,为避免强引用循环,最好捕获一个self的弱引用(__weak typeof(self) weakSelf = self;),但是弱引用还会带来另一个问题,weakSelf有可能为nil,如果多次调用weakSelf的方法,有可能在Block执行过程中weakSelf变成nil,出现crash,因此需要在Block中将weakSelf”强化“(__strong typeof(self) strongSelf = weakSelf),__strong 这一句执行的时候,如果weakSelf还没有变成nil,那么就会retain self,让self在执行期间不会变成nil。如果weakSelf变成nil,就直接return 返回。 这样在Block内部的东西要么全部执行要么不执行。
☼ MRC
*使用__block解决循环引用,但是需要把这个__block 修饰的变量设为nil。
5、使用系统的某些Block Api(如UIView的block版本写动画时),是否也考虑引用循环问题?
系统的某些Block Api中,UIView的Block版本写动画时不需要考虑,但是有些APi需要考虑一下。
如果使用了一些参数中含有ivar的系统API ,如GCD ,NSNotificationcenter要小心一点,比如:如果GCD内部如果引用了self,而且GCD的其他参数是ivar,则要考虑循环引用。
解决办法: 使用检测工具或者在写代码的时间注意一下就可以了。
参考资料:
@iOS程序犭袁的Git分享
iOS Block详解