代码块的本质是和其余的变量相似,不一样的是,代码块存储的数据是一个函数体。使用代码块,你能够像调用其余标准函数同样的调用,能够传入参数,并获得返回值。
脱字符是代码块的语法标记。下图表示代码块的定义。html
1.代码块的基本使用数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//无参数无返回值
void
(^myblock)() = ^()
{
NSLog
(@
"Hello, World!"
);
};
myblock();
//带参数无返回值
void
(^myblock2)(
NSString
*string) = ^(
NSString
*string){
NSLog
(@
"%@"
,string);};
myblock2(@
"Hello, World myblock2!"
);
//无参数有返回值
int
(^myblocksss)() = ^(
int
i){
return
12;};
int
c = myblocksss();
NSLog
(@
"%i"
,c);
//有参数有返回值
int
(^myblock3)(
int
) = ^(
int
i){
return
12 * i; };
int
i = myblock3(3);
NSLog
(@
"%i"
,i);
|
2,利用typedef为Block进行重命名函数
使用typedef为block进行一次重命名,方法跟为函数指针进行重命名是同样的:post
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// Copyright © 2016年 liujun. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef
int
(^ MyBlock)(
int
a,
int
b);
int
main(
int
argc,
const
char
* argv[]) {
@autoreleasepool
{
// insert code here...
__block
int
n = 100;
MyBlock block = ^(
int
a,
int
b)
{
n = 20;
//不过没有用__block 修饰 代码不会编译经过
return
n + a + b;
};
NSLog
(@
"%i %i"
, n ,block(3,4));
//输出结果 100 27
NSLog
(@
"%i %i"
, block(3,4) ,n);
//输出结果 27 20
//以上输出。说明代码块是在调用的时候才会被执行
NSLog
(@
"Hello, World!"
);
}
return
0;
}
|
3.Block在内存中的位置this
根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。
NSGlobalBlock:相似函数,位于text段;
NSStackBlock:位于栈内存,函数返回后Block将无效;
NSMallocBlock:位于堆内存。url
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
// Copyright © 2016年 liujun. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef
long
(^Sum)(
int
,
int
);
int
main(
int
argc,
const
char
* argv[]) {
@autoreleasepool
{
// insert code here...v
Sum sum1 = ^
long
(
int
a,
int
b) {
return
a + b ;
};
NSLog
(@
"sum1 = %@"
, sum1);
// 打印结果:sum1 = <__NSGlobalBlock__: 0x47d0>
int
base = 100;
Sum sum2 = ^
long
(
int
a,
int
b) {
return
base + a + b;
};
NSLog
(@
"sum2 = %@"
, sum2);
// 打印结果:sum2 = <__NSMallocBlock__: 0xbfffddf8>
Sum sum3 = [sum2
copy
];
NSLog
(@
"sum3 = %@"
, sum3);
// 打印结果:sum3 = <__NSMallocBlock__: 0x902fda0>
NSLog
(@
"Hello, World!"
);
}
return
0;
}
|
NSGlobalBlock,咱们只要实现一个没有对周围变量没有引用的Block,就会显示为是它。而若是其中加入了对定义环境变量的引用,就是NSStackBlock。那么NSMallocBlock又是哪来的呢?malloc一词其实你们都熟悉,就是在堆上分配动态内存时。没错,若是你对一个NSStackBlock对象使用了Block_copy()或者发送了copy消息,就会获得NSMallocBlock。这一段中的几项结论可从代码实验得出。spa
也就获得了下面对block的使用注意点。指针
对于Global的Block,咱们无需多处理,不需retain和copy,由于即便你这样作了,彷佛也不会有什么两样。对于Stack的Block,若是不作任何操做,就会向上面所说,随栈帧自生自灭。而若是想让它得到比stack frame更久,那就调用Block_copy(),让它搬家到堆内存上。而对于已经在堆上的block,也不要期望经过copy进行“真正的copy”,由于其引用到的变量仍然会是同一份,在这个意义上看,这里的copy和retain的做用已经很是相似。code
4,外部参数在代码块的使用htm
blk1和blk2的区别在于:
blk1没有使用Block之外的任何外部变量,Block不须要创建局部变量值的快照,这使blk1与通常函数没有任何区别。
blk2与blk1惟一不一样是的使用了局部变量base,在定义(注意是“定义”,不是“运行”)blk2时,局部变量base当前值被copy到栈上,做为常量供Block使用。执行下面代码,结果是203,而不是204。
1
2
3
4
5
6
7
|
int
base = 100;
base += 100;
BlkSum sum = ^
long
(
int
a,
int
b) {
return
base + a + b;
};
base++;
printf(
"%ld"
,sum(1,2));
|
在Block内变量base是只读的,若是想在Block内改变base的值,在定义base时要用 __block修饰:__block int base = 100;
1
2
3
4
5
6
7
8
9
|
__block
int
base = 100;
base += 100;
BlkSum sum = ^
long
(
int
a,
int
b) {
base += 10;
return
base + a + b;
};
base++;
printf(
"%ld\n"
,sum(1,2));
printf(
"%d\n"
,base);
|
输出将是214,211。Block中使用__block修饰的变量时,将取变量此刻运行时的值,而不是定义时的快照。这个例子中,执行sum(1,2)时,base将取base++以后的值,也就是201,再执行Blockbase+=10; base+a+b,运行结果是214。执行完Block时,base已经变成211了。
static变量、全局变量 :若是把上个例子的base改为全局的、或static。Block就能够对他进行读写了。由于全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量。
1
2
3
4
5
6
7
8
9
|
static
int
base = 100;
BlkSum sum = ^
long
(
int
a,
int
b) {
base++;
return
base + a + b;
};
base = 0;
printf(
"%d\n"
, base);
printf(
"%ld\n"
,sum(1,2));
// 这里输出是3,而不是103
printf(
"%d\n"
, base);
|
输出结果是0 4 1,代表Block外部对base的更新会影响Block中的base的取值,一样Block对base的更新也会影响Block外部的base值。
Block变量,被__block修饰的变量称做Block变量。 基本类型的Block变量等效于全局变量、或静态变量。
5,循环引用
retain cycle问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就致使了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。好比:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@implementation
TsetBlock
-(
id
)init{
if
(
self
= [superinit]) {
self
.testStr =@
"中国"
;
self
.block = ^(
NSString
*name,
NSString
*str){
NSLog
(@
"arr:%@"
,
self
.testStr);
// 编译警告:Capturing 'self' strongly in this block is likely to lead to a retain cycle
};
}
returnself;
}
@end
|
网上大部分帖子都表述为"block里面引用了self致使循环引用",但事实真的是如此吗?我表示怀疑,其实这种说法是不严谨的,不必定要显式地出现"self"字眼才会引发循环引用。咱们改一下代码,不经过属性self.testStr去访问String变量,而是经过实例变量_testStr去访问,以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@implementation
TsetBlock
-(
id
)init{
if
(
self
= [superinit]) {
self
.testStr =@
"中国"
;
self
.block = ^(
NSString
*name,
NSString
*str){
NSLog
(@
"arr:%@"
, _testStr);
// 一样出现: Capturing 'self' strongly in this block is likely to lead to a retain cycle
};
}
returnself;
}
@end
|
能够发现:
即便在你的block代码中没有显式地出现"self",也会出现循环引用!只要你在block里用到了self所拥有的东西!
要分两种环境去解决:在ARC下不用__block ,而是用 __weak 为了不出现循环引用
1.ARC:用__week
__weaktypeof (self) weakSelf = self; 或者
__weak someClass *weakSelf = self;
2.MRC:用__block ,__block修饰的变量在Block copy时是不会retain的,因此,也能够作到破解循环引用。
__block someClass *blockSelf = self;
bloack的 retain、copy、release 操做
对Block不论是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;
NSGlobalBlock:retain、copy、release操做都无效;
NSStackBlock:retain、release操做无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即便retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的作法是先将stackBlock copy到堆上,而后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy以后生成新的NSMallocBlock类型对象。
NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增长、减小计数。copy以后不会生成新的对象,只是增长了一次引用,相似retain;
尽可能不要对Block使用retain操做。
6.代码块的递归调用
代码块想要递归调用,代码块变量必须是全局变量或者是静态变量,这样在程序启动的时候代码块变量就初始化了,能够递归调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
static
void
(^
const
myblock4)(
int
) = ^(
int
i)
{
if
(i > 0)
{
NSLog
(@
"%i"
,i);
myblock4(i - 1);
}
};
|