Block 再学习 !
如何优雅的使用 Block?
How Do I Declare A Block in Objective-C?
阮一峰的一句话解释简洁明了:闭包就是能够读取其它函数内部变量的函数
详情:http://blog.csdn.net/jasonblog/article/details/7756763
block的几种适用场合:
- 任务完成时回调处理
- 消息监听回调处理
- 错误回调处理
- 枚举回调
- 视图动画、变换
- 排序
作为基本变量 As a local variable
1 |
|
作为对象属性 As a property
1 |
|
作为方法参数 As a method parameter
1 |
|
作为回调 As an argument to a method call
:
1 |
|
作为类型别名 As atypedef
1 |
|
什么是Block
- Block是iOS中一种比较特殊的
数据类型
- 是一个能工作的代码单元,可以在
任何需要的时候
被执行 - 本质是轻量级的
匿名函数
,可以作为其他函数的参数或者返回值。 - Block是苹果官方特别推荐使用的数据类型, 应用场景比较广泛
- 动画
- 多线程
- 集合遍历
- 网络请求回调
- Block的作用
- 用来保存某一段代码, 可以在恰当的时间再取出来调用
- 功能类似于函数和方法
1 |
|
Block的格式
- Block的定义格式
1 |
|
- block最简单形式(无参无返回值)
1 |
|
- block中级进阶形式(有参无返回值)
1 |
|
- block高级进阶形式(有参有返回值)
1 |
|
- 调用Block保存的代码
1 |
|
Block 与 变量
观察下面四段代码的
输出值
- Block 可以读取变量,但是默认情况下不能修改变量的值
1 |
|
- Block 能读取变量的原理是 「copy」了一份变量的值。所以在 Block 定义之后修改变量的值,再调用 Block,值依旧是修改前的。换句话说,定义好 Block 之后,修改变量值对 Block 无效。
1 |
|
__blcok
关键字的神奇功效。
首先,如果需要对block 外部定义的变量在 block 内修改,那么需要对这个变量添加一个
__block
修饰。
1 |
|
如果需要在调用之前,变量的修改都会影响 block 内部对这个变量的使用,换句话说,block 对变量不再是简单的值复制,而是动态的"监听"值的变化,然后在调用的时候读取变量的值。需要对这个变量添加一个
__block
修饰。
1 |
|
- 注意区别
变量
和指针所指向的变量
1 |
|
这里的
[str appendString:@"wong"];
不报错是因为str 是指向@"Damon"
的函数指针,[str appendString:@"wong"];
并不是修改 str 存储的值,本质上是 str 向@"Damon"
发送了一条appendString 消息,然后再更改@"Damon"
为@"Damonwong"
,而 str 存储的指向@"Damonwong"
对象的指针没有发生变化。所以,block 本质是不能修改变量存储的值,但是消息分发依旧有效。
Block 的循环引用问题
虽然 Block 用起来特别方便,但是要特别注意循环应用的问题。
1 |
|
由于 self 是 __strong 修饰,在 ARC 下,当编译器自动将代码中的 block 从栈拷贝到堆时,
block 会强引用和持有 self
,而self 恰好也强引用和持有了 block
,就造成了传说中的循环引用。为了避免这种情况发生,可以在变量声明时用
__weak
修饰符修饰变量 self,让 block 不强引用 self,从而破除循环。
1 |
|
黑科技,防止循环引用
1 |
|
Tips
宏定义:#define Weak_Ref(obj) _weak typeof(obj) weak##obj = obj;
注意
self.name
这类点语法,[self name]
消息传递及self
。block 中使用 self
不一定
造成循环引用,但可能性极大
重写
dealloc
方法可以很方便的知道是否存在循环引用Block 在内存中的位置
由于block也是NSObject,我们可以对其进行retain操作。不过在将block作为回调函数传递给底层框架时,底层框架需要对其copy一份。比方说,如果将回调block作为属性,不能用retain,而要用copy。我们通常会将block写在栈中,而需要回调时,往往回调block已经不在栈中了,使用copy属性可以将block放到堆中。或者使用Block_copy()和Block_release()。
- 根据Block在内存中的位置分为三种类型
- NSGlobalBlock:类似函数,位于text段,全局的静态 block,不会访问任何外部变量
- NSStackBlock :保存在栈中的 block,当函数返回时会被销毁
- NSMallocBlock:保存在堆中的 block,当引用计数为 0 时会被销毁。
MRC 下的 Block
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/*------------MRC-----------------*/
typedef long (^MyBlock)(int, int); MyBlock block1 = ^ long (int a, int b) {
return a + b;
};
NSLog(@"block1 = %@", block1);
// block1 = <__NSGlobalBlock__: 0x47d0> int base = 100;
MyBlock block2 = ^ long (int a, int b) {
return base + a + b;
};
NSLog(@"block2 = %@", block2);
// block2 = <__NSStackBlock__: 0xbfffddf8> MyBlock block3 = [[block2 copy] autorelease];
NSLog(@"block3 = %@", block3);
// block3 = <__NSMallocBlock__: 0x902fda0>
block1
没有使用任何外部变量,因此存储在 代码区,编译器给其的类型为NSGlobalBlock
block2
使用到了局部变量,在定义(注意是定义,不是运行)block2时,局部变量base当前值被copy到栈上,作为常量供Block使用。编译器给其类型为NSStackBlock
block3
经过拷贝,局部变量 base 的值被 copy 到堆中,编译器给其类型为NSMallocBlock
总结说来,block 的类型取决于内部使用的变量在哪。ARC 下的 Block
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/*------------ARC-----------------*/
typedef long (^MyBlock)(int, int);
MyBlock block1 = ^ long (int a, int b) {
return a + b;
};
NSLog(@"block1 = %@", block1);
// block1 = <__NSGlobalBlock__: 0x100001080> int base = 100;
MyBlock block2 = ^ long (int a, int b) {
return base + a + b;
};
NSLog(@"block2 = %@", block2);
// block2 = <__NSMallocBlock__: 0x100203cf0> __block int sum = 100;
MyBlock block3 = ^ long (int a, int b) {
return sum + a + b;
};
NSLog(@"block3 = %@", block3);
// block3 = <__NSMallocBlock__: 0x100207100>
因为 ARC 下,编译器帮我们管理内存,所以只要内部调用了外部变量,编译器都会 copy 一份变量到heap 中,并增加引用计数。 所以
block2
和block3
的类型都是NSMallocBlock。其余和 MRC 一样。
Tops:
以下情况,block 会拷贝到堆:
当 block 调用 copy 方法时,如果 block 在栈上,会被拷贝到堆上;
当 block 作为函数返回值返回时,编译器自动将 block 作为 _Block_copy 函数,效果等同于 block 直接调用 copy 方法;
当 block 被赋值给 _strong id 类型的对象或 block 的成员变量时,编译器自动将 block 作为Block_copy 函数,效果等同于 block 直接调用 copy 方法;
当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。这些方法会在内部对传递进来的 block 调用 copy 或 _Block_copy 进行拷贝;
Objective-C Blocks Quiz
Example A
1
2
3
4
5
6void exampleA() {
char a = 'A';
^{
printf("%cn", a);
}();
}
Always works
Explain
This always works. The stack for exampleA doesn’t go away until after the block has finished executing. So whether the block is allocated on the stack or the heap, it will be valid when it is executed.
函数exampleA不会消失,直到 block 运行结束,所以不管 block 在堆中还是栈中,它都可以运行。
Example B
1
2
3
4
5
6
7
8
9
10
11
12
13void exampleB_addBlockToArray(NSMutableArray *array) {
char b = 'B';
[array addObject:^{
printf("%cn", b);
}];
} void exampleB() {
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
Only works in ARC
Explain
Without ARC, the block is an NSStackBlock allocated on the stack of exampleB_addBlockToArray. By the time it executes in exampleB, the the block is no longer valid, because that stack has been cleared.
With ARC, the block is properly allocated on the heap as an autoreleased NSMallocBlock to begin with.
在 MRC中,这里的block 存在栈中,所以在执行exampleB函数的exampleB_addBlockToArray(array);之后,b 变量变得无效,所以[array objectAtIndex:0]不能成功。 在 ARC 中,这个 block 存在堆中,当运行到[array objectAtIndex:0],block 还没被释放,所以可以运行。
Example C
1
2
3
4
5
6
7
8
9
10
11
12void exampleC_addBlockToArray(NSMutableArray *array) {
[array addObject:^{
printf("Cn");
}];
} void exampleC() {
NSMutableArray *array = [NSMutableArray array];
exampleC_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
Always works
Explain
Since the block doesn’t capture any variables in its closure, it doesn’t need any state set up at runtime. it gets compiled as an NSGlobalBlock. It’s neither on the stack nor the heap, but part of the code segment, like any C function. This works both with and without ARC.
因为这里的 block 是全局的NSConcreteGlobalBlock,所以不管是 ARC 还是 MRC 都是可以用的。
Example D
1
2
3
4
5
6
7
8
9
10
11
12typedef void (^dBlock)(); dBlock exampleD_getBlock() {
char d = 'D';
return ^{
printf("%cn", d);
};
} void exampleD() {
exampleD_getBlock()();
}
Only works in ARC
Explain
This is similar to example B. Without ARC, the block would be created on the stack of exampleD_getBlock and then immediately become invalid when that function returns. However, in this case, the error is so obvious that the compiler will fail to compile, with the error error: returning block that lives on the local stack.
With ARC, the block is correctly placed on the heap as an autoreleased NSMallocBlock.
在 MRC 时,类似于example B,block 会被创建在栈中,所以当block 返回时,马上失效。在这种情况下,错误是显而易见的,编译器无法编译成功。返回一个错误:returning block that lives on the local stack。 在 ARC 中,当自动释放池销毁,block 才失效
Example E
1
2
3
4
5
6
7
8
9
10
11
12
13
14typedef void (^eBlock)(); eBlock exampleE_getBlock() {
char e = 'E';
void (^block)() = ^{
printf("%cn", e);
};
return block;
} void exampleE() {
eBlock block = exampleE_getBlock();
block();
}
Only works in ARC
Explain
This is just like example D, except that the compiler doesn’t recognize it as an error, so this code compiles and crashes. Even worse, this particular example happens to work fine if you disable optimizations. So watch out for this working while testing and failing in production.
With ARC, the block is correctly placed on the heap as an autoreleased NSMallocBlock.
这题类似于example D。在 MRC 会造成崩溃。
Block_copy & copy
[block copy] 和 Block_copy(block)不等效。block 的赋值不是简单的拷贝,所以要拷贝最好使用 Block_copy()这个宏。
一、根据需求提出问题
- 请耐心把这篇文章看完,你对 Block 会有更深刻的了解。
- 这里直接用一个需求来探究循环引用的问题:如果我想在Block中延时来运行某段代码,这里就会出现一个问题,看这段代码:
- (void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakPerson test];
});
};
person.mitBlock();
}直接运行这段代码会发现
[weakPerson test];
并没有执行,打印一下会发现,weakPerson 已经是 Nil 了,这是由于当我们的viewDidLoad
方法运行结束,由于是局部变量,无论是 MitPerson 和 weakPerson 都会被释放掉,那么这个时候在 Block 中就无法拿到正真的 person 内容了。- 按如下方法修改代码:
- (void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};
person.mitBlock();
}这样当2秒过后,计时器依然能够拿到想要的 person 对象。
二、深入探究原理
- 这里将会对每行代码逐步进行说明
1、开辟一段控件存储 person 类对象内容,创建 person 强指针。
MitPerson*person = [[MitPerson alloc]init];2、创建一个弱指针 weakPerson 指向person对象内容
__weak MitPerson * weakPerson = person;person.mitBlock = ^{
3、在 person 对象的 Block 内部创建一个强指针来指向 person 对象,为了保证当计时器执行代码的时候,person 对象没有被系统销毁所以我们必须在系统内部进行一次强引用,并用 GCD 计时器引用 strongPerson,为了保留 person 对象,在下面会对这里更加详细的说明。
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};4、执行 Block 代码
person.mitBlock();- 下面将详细分析一下下面这段代码:
person.mitBlock = ^{
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};- 首先需要明白一些关于 Block 的概念:
- 1、默认情况下,block 是放在栈里面的
- 2、一旦blcok进行了copy操作,block的内存就会被放在堆里面
- 3、堆立面的block(被copy过的block)有以下现象
- 1> block内部如果通过外面声明的强引用来使用,那么block内部会自动产生一个强引用指向所使用的对象。
- 2> block内部如果通过外面声明的弱引用来使用,那么block内部会自动产生一个弱引用指向所使用的对象。
- 我们进行这段代码的目的:
- 首先,我们需要在 Block 块中调用,person 对象的方法,既然是在 Block 块中我们就应该使用弱指针来引用外部变量,以此来避免循环引用。但是又会出现问题,什么问题呢?就是当我计时器要执行方法的时候,发现对象已经被释放了。
- 接下来就是为了避免 person 对象在计时器执行的时候被释放掉:那么为什么 person 对象会被释放掉呢?因为无论我们的person强指针还是 weakPerson 弱指针都是局部变量,当执行完ViewDidLoad 的时候,指针会被销毁。对象只有被强指针引用的时候才不会被销毁,而我们如果直接引用外部的强指针对象又会产生循环引用,这个时候我们就用了一个巧妙的代码来完成这个需求。
- 首先在 person.mitBlock 引用外部 weakPerson,并在内部创建一个强指针去指向 person 对象,因为在内部声明变量,Block 是不会强引用这个对象的,这也就在避免的 person.mitBlock 循环引用风险的同时,又创建出了一个强指针指向对象。
- 之后再用 GCD 延时器 Block 来引用相对于它来说是外部的变量 strongPerson ,这时延时器 Block 会默认创建出来一个强引用来引用 person 对象,当 person.mitBlock 作用域结束之后 strongPerson 会跟着被销毁,内存中就仅剩下了 延时器 Block 强引用着 person 对象,2秒之后触发 test 方法,GCD Block 内部方法执行完毕之后,延时器和对象都被销毁,这样就完美实现了我们的需求。
- 最后再用一张图来阐述各个指针、Block 与对象之间的关系
黑色代表强引用,绿色代表弱引用
- 总结:person.mitBlock 中创建 strongPerson 是为了能够使 GCD Block 保存 person 对象,创建 strongPerson 时候使用 weakPerson 是为了避免 mitBlock 直接引用外部强指针变量所造成的循环引用。
Block循环引用.png
Block 再学习 !的更多相关文章
- 八.OC基础加强--1.autorelease的用法 2.ARC下内存管理 3.分类(category)4.block的学习
1.autorelease的用法 1.自动释放池及autorelease介绍 (1)在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的. (2)当一个对象调用auto ...
- js再学习笔记
#js再学习笔记 ##基本 1.js严格区分大小写 2.js末尾的分号可加,也可不加 3.六种数据类型(使用typeof来检验数据的类型) `typeof` - undefined: `var ...
- express再学习
对比spring,django,再学习express就有很多共通的地方啦... 看的书是一本小书,<express in action>,排版比较好. 昨天开始看,看了快四分之一啦... ...
- Android再学习-20141023-Intent-Thread
20141023-Android再学习 Intent对象的基本概念 Intent是Android应用程序组件之一 Intent对象在Android系统中表示一种意图 Intent当中最重要的内容是ac ...
- Android再学习-20141022-Activity的生命周期
20141022-Android再学习 如何在一个应用程序当中定义多个Activity 定义一个类,继承Activity 在该类当中,复写Activity当中的onCreate方法.onCreate( ...
- Android再学习-20141018-布局-进度条
20141018-Android再学习 对齐至控件的基准线 为了保证印刷字母的整齐而划定的线(四线三格的第三条线). android:layout_alignBaseline 与父控件的四个边缘对齐( ...
- 再学习sqlhelper
在机房收费重构系统的时候,第一次学习sqlhelper.当时感觉比较简单,没有写博客总结,现在又经过了图书馆的学习,感觉还是有必要写一写的. SqlHelper是一个基于.NETFramework的数 ...
- c/c++再学习:常用字符串转数字操作
c/c++再学习:常用字符串转数字操作 能实现字符串转数字有三种方法,atof函数,sscanf函数和stringstream类. 具体demo代码和运行结果 #include "stdio ...
- c/c++再学习:C与Python相互调用
c/c++再学习:Python调用C函数 Python 调用C函数比较简单 这里两个例子,一个是直接调用参数,另一个是调用结构体 C代码 typedef struct { int i1; int i2 ...
随机推荐
- iOS不可变数组的所有操作
#pragma mark 创建数组 //1.通过对象方法创建数组 NSArray * array = [[NSArray alloc]initWithObjects:@"One", ...
- 编写一个闹钟和定时关机工具(MFC VS2010)
这个小工具在自己生活当中能用到,运行软件以后,会显示当前的系统时间,然后你可以设定时间,再选择是定时响铃还是关机.截图如下: 前言:本程序采用visual studio 2010 ,对话框类型的应用程 ...
- hdu_4826_Labyrinth_2014百度之星(dp)
题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=4826 题意:中文题,不解释 题解:dp搞,第一列只能从上往下走,所以先算出第一列的dp数组,然后开两个 ...
- JAVA EE 运行环境配置(包含JAVA SE)
JAVA EE 运行环境配置(包含JAVA SE) 1.下载并安装jre-7u7-windows-i586.exe (最新的JAVA运行环境) 2.下载并安装java_ee_sdk-6u4-jdk7- ...
- SSH登录很慢问题的解决方法
用ssh连其他linux机器,会等待10-30秒才有提示输入密码.严重影响工作效率. 关闭ssh的gssapi认证 用ssh -v user@server 可以看到登录时有如下信息: debug1: ...
- Ansible9:条件语句【转】
在有的时候play的结果依赖于变量.fact或者是前一个任务的执行结果,从而需要使用到条件语句. 一.when 有的时候在特定的主机需要跳过特定的步骤,例如在安装包的时候,需要指定主机的操作系统 ...
- HttpRequest中常见的四种ContentType【转载】
本文转自:http://www.aikaiyuan.com/6324.html HTTP/1.1 协议规定的 HTTP 请求方法有 OPTIONS.GET.HEAD.POST.PUT.DELETE.T ...
- cpu、内存、缓存、硬盘使用率
1.cpu ./bunsan2.sh uptime < servers.txt | awk '{print $11 }' |sed 's/,//g' #!/bin/bash cpu_load=$ ...
- u盘烧写后实际容量变小了
百度了一下 : http://jingyan.baidu.com/article/d45ad148f383ea69552b808a.html 百度下载 USBoot 打开软件 列表中选择你的U盘,点击 ...
- springMVC中ajax的运用于注意事项
ajax的运用: 注意事项: dataType:"json"在ajax中可写可不写(ajax能够自动识别返回值类型),写了更加规范,可以在ajax识别错误返回值类型的时候,指定返回 ...