How Do I Declare A Block in Objective-C?网络
阮一峰的一句话解释简洁明了:闭包就是可以读取其它函数内部变量的函数多线程
详情:http://blog.csdn.net/jasonblog/article/details/7756763闭包
block的几种适用场合:app
- 任务完成时回调处理
- 消息监听回调处理
- 错误回调处理
- 枚举回调
- 视图动画、变换
- 排序
local variable
1
|
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...}; |
property
1
|
@property (nonatomic, copy) returnType (^blockName)(parameterTypes); |
method parameter
1
|
- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName; |
method call
: 1
|
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}]; |
typedef
1
2 |
typedef returnType (^TypeName)(parameterTypes); TypeName blockName = ^returnType(parameters) {...}; |
数据类型
任何须要的时候
被执行匿名函数
,能够做为其余函数的参数或者返回值。 1
2 |
//Block 是对 C 语言的一个拓展 //快速建立 Block 用 inlineBlock |
1
2 3 |
返回值类型 (^block变量名)(形参列表) = ^(形参列表) { }; |
1
2 3 4 5 6 |
void (^block名)() = ^{代码块;} 例如: void (^myBlock)() = ^{ NSLog(@"Damonwong"); }; |
1
2 3 4 5 6 7 |
void (^block名称)(参数列表) = ^ (参数列表) { // 代码实现; } 例如: void (^myBlock)(int) = ^(int num){ NSLog(@"num = %i", num); }; |
1
2 3 4 5 6 7 |
返回类型 (^block名称)(参数列表) = ^ 返回类型 (参数列表) { // 代码实现; } 例如: int (^myBlock)(int, int) = ^(int num1, int num2){ return num1 + num2; }; |
1
|
block变量名(实参); |
观察下面四段代码的输出值
框架
1
2 3 4 5 6 7 8 |
int x = 123; void (^printXAndY)(int) = ^(int y) { x = 100; //会报错, printf("%d %d\n", x, y); }; printXAndY(456); /*------------------*/ @"prints: 123 456" |
1
2 3 4 5 6 7 8 |
int x = 123; void (^printXAndY)(int) = ^(int y) { printf("%d %d\n", x, y); }; x = 456; // 修改 x 为 456,block 依旧输出 123 printXAndY(456); /*------------------*/ @"prints: 123 456" |
__blcok
关键字的神奇功效。
首先,若是须要对block 外部定义的变量在 block 内修改,那么须要对这个变量添加一个__block
修饰。函数
1
2 3 4 5 6 7 8 |
__block int x = 123; void (^printXAndY)(int) = ^(int y) { x = 100; //不会报错 printf("%d %d\n", x, y); }; printXAndY(456); /*------------------*/ @"prints: 100 456" |
若是须要在调用以前,变量的修改都会影响 block 内部对这个变量的使用,换句话说,block 对变量再也不是简单的值复制,而是动态的"监听"值的变化,而后在调用的时候读取变量的值。须要对这个变量添加一个__block
修饰。动画
1
2 3 4 5 6 7 8 |
__block int x = 123; void (^printXAndY)(int) = ^(int y) { printf("%d %d\n", x, y); }; x = 456 //block 会「动态」识别外部变量的变化,输出456 printXAndY(456); /*------------------*/ @"prints: 456 456" |
变量
和指针所指向的变量
1
2 3 4 5 6 7 8 |
int x = 123; NSMutableString *str = [NSMutableString stringWithFormat:@"Damon"]; void (^printStr)() = ^() { [str appendString:@"wong"]; NSLog(@"%@",str); }; printStr(); // prints:Damonwong |
这里的 [str appendString:@"wong"];
不报错是由于str 是指向@"Damon"
的函数指针,[str appendString:@"wong"];
并非修改 str 存储的值,本质上是 str 向@"Damon"
发送了一条appendString 消息,而后再更改@"Damon"
为@"Damonwong"
,而 str 存储的指向@"Damonwong"
对象的指针没有发生变化。因此,block 本质是不能修改变量存储的值,可是消息分发依旧有效。ui
虽然 Block 用起来特别方便,可是要特别注意循环应用的问题。this
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 |
// ARC enabled /************** MyObject Class **************/ typedef void (^myBlock)(void); @interface MyObject : NSObject { myBlock block; } @end @implementation MyObject - (id)init { self = [super init]; block = ^{ NSLog(@"self = %@", self); }; return self; } - (void)dealloc { NSLog(@"dealloc"); } @end /************** main function **************/ int main() { id myObject = [[MyObject alloc] init]; NSLog(@"%@", myObject); return 0; } |
因为 self 是 __strong 修饰,在 ARC 下,当编译器自动将代码中的 block 从栈拷贝到堆时,
block 会强引用和持有 self
,而self 刚好也强引用和持有了 block
,就形成了传说中的循环引用。atom为了不这种状况发生,能够在变量声明时用
__weak
修饰符修饰变量 self,让 block 不强引用 self,从而破除循环。
1
2 3 4 5 6 7 8 9 |
- (id)init { self = [super init]; __weak typeof(self) weak_self = self; block = ^{ NSLog(@"self = %@", weak_self); }; return self; } |
黑科技,防止循环引用
1
2 3 4 5 6 7 8 9 10 11 |
- (id)init { self = [super init]; __block typeof(self) temp = self; block = ^{ NSLog(@"self = %@", temp); temp = nil; }; return self; } // 使用这个,必须调用一次 block |
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 6
void 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 13
void 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 12
void 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 12
typedef 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 14
typedef 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()这个宏。
1、根据需求提出问题
- 请耐心把这篇文章看完,你对 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 对象。
2、深刻探究原理
- 这里将会对每行代码逐步进行说明
一、开辟一段控件存储 person 类对象内容,建立 person 强指针。 MitPerson*person = [[MitPerson alloc]init];
二、建立一个弱指针 weakPerson 指向person对象内容 __weak MitPerson * weakPerson = person;
person.mitBlock = ^{ 三、在 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]; }); };
四、执行 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 的概念:
- 一、默认状况下,block 是放在栈里面的
- 二、一旦blcok进行了copy操做,block的内存就会被放在堆里面
- 三、堆立面的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