神奇的 Block

本文不做Block的基本介绍和底层实现原理,有兴趣的同学直接戳这篇文章(http://www.jianshu.com/p/51d04b7639f1),写得灰常好,本文只在应用层面上带领读者进行思考,并整理出一些结论.这些结论是我从书上和上网资料收集所得,并通过实践进行验证而来,希望能和高手们共同探讨 :)

在看例子之前,至少要知道block有几个类型.

  • _NSConcreteGlobalBlock(全局块)
  • _NSConcreteStackBlock(栈块)
  • _NSConcreteMallocBlock(堆块)

废话不说,直接看例子.测试环境为ARC,就不做MRC的测试了.

精神病入门

例子一:

typedef void (^blk_t) ();

int main(int argc, const char * argv[]) {

blk_t block = ^{

printf("I‘m just a block\n");

};

block();

return 0;

}

很简单的一段代码,执行block之后结果是I‘m just a block.但如果问你,这个block是什么类型的block,你会怎么回答?

在代码中打一个断点,通过打印block的isa,可以知道该block是什么类型的.

第一步:打个断点

第二步:打印isa

然后就能看到结果了:

看到结果,尼玛居然是个全局块,可是我明明是在栈上创建的一个block呀!

再来看一个例子,这时定义了一个局部变量,并在block中使用了这个局部变量.

例子二:

typedef void (^blk_t) ();

int main(int argc, const char * argv[]) {

int i = 1;

blk_t block = ^{

printf("%d\n",i);

};

block();

return 0;

}

按照以上步骤再看看block的isa

……我去,怎么成堆块了?

别急,再举个??.

例子三:

typedef void (^blk_t) ();

int main(int argc, const char * argv[]) {

int i = 1;

__weak blk_t block = ^{

printf("%d\n",i);

};

block();

return 0;

}

虽然编译器在__weak blk_t block = ^{这行爆出了警告,但是程序还是能够正常运行.block并未因为一个弱引用立即释放.然后看看结果:

终于看到栈块了,全家福终于齐人了.

下面开始总结了.

在哪些情况下,Block为_NSConcreteGlobalBlock类对象?

  • 记述全局变量的地方创建的Block,比如下面的例子.

blk_t block = ^{

printf("I‘m just a block");

};

int main(int argc, const char * argv[]) {

block();

return 0;

}

  • 不截获自动变量的时候.

例子一这种情况下.虽然是在栈上创建的一个block,但由于闭包内不截获外部的自动变量(局部变量),将会被编译器编译为_NSConcreteGlobalBlock.

再来总结一下第二个例子.之所以是一个堆块,是因为编译器为块进行了copy操作(实质上是调用_Block_copy函数).以下方式会让块从栈复制到堆上.

  • 调用Block的copy实例方法.

[^{

printf("a heap block");

} copy];

// 对block调用copy,会把栈上的block复制到堆上.

  • 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时.

也就是说,有个__strong修饰的变量指向这个block就会让编译器为block调用copy方法.

例子二就是将Block赋值给了一个__strong(默认都是strong)修饰的Block类型成员变量——blk_t block.

因此,在例子三中,我将强引用变成弱引用,创建了一个栈上的block.虽然编译器会有警告,因为编译器在这里可能还不知道那个块也是栈上的,而这个栈上的块,显然不会立即释放.

  • Block作为函数返回值时

例如:

blk_t return_A_Block(){

int val = 10;

return ^{NSLog(@"%d",val);};

}

int main(int argc, const char * argv[]) {

NSLog(@"%@",return_A_Block());

return 0;

}

打印所得是一个堆块.

Block的副本

Block的类 副本源的配置存储域 复制效果
_NSConcreteStackBlock 从栈复制到堆
_NSConcreteGlobalBlock 程序的数据区域 什么也不做
_NSConcreteMallocBlock 引用计数增加

精神病进阶

例子四:

typedef void (^blk_t) (id obj);

int main(int argc, const char * argv[]) {

blk_t blk;

{

id array = [[NSMutableArray alloc] init];

blk = ^(id obj){

[array addObject:obj];

NSLog(@"%ld",[array count]);

};

}

// array超出了作用域,在括号外已经不能被使用了

blk([NSObject new]);

blk([NSObject new]);

blk([NSObject new]);

return 0;

}

打印台输出结果:

可以看到,在超出了作用域后,array依旧能够被访问到.

例子五:

int main(int argc, const char * argv[]) {

blk_t blk;

{

id array = [[NSMutableArray alloc] init];

id __weak array2 = array;

blk = ^(id obj){

[array2 addObject:obj];

NSLog(@"%ld",[array2 count]);

};

}

blk([NSObject new]);

blk([NSObject new]);

blk([NSObject new]);

return 0;

}

打印台结果:

大相径庭的结果.

在例子四中,Block中截获了外部的自动变量,并且根据上面说过的结论,编译器为我们调用了copy方法,这个Block是个堆块.

我们将例子四稍加改写,将块改为栈块(即不让编译器为我们调用copy方法):

int main(int argc, const char * argv[]) {

__weak blk_t blk;

{

id array = [[NSMutableArray alloc] init];

blk = ^(id obj){

[array addObject:obj];

NSLog(@"%ld",[array count]);

};

}

blk([NSObject new]);

blk([NSObject new]);

blk([NSObject new]);

return 0;

}

打印出来的结果和例子五一致.

从该例子得出的结论是:

  • 只有调用了Block的copy方法,才能持有截获的附有__strong修饰符的对象类型的自动变量值.

基于这个结论,我们还可以得出,在ARC环境下,定义block类型的属性时,可以用strong,并不是非得用copy才是正确的.

// 两者效果一样

@property (strong, nonatomic)     blk_t     *block;

@property (copy, nonatomic)     blk_t     *block;

在例子五中,虽然截获的自动变量是__weak修饰符修饰的对象类型.但是作用域过后array被释放,nil被赋值给了array2,并不能持有对象.这让我们想起了平时为了防止循环引用,我们会用一个弱指针指向self,并让block捕获弱指针而不是让block持有self.

注意,即使你不使用self.object访问实例变量,而是通过_object访问,也同样会造成循环引用.因为无论用什么形式访问实例变量,经过编译后,最终都会转换成self+变量内存偏移的形式来进行访问,还是会造成循环引用.

那么,截获和__block修饰有何不同呢?

block本质也是一个结构体,截获的对象会成为结构体成员的一部分.

例如:

int main(int argc, const char * argv[]) {

id obj;

^{

obj;

};

return 0;

}

其中生成的block会是这样子的:

struct __main_block_impl_0 {

struct __block_impl impl;

struct __Person__test_block_desc_0* Desc;

id __strong obj;

};

注:在C语言中,结构体不能含有附有__strong修饰的变量.因为编译器不知道应何时进行C语言结构体的初始化和废弃操作,不能很好的管理内存.

而OC却可以,它能够准确的把握block从栈复制到堆以及堆上的block被废弃的时机.

如果是通过__block修饰的一个变量呢?

int main(int argc, const char * argv[]) {

__block int a;

^{

a = 10;

};

return 0;

}

其中生成的block会是这样子的:

struct __main_block_impl_0 {

struct __block_impl impl;

struct __Person__test_block_desc_0* Desc;

__Block_byref_a_0 *a; // by ref

};

此时,被__block修饰的变量变成了一个结构体(__Block_byref_a_0类型).至于这个结构体又长什么样,就不贴代码了,只需知道a被包装到了这个结构体中,成为其中一个成员变量,其他成员变量描述了该结构体的一些信息.

  • 当Block被拷贝到堆上的时候,附有__strong修饰的变量因为Block结构体内有强指针持有,使得该指针所指向的对象在作用域外还有引用计数,因此存活着.
  • 当Block被拷贝到堆上的时候,被__block修饰的变量被包装到了一个新的结构体中,被block结构体持有,该结构体跟随Block也被拷贝到堆上了.
  • 截获的方式并不能修改截获的变量本身,而__block修饰的方式却可以,因为它本质是复制了一份该变量.

根据结论,可以知道用__block修饰的方式也能够避免循环引用.只要在块中将需要避免循环引用的变量置为nil.

如:

- (id)init{

self = [super init];

__block id tmp = self;

blk_t blk = ^{

tmp.name = @"ye";

tmp = nil;

}

return self;

}

如果最后不置为nil,那么self持有block结构体,block结构体持有__block变量结构体,__block变量结构体持有self,只有在最后将_block变量结构体中的self置空,才能手动破除循环.这个方式比weak方式优点的地方在于可控制对象的持有期间.

时间: 2024-08-24 14:37:18

神奇的 Block的相关文章

由浅至深学习block

关于block 在iOS 4.0之后,block横空出世,它本身封装了一段代码并将这段代码当做变量,通过block()的方式进行回调.这不免让我们想到在C函数中,我们可以定义一个指向函数的指针并且调用: bool executeSomeTask(void) {     //do something and return if success or not } bool (*taskPoint)(void); taskPoint = something; 上面的函数指针可以直接通过(*taskPo

iOS开发-由浅至深学习block

关于block 在iOS 4.0之后,block横空出世,它本身封装了一段代码并将这段代码当做变量,通过block()的方式进行回调.这不免让我们想到在C函数中,我们可以定义一个指向函数的指针并且调用: 1 2 3 4 5 bool executeSomeTask(void) {     //do something and return if success or not } bool (*taskPoint)(void); taskPoint = something; 上面的函数指针可以直接

由浅到深的了解block

作为IOS开发者来说,你不会用block,真的就不能和其他小伙伴一块玩耍了,block运用随处可见,block这样的语法真的看起来很诡异,那么这个神奇的block(闭包)到底是何方神圣呢?各位看官,不要讲话,保留疑问,先看文章摘要,please静静的感受一下它的用法吧, go!! 本文章的概要 1. block的基本应用 2. block的typedef 3. block的作用 4. block运用过程中出现的问题以及如何规避 5. 深入了解block block的基本应用 直观感受block跟

OC学习之进阶

1.内存管理 ? ? ? ? 概念,当alloc出一个对象,其构成为 对象自身+retainCount ,这个retainCount是对象计数器 .判断对象要不要回收的唯一依据(存在一种例外:对象值为nil时,引用计数为0,但不回收 空间)就是计数器是否为0,若不为0则存在.对象被销毁前会给系统发送dealloc,当然可以重写此函数.dealloc不能直接调用,如果意见被回收过的对象再次使用会报野指针(Zombie).在diagnostics中可以开启Zombie监测. ? ? ? ? Obje

项目问题总结:Block内存泄露 以及NSTimer使用问题

BLock的内存泄露 在我们代码中关于block的使用可以说随处可见,第一次接触block的时候是关于UIView的块动画,那时觉得block的使用好神奇,再后来分析总结为block其实就是一个c语言函数,只是我们可以在任意处调用此函数.有了这样的理解我开始经常使用block.在做项目以后发现使用block竟然会引起内存泄露,于是开始自己调试研究block的内存管理问题. 普通的block使用(包括块动画) 这里有一个简单的block使用,在里面我们可以添加任何自己想进行的操作,大部分的使用也是

Javascript的坑(一)---------- block statement scope

在ECMAScript 6之前,Javascript是没有block statement scope的..... 这就导致了诡异的现象,比如下面的代码 var x = 1; { var y = 2; } console.log(y); // outputs 2 简直神奇..... 现在有了ECMAScript 6,代码就可以这样写 var x = 1; { let y = 2; } console.log(y); // ReferenceError: y is not defined 嗯,这样就

BFC 神奇背后的原理

BFC已经是一个耳听熟闻的词语了,网上有许多关于BFC的文章,介绍了如何触发BFC, 以及BFC的一些用处(如清浮动,防止margin重叠等).虽然我知道如何利用BFC解决这些问题,但当别人问我BFC是什么,我还是不能很有底气地解释清楚.于是这两天仔细阅读了CSS2.1 spec, 和许多文章,来全面地理解BFC: BFC是个什么? 哪些元素会生成BFC BFC的神奇的作用,及背后的原理 一.BFC是什么? 在解释BFC是什么之前,需要先介绍Box, Formatting context的概念.

iOS中block实现的探究

[0. Brief introduction of block] Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C语言的扩展,用来实现匿名函数的特性. 用维基百科的话来说,Block是Apple Inc.为C.C++以及Objective-C加入的特性,使得这些语言能够用类lambda表达式的语法来创建闭包. 用Apple文档的话来说,A block is an anonymous inline collection of code, and sometimes also

前端精选文摘:BFC 神奇背后的原理

一.BFC是什么? 在解释 BFC 是什么之前,需要先介绍 Box.Formatting Context的概念. Box: CSS布局的基本单位 Box 是 CSS 布局的对象和基本单位, 直观点来说,就是一个页面是由很多个 Box 组成的.元素的类型和 display 属性,决定了这个 Box 的类型. 不同类型的 Box, 会参与不同的 Formatting Context(一个决定如何渲染文档的容器),因此Box内的元素会以不同的方式渲染.让我们看看有哪些盒子: block-level b