Block 的总结

要理解Block的实现,要先理解runtime,然而理解Runtime要先理解C语言的结构体(可见我基础是TM有多差),

如下:

什么是结构体,结构体是通过已存在的类型来组合生成的数据类型,定义它的格式为:

1.结构体定义

struct 结构体名

结构体成员声明列表;

(结构体的成员,需要指明它的类型和名称)

};(结构体定义也是语句,所以也要加“;”号)

2.定义结构体变量

三种定义格式

(1)struct 结构体名 变量名

(2)定义结构体的同时定义结构体变量

如 struct Date {

int year,age,day;

}dt(变量名);

struct {      <-省去了结构体名,直接定义结构体变量

int year,age,day;

}dt(变量名);

也可以通过typedef机制,去掉struct 结构体名!

typedef struct Date MyDate;

typedef struct { int year,age, day }MyDate;

3.结构体变量初始化和赋值

Date dt = {2016,8,30};常量初始化

Date dt0 = dt;                        复制初始化

Date dt1;

dt1 = dt;                                  复制赋值

dt1 = {2016,8,30};//非法

*几种相关的数据类型及访问结构体成员的方式

结构体成员的表示方式

结构体变量名 . 结构成员名 ( . 是成员引用运算符)

结构指针 -> 结构成员名      ( -> 是箭头运算符)

(*结构指针). 结构成员名

结构体数组中元素的结构体成员表示方式:

结构体数组名 [下标]. 结构体成员

(*(结构体数组名+下标)). 结构体成员

(结构体数组名+下标)->结构体成员

Block的实质

我们会把代码通过Clang命令转换为中间代码来观察block的实现,探索它的本质。

打开终端,进入项目路径,然后敲入Clang的命令clang -rewrite-objc BlockClang.c。此时,Finder里多了个文件BlockClang.cpp,它正是转换后的中间代码。(clang 命令可以将 Objetive-C 的源码改写成 C / C++ 语言的,借此可以研究 block 中各个特性的源码实现方式。)

Block的实现:

structBlock_layout {

void*isa;

intflags;

intreserved;

void(*invoke)(void*, ...);

structBlock_descriptor *descriptor;

/* Imported variables. */

};

structBlock_descriptor {

unsignedlongintreserved;

unsignedlongintsize;

void(*copy)(void*dst,void*src);

void(*dispose)(void*);

};

从结构体中看到isa,所以OC处理Block是按照对象来处理的在iOS中,isa常见的就是_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock这3种。

Block捕捉外部变量

说到外部变量,我们要先说一下C语言中变量有哪几种。一般可以分为一下5种:

自动变量

函数参数 (非外部变量)

静态变量

静态全局变量

全局变量

我们先实验静态变量,静态全局变量,全局变量这3类。测试代码如下:

#import

intglobal_i =1;//全局变量

staticintstatic_global_j =2;//静态全局变量

intmain(intargc,constchar* argv[]) {

staticintstatic_k =3;//静态变量

intval =4;//自动变量

void(^myBlock)(void) = ^{

global_i ++;

static_global_j ++;

static_k ++;

NSLog(@"Block中 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);

};

global_i ++;

static_global_j ++;

static_k ++;

val ++;

NSLog(@"Block外 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);

myBlock();

return0;

}

运行结果

Block 外  global_i = 2,static_global_j = 3,static_k = 4,val = 5

Block 中  global_i = 3,static_global_j = 4,static_k = 5,val = 4//val因为没有__block修饰

我们在看下C语言的源码:

intglobal_i =1;

staticintstatic_global_j =2;

struct__main_block_impl_0 {

struct__block_impl impl;

struct__main_block_desc_0* Desc;

int*static_k;

intval;

__main_block_impl_0(void*fp,struct__main_block_desc_0 *desc,int*_static_k,int_val,intflags=0) : static_k(_static_k), val(_val) {

impl.isa= &_NSConcreteStackBlock;

impl.Flags= flags;

impl.FuncPtr= fp;

Desc = desc;

}

};

staticvoid__main_block_func_0(struct__main_block_impl_0 *__cself) {

int*static_k = __cself->static_k;// bound by copy

intval = __cself->val;// bound by copy

global_i ++;

static_global_j ++;

(*static_k) ++;

NSLog((NSString*)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);

}

staticstruct__main_block_desc_0 {

size_t reserved;

size_t Block_size;

} __main_block_desc_0_DATA = {0,sizeof(struct__main_block_impl_0)};

intmain(intargc,constchar* argv[]) {

staticintstatic_k =3;

intval =4;

void(*myBlock)(void) = ((void(*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));

global_i ++;

static_global_j ++;

static_k ++;

val ++;

NSLog((NSString*)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_1,global_i,static_global_j,static_k,val);

((void(*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

return0;

}

我们看到

在__main_block_impl_0中,可以看到静态变量static_k和自动变量val,被Block从外面捕获进来,成为__main_block_impl_0这个结构体的成员变量了。

再看下面这个构造函数

__main_block_impl_0( *fp,  __main_block_desc_0 *desc,int*_static_k,int_val,intflags=0) : static_k(_static_k), val(_val)

这个构造函数中,自动变量和静态变量被捕获为成员变量追加到了构造函数中。

我们还注意到

&static_k 静态变量传入的是地址

所以值也是被改变的

要明确,block外不会有很多变量,但只有是block内部用到的变量才会被block捕获,创建相应的结构体内部变量。

我们可以发现,系统自动给我们加上的注释,bound by copy,自动变量val虽然被捕获进来了,但是是用 __cself->val来访问的。Block仅仅捕获了val的值,并没有捕获val的内存地址。所以在__main_block_func_0这个函数中即使我们重写这个自动变量val的值,依旧没法去改变Block外面自动变量val的值。

自动变量(局部变量)是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并内存地址,所以Block内部不能改变自动变量的值。

我们继续分析 静态变量,全局变量和静态全局变量被改变的原因:

静态全局变量,全局变量由于作用域的原因,于是可以直接在Block里面被改变。他们也都存储在全局区。

静态变量传递给Block是内存地址值,所以能在Block里面直接改变值。

总结一下在Block中改变变量值有2种方式,一是传递内存地址指针到Block中,二是改变存储区方式(__block)。

OK下面在分析下Block的Copy问题

OC中,一般Block就分为以下3种,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock。

1.从捕获外部变量的角度上来看

_NSConcreteStackBlock:

只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。

StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。

_NSConcreteMallocBlock:有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制

_NSConcreteGlobalBlock:没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束。

没有用到外部变量肯定是_NSConcreteGlobalBlock,这点很好理解。不过只用到全局变量、静态变量的block也是_NSConcreteGlobalBlock。

只用到全局变量、静态变量的block也可以是_NSConcreteGlobalBlock。

2.从持有对象的角度上来看:

_NSConcreteStackBlock是不持有对象的。

_NSConcreteMallocBlock是持有对象的。

_NSConcreteGlobalBlock也不持有对象

以下4种情况,系统都会默认调用copy方法把Block赋复制

1.手动调用copy

2.Block是函数的返回值

3.Block被强引用,Block被赋值给__strong或者id类型

4.调用系统API入参中含有usingBlcok的方法

__block 的实现

带有__block的变量被转化为一个结构体__Block_byref_i_0,这个结构体有5个成员变量,第一个是isa指针,第二个是指向自身类型的__forwarding指针,第三个是一个标记flag,第四个是它的大小,第五个是变量值,名字和变量名同名。

MRC

Block在捕获住__block变量之后,并不会复制到堆上,所以地址也一直都在栈上。这与ARC环境下的不一样。

ARC

ARC环境下,一旦Block赋值就会触发copy,__block就会copy到堆上,Block也是__NSMallocBlock。ARC环境下也是存在__NSStackBlock的时候,这种情况下,__block就在栈上。

__forwarding 在栈上和堆上的作用不同

__forwarding指针这里的作用就是针对堆的Block,把原来__forwarding指针指向自己,换成指向_NSConcreteMallocBlock上复制之后的__block自己。然后堆上的变量的__forwarding再指向自己。这样不管__block怎么复制到堆上,还是在栈上,都可以通过(i->__forwarding->i)来访问到变量值。

然而当Block在MRC下时候,我们不手动Copy的话,Block 依然在栈上,这时__forwarding 指针就只指向自己了。

Block 循环应用问题

(严格的来说,捕获是必须在Block结构体__main_block_impl_0里面有成员变量的话,Block能捕获的变量就只有带有自动变量和静态变量了。)

带__block的自动变量 和 静态变量 就是直接地址访问。所以在Block里面可以直接改变变量的值。

静态全局变量,全局变量,函数参数他们并不会被Block持有,也就是说不会增加retainCount值。

@interface AViewController:UIViewController

void (^ myBlock)(id myObj);

id _obj;

@end

@implementation AViewController

- (id)init {

if (self = [super init]){

__weak AviewController *weakSelf = self;

myBlock = ^{ NSLog(@“ Who is Block retainer : %@ %@“,weakSelf, _obj) };

- (void)dealloc {

NSLog (@“ AVcdealloc”);

}

@end

@implementation BViewController

- (void )viewDidLoad {

id obj = [AViewcontroller new];

NSLog(@“What is obj %@“, obj);

}

//此处 在Block语法内使用 _obj 实际上截获了self,因为对于编译器来说_obj只不过是对象结构体的成员变量。

一般我们使用__weak 来避免循环引用,其实我们也可以使用__block 来解决 但是在block 内部一定要对__block变量赋值nil,以消除block变量对 对象的引用

(对象引用Block,block变量引用对象同时引用Block)

使用__block 有三个优点

1.通过block变量可以控制对象的持有期间。

2.在不能使用__weak修饰符的环境中,不使用__unsafe_unretained修饰符也可以。不用担心悬垂指针。

(在执行block时可以动态地决定是否将nil或者其他对象爱哪个赋值在block变量中。)

缺点:

1.为避免循环引用必须执行Block;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,667评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,361评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,700评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,027评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,988评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,230评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,705评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,366评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,496评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,405评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,453评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,725评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,803评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,015评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,514评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,111评论 2 341

推荐阅读更多精彩内容