Block基础+实践
一.目录
- Block的定义.
一. Block的基础
1. Block的定义?
Block是一种特殊的数据类型,它可以保存一段代码,在合适的时候取出来调用.Blocks是C语言的扩充功能, iOS 4中引入了这个新功能“Blocks”。从那开始,Block就出现在iOS的各个API中,并被大家广泛使用。一句话来形容Blocks,带有自动变量(局部变量)的匿名函数.
2. Block变量的声明.
(1)一般的声明方式
返回值类型(^Block的名称)(形参) = ^(形参) {};
(2)Block作为函数的参数的声明方式
#import <UIKit/UIKit.h>
typedef void (^Blcok)();
@interface ViewController : UIViewController
- (void)function:(Blcok)block;
@end
3. Block的几种分类.
(1)无参数无返回值
// Block的声明
void (^firstBlock)() = ^() {
NSLog(@"定义一个Block类型(无参数,无返回)");
};
//调用block
firstBlock();
(2)无参数有返回值
NSString* (^SecongBlock)() = ^() {
return @"无参数有返回值";
};
NSString * secongStr = SecongBlock();
NSLog(@"%@",secongStr);
(3)有参数无返回值
void (^ThirdBlock)(int a, int b) = ^ (int a, int b) {
NSLog(@"sum = %d",a + b);
};
ThirdBlock(1,2);
(4)有参数有返回值
NSString* (^FourthBlock)(NSString * a, NSString * b) = ^ (NSString * a, NSString * b) {
return [NSString stringWithFormat:@"%@ + %@",a,b];
};
FourthBlock(@"我是",@"好人!");
4. Block的调用顺序.
按照Block的定义来将,Blcok是一段封装的代码块,在合适的时候,也就是说再调用这段代码块的时候才会使用.所以Block的执行顺序应该是先执行调用语句,再执行实现语句.
5. Block的实现原理.
(1)借用这个最简单的Block来解释一下.
void (^blk)(void) = ^(){printf("This is a block.");};
blk();
(2)对这个Block进行解析可以得到下面的该Block的源码.
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("This is a block.");
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(){
void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0
,&__main_block_desc_0_DATA);
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
(3) 分析源码,定义的Block被转换成了
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
可以看到,定义的Block被转换成了指向__main_block_impl_0
结构体的指针.先不管构造函数的参数.
先看第一个结构体: __block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
字段描述
1. isa指针,如果我们对runtime了解的话,就明白isa指向Class的指针。
2. Flags,当block被copy时,应该执行的操作
3. Reserved为保留字段
4. FuncPtr指针,指向block内的函数实现
__block_impl保存block的类型isa(如&_NSConcreteStackBlock),标识(当block发生copy时,会用到),block的方法.
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("This is a block.");
}
看第二个结构体: __main_block_desc_0
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
字段描述
1. reserved为保留字段默认为0
2. Block_size为sizeof(struct __main_block_impl_0),用来表示block所占内存大小。因为没有持有变量,block大小为impl的大小加上Desc指针大小
3. __main_block_desc_0_DATA为__main_block_desc_0的一个结构体实例
这个结构体,用来描述block的大小等信息。如果持有可修改的捕获变量时(即加__block),会增加两个函数(copy和dispose).
看第三个也是最重要的结构体: __main_block_impl_0
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_impl_0里面有两个变量struct __block_impl_imply和struct __main_block_desc_0.
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
结构体构造函数用来初始化变量`__main_block_impl_0`和`__main_block_desc_0`.
回到开始:
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
我们可以看到,block其实就是指向__main_block_impl_0的结构体指针,这个结构体包含两个__block_impl和__main_block_desc_0两个结构体,和一个方法。
通过上面的分析,是不是很已经清晰了
最后,main函数里面的
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
同样,去除转化代码,上面的代码就可以转化为
blk->FuncPtr(bulk);
执行block函数
这样我们就完成了,对简单block实现的分析。
该段解释转载于 没事蹦蹦作者的<<Block实现原理>> 文章
(http://www.jianshu.com/p/ca6ac0ae93ad)
6. Block的修饰词.
(1) __block
说之前,先说说,block访问对象的问题.
图1: Block中访问对象的微妙关系.png 图2: 对象在block copy前后内存的变化.jpg 图3: __Block修饰对象.jpg那么__block在这个过程中起到什么作用呢?
- 首先我们发现图1中,仅仅访问对象不进行操作是没问题的. 但是对该对象操作就会出现报错.提示:缺少__block修饰.
- 在图2中,我们发现在进入block快前后变量c的指针内存地址发生变化了.
- 在图3中. 第一个模块. 变量d初始值是2,进入代码快的时候会被备份一次,再对d进行重新赋值为4,d现在对应的实际值为4了.但是在block块中还是当d==2时候的备份数据.所以得出的该dBlock(1,1)的值为4. 但是在第二个模块中,用__block对e变量修饰了.就会访问该变量的地址所对应的变量.
总体来说:为了可以让block可以修改外部的变量,我们需要对变量用__block修饰. 如果block访问的变量没有添加这个关键字.则会访问到block自己拷贝的那一份变量,它是在block创建的时候创建的.而访添加了这个关键字的变量,则会访问这个变量的地址所对应的变量.
(2)__weak / __strong
对对象进行弱引用或者强引用.
在ARC环境下,我们常常会使用__weak 的修饰符来修饰一个变量,防止其在block中被循环引用,但是有些特殊情况下,我们在block中又使用__strong 来修饰这个在block外刚刚用__weak修饰的变量,为什么会有这样奇怪的写法呢?
在block中调用self会引起循环引用,但是在block中需要对weakSelf进行
strong,保证代码在执行到block中,self不会被释放,当block执行完后,
会自动释放该strongSelf.
7. Block的循环引用.
说Block的循环引用之前,先说一下什么叫循环引用.
循环引用: 循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。若当前对象是一个ViewController,则在dismiss或者pop之后其dealloc无法被调用,在频繁的push或者present之后内存暴增,然后APP就duang地挂了。
Block的循环引用
block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题,一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,block的这种循环引用会被编译器捕捉到并及时提醒。
#import "Friend.h"
@interface Friend ()
@property (nonatomic) NSArray *arr;
@end
@implementation Friend
- (id)init
{
if (self = [super init]) {
self.arr = @[@111, @222, @333];
self.block = ^(NSString *name){
NSLog(@"arr:%@", self.arr);
};
}
return self;
}
属性引用,也就是block里面引用了self导致循环引用.但是通过实例化变量也是会导致循环引用.
属性变量导致的循环引用.png 实例变量导致的循环引用.png由此我们知道了,即使在你的block代码中没有显式地出现"self",也会出现循环引用!只要你在block里用到了self所拥有的东西!具体是这么处理Block的循环引用:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;//加一下强引用,避免weakSelf被释放掉
NSLog(@"%@", strongSelf->_xxView); //不会导致循环引用.
};
__block 本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。另外一点就是 __block 修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。
__weak 本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。
该段解释来源于 编程小翁作者的 <<iOS容易造成循环引用的三种场景,就在你我身边! >> 文字
(http://www.cnblogs.com/wengzilin/p/4347974.html)
7. Block和引用计数.
在block中访问对象,默认会retain,而添加了__block的对象不会被retain.
如果我们访问类的成员变量,或者通过类方法来访问对象,那么这些对象不会被retain,而类对象会被return,最常见的是self.
根据这个机制,如果我们将block用来传值,在block不用时,务必要置为nil,而在实现block的方法里,务必要释放.
该段解释转载于 珲少作者的<<iOS 中block结构的简单用法>> 文章
(https://my.oschina.net/u/2340880/blog/398154)
8. Block的截获.
block需要注意的一个特性就是"Variable Capturing",直译过来就是捕捉变量。block会将“捕捉”到的变量复制一份,然后对复制品进行操作,这是非常重要的一点。
二. Blcok的实践
1. Block处理三级页面的传值问题.
需求,页面A --跳转--> 页面B --跳转--> 页面C --返回-- 页面A. 用页面C的背景颜色改变页面A的背景颜色.
- 在页面C.h声明一个Block.并在C.m调用.
2.将控制器C作为B控制器的一个属性.方便在A控制器中调用.
控制器B.png3.在控制器A中获取到控制器C的block属性.
控制器A.png4.完毕.
2. Block的声明为一个属性.需要用copy修饰.
Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的.