一、Block介绍
1.1概念:
将函数及其执行上下文封装起来的对象 底层用struct实现
1.2block实现原理:
a .新建项目 代码放入file.m中 b.打开终端cd到项目目录下 c.敲clang -rewrite-objc file.m查看编译后的文件内容 本质是将OC重写为C后目录下多了个file.cpp文件
1)Block实际上是OC对于闭包(闭包是一个函数或指向函数的指针,再加上该函数执行的外部的上下文变量。)的实现。OC是对C语言的扩展,block的实现是基于函数和指向函数的指针,num__block修饰的局 全 静局 在block中输出结果都是修改后的。
编译器会将block的内部代码生成对**应的函数,可以看出函数的C语言实现,去掉类型转换能看出block在内部会作为一个指向结构体的指针,当调用block的时候其实是根据block对应的指针找到相应的函数进而调用并传入自身。block是能读取其它函数局部变量的匿名函数**。
block在iOS4后使用,不需要定义繁琐的协议方法,语法简洁,可在方法中定义实现,这样可访问方法中的局部变量,使代码更紧凑、结构化。但若通信事件较多建议用delegate。
block开发方便且能大幅度提高应用执行效率(多核CPU可直接处理Block指令)。block类似于C里面的函数指针,都可作为参数传递,用于回调。但block的实现可定义在方法中,函数则不可以。
内部结构:
struct Block_layout{
void *isa; //实现对象相关功能的指针,所有对象都有它
int flags; //按bit位表示附加信息
int reserved;//保留变量
void ( *invoke)(void *,...);//函数指针 ,指向具体block调用的函数地址
struct Block_descriptor *descriptor;//描述信息,如reserved,size大小,copy,dispose函数指针
/ *Imported variables */;//捕获过来被复制到结构体的变量或变量地址,block能访问它外部的局部变量
}
struct Block_descriptor{
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst,void src);
void ( *dispose)(void *);
}
1.3用法: typedef声 copy对象接参自动复制
使用实例:cocoaTouch框架下动画效果的Block的调用
1)使用typedef声明block :typedef void(^didFinishBlock) (NSObject *ob);这就声明了一个didFinishBlock类型的block,然后便可用。
2)copy属性 定义好的block成员变量:@property (nonatomic,copy) didFinishBlock finishBlock; 声明一个block对象,注意对象属性设置为copy,接到block 参数时,便会自动复制一份。
1.4作用:
实现两个对象之间的通信交互,并解耦合。除了用block,还可以用代理、通知、KVO。block**配合上** dispatch_queue**能方便地实现简单的多线程编程和异步编程。
1.5 使用block的好处:block 定参返 获状态 能修改 共享 简代封 并发遍回
Block除了能够1)定义参数列表、返回类型 外,还能够
2)获取被定义时的词法范围内的状态(比如局部变量),并且
3)在一定条件下(比如使用__block变量)能够修改这些状态。 此外,这些
4)可修改的状态在相同词法范围内的多个block之间是共享的,即便出了该词法范围(比如栈展开,出了作用域),仍可以继续共享或者修改这些状态。
通常来说,block都是一些简短代码片段的封装,适用作工作单元,通常
5)用来做并发任务、遍历、以及回调。
比如我们可以在遍历NSArray时做一些事情:
-(void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
其中将stop设为YES,就跳出循环,不继续遍历了。
而在很多框架中,block越来越经常被用作回调函数,取代传统的回调方式。
用block作为回调函数,可以
6)使得程序员在写代码更顺畅,不用中途跑到另一个地方写一个回调函数,有时还要考虑这个回调函数放在哪里比较合适。采用block,可以在调用函数时直接写后续处理代码,将其作为参数传递过去,供其任务执行结束时回调。
另一个好处,就是采用block作为回调,可以
7)直接访问局部变量。
比如我要在一批用户中修改一个用户的name,修改完成后通过回调更新对应用户的单元格UI。这时候我需要知道对应用户单元格的index,如果采用传统回调方式,要么需要将index带过去,回调时再回传过来;要么通过外部作用域记录当前操作单元格的index(这限制了一次只能修改一个用户的name);要么遍历找到对应用户。而使用block,则可以直接访问单元格的index。
二、截获变量
怎样理解block截获变量的特性?
基本数据类型局部变量 截获其值 (关键词:基数局变 值;对象局变 所有权修饰符 ; 指针 局静变 ;全局 静全局 不截获 )
对象局变 连同所有权修饰符一起截获
局部静态变量 指针形式截获
不截获全局变量、静态全局变量
clang -rewrite-objc-fobjc-arc file.m
num做为局部变量时加上 _ _block 修饰符、num做为全局变量以及num为静态局部变量时在block中输出结果是一样的,皆为被修改之后的值,而作为局部变量并且未加上__block的num在block中输出的值却还是未赋值之前的值。
iOS - 揭露Block的内部实现原理https://www.jianshu.com/p/92ea0753ecf4
三、__block修饰符
一般,对被截获变量基本数据 对象进行赋值需要加 _ _block修饰符
静态局部变量 全局 静态全局 不需要加 _ _block
_ _block修饰的变量变成对象
_ _block int multiplier变成
struct _Block _byref _multiplier _0{
void *isa;
_Block _byref _multiplier _0 _forwarding;
int flags;
int size;
Int multiplier; } 栈上的 _ _forwarding指针指向自身 _ _block变量
四、Block的内存管理
4.1block的几种类型
编译结果中会有impl.isa=&_NSConcreteStackBlock 这个isa标记block类型
在Objective-C语言中,一共有3种类型的block:全静不访外 栈函返回销 堆引0销
-
_NSConcreteGlobalBlock 全局的静态 保存在已初始化数据区block,不会访问任何外部变量(block里面没有局部变量的时候)。copy的结果什么也不做
-
_NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。copy的结果从栈到堆
-
_NSConcreteMallocBlock 保存在堆中的block,当引用计数为0时会被销毁。copy的结果增加引用计数
4.2block与_ _block的copy操作
block的copy操作:当变量作用域结束后,栈上block消失,堆上block还是存在。栈block进行copy操作 堆上的block没有其他成员变量指向它,与alloc变量但没有调用release效果一样,在MRC下会产生内存泄露
__block的copy操作: _ _block变量有个 _ _forwarding指针,copy操作后在堆上会产生同样的,栈上的 _ _forwarding会指向堆上的 _ _block变量,而堆上的 _ _forwarding会指向自身 _ _block变量
_ _forwarding存在的意义:不论在任何内存位置,都可以顺利的访问同一个 _ _block变量
栈上创建的局部变量通过__block修饰后变成对象 第一句,故multiplier=6不是对变量赋值,而是通过multiplier的 _ _forwarding指针对成员变量进行赋值。 _blk是某一对象的成员变量,赋值操作实际是对其进行copy,block就会在堆上有副本,通过栈上的 找到堆上的副本进行修改,改成6
五、Block的循环引用
5.1.1为什么block会产生循环引用?怎么解决?
block中对象类型的局部变量或成员变量,会连同所有权修饰符/属性关键字共同截获,所以block中会有strong类型的指针指向当前的对象,而当前对象对block又强引用,就会形成自循环引用。
解决方案:通过 _ _weak对其声明进行循环引用的消除。
5.1.2为什么用_ _weak所有权修饰符的变量就能避免循环引用?
在当前栈创建 _ _weak 所有权修饰符 修饰的weakArray,来指向原对象的 _Array成员变量,在block中使用weakArray,解除自循环引用。
5.2_ _block会导致循环引用吗?怎么解决?
如果定义了一个 _ _block说明符,ARC环境下可以断环解除循环引用,弊端:如果block一直得不到调用,就无法解除;MRC不会循环引用。
对象有一成员变量,持有Block,Block中使用了 _ _block变量,而 _ _block本身对原对象持有,这种叫大环引用。断开 _ _block对原对象的持有即在block内部加上对原对象的置nil操作,当调用该block后就会断环,若一直得不到调用就不会解除大环引用。
5.3遇到过哪些循环引用?怎样解决的?
block引起的循环引用
__block引起的循环引用
NSTimer
六、Block适用场合:排错举动消完
排序、错误回调、枚举回调、视图动画变换、消息监听回调、任务完成时回调处理
七、与delegate的区别:可轻高 声遵实 内效匿成属参 栈到堆易循环引用copy强对捕强 代理对weak直回没额外消耗C指查
优先使用block。如果回调函数很多,多于三个使用代理。如果回调的很频繁,次数很多,像UITableview,每次初始化、滑动、点击都会回调,使用代理。
1). block的代码**可读性更好。因为应用block和实现block的地方在一起。代理的声明和实现就分开来了,在两个类中。代理使用起来也更麻烦,因为要声明协议、声明代理、遵守协议、实现**协议里的方法。block不需要声明,也不需要遵守,只需要声明和实现就可以了。
2)block是一种轻量级的回调,可以直接访问上下文,由于block的代码是内联的,运行效率更高。block就是一个对象,实现了匿名函数的功能。所以我们可以把block当做一个成员变量、属性、参数使用,使用起来非常灵活。像用AFNetworking请求数据和GCD实现多线程,都使用了block回调。
3) blcok的运行成本高。block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是引用计数加1,使用完或者block置nil后才销毁。block容易造成循环引用,而且不易察觉。因为为了blcok不被系统回收,所以我们都用copy关键字修饰,实行强引用。block对捕获的变量也都是强引用,所以就会造成循环引用。delegate只是保存了一个对象指针(一定要用**week修饰delegate,不然也会循环引用),直接回调,没有额外消耗。就像C的函数指针,只多做了一个查表动作。**