问题:
1.Block是什么,block当初是为了解决什么样的问题而设计的?
2.为什么要用copy修饰Block
3.为什么Block中可以访问局部变量而不能修改
4.为什么用__block修饰的局部变量可以在Block内部修改
5.为什么使用Block会造成循环引用,怎么解决
6.Block,delegate的使用场景
1.Block是什么,为什么会出现Block这种技术
用一句话来概括就是带有自动变量的匿名函数。
block 本质是一个函数指针加上一个对应捕获上下文变量的内存块(结构体或者类)
block的定义如下
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
一个 block 实例实际上由 6 部分构成:
1. isa 指针,所有对象都有该指针,用于实现对象相关的功能。
2. flags,用于按 bit 位表示一些 block 的附加信息
3. reserved,保留变量。
4. invoke,函数指针,指向具体的 block 实现的函数调用地址。
5. descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
6. variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。
根据isa指针,block一共有3种类型的block
_NSConcreteGlobalBlock 全局静态
_NSConcreteStackBlock 保存在栈中,出函数作用域就销毁
_NSConcreteMallocBlock 保存在堆中,retainCount == 0销毁
block当初是为了解决什么样的问题而设计的?
答案1. block的提出是为了解决在不同的对象间除了传递值之外还可以传递一个操作而提出的。
答案2. block的设计是为了解决方法中方法可做为参数传入当前上下文中,将方法具体的实现进行抽象,一种策略设计模式的具体表现。
2.为什么要用copy修饰Block
为什么要用copy,这是因为在MRC时期,作为属性的block在初始化时是被存放在静态区的,这样在使用时如果block内有调用外部变量,那么block无法保留其内存,在初始化的作用域内使用并不会有什么影响,但一但出了block的初始化作用域,就会引起崩溃,使用copy可以将block的内存推入堆中,这样让其拥有保存调用的外部变量的内存的能力。
大部分人都认为block作为属性在声明时,只能用copy修饰,而不可以用strong。其实用strong也是可以的。只是使用copy修饰block是MRC时期的遗留物,这在MRC时期是至关重要的事情,但是使用ARC的现在,strong是可以代替的,只是一个习惯问题而已。
3.为什么Block中可以访问局部变量而不能修改
例子如下:
注: 通过clang命令将OC转为C++代码来查看一下Block底层实现,clang命令使用方式为终端使用cd定位到main.m文件所在文件夹,然后利用clang -rewrite-objc main.m将OC转为C++,成功后在main.m同目录下会生成一个main.cpp文件
由此可知,在Block定义时便是将局部变量的值传给Block变量所指向的结构体,因此在调用Block之前对局部变量进行修改并不会影响Block内部的值,同时内部的值也是不可修改的
4.为什么用__block修饰的局部变量可以在Block内部修改
添加__block修饰符之后的代码如下:
可以看到并不是直接传递a的值了,而是把a的地址传过去了,所以在block内部便可以修改到外面的变量了。
5.为什么使用Block会造成循环引用,怎么解决
因为对象obj在Block被copy到堆上的时候自动retain了一次。因为Block不知道obj什么时候被释放,为了不在Block使用obj前被释放,Block retain了obj一次,在Block被释放的时候,obj被release一次。
循环引用问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了循环引用,最后这个Block和obj就变成了孤岛,谁也释放不了谁。
解决办法:
在ARC中没有retain,retainCount的概念。只有强引用和弱引用的概念。当一个变量没有__strong的指针指向它时,就会被系统释放。
6.Block,delegate的使用场景
通常情况应该优先使用 block。而有两个情况可以考虑 delegate。
**1. **有多个相关方法。假如每个方法都设置一个 block, 这样会更麻烦。而 delegate 让多个方法分成一组,只需要设置一次,就可以多次回调。当多于 3 个方法时就应该优先采用 delegate。
比如一个网络类,假如只有成功和失败两种情况,每个方法可以设计成单独 block。但假如存在多个方法,比如有成功、失败、缓存、https 验证,网络进度等等,这种情况下,delegate 就要比 block 要好。
**2. **为了避免循环引用,也可以使用 delegate。使用 block 时稍微不注意就形成循环引用,导致对象释放不了。这种循环引用,一旦出现就比较难检查出来。而 delegate 的方法是分离开的,并不会引用上下文,因此会更安全些。假如写一个库供他人使用,不清楚使用者的水平如何。这时为防止误用,宁愿麻烦一些,笨一些,使用 delegate 来替代 block。