Block 本质、实现原理、内存管理、循环引用、__block等

一、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销

  1. _NSConcreteGlobalBlock 全局的静态 保存在已初始化数据区block,不会访问任何外部变量(block里面没有局部变量的时候)。copy的结果什么也不做

  2. _NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。copy的结果从栈到堆

  3. _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的函数指针,只多做了一个查表动作。**

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值