block详解
来源:互联网 发布:淘宝刷粉丝怎么刷 编辑:程序博客网 时间:2024/06/15 15:28
一、Block初识
一、什么是Blocks
Block是一个C级别的语法以及运行时的一个特性,和标准C中的函数指针类似,但是其运行需要编译器和运行时支持,从ios4.0开始就很好的支持Block。二、在ios开发中,什么情况下使用Block
Block除了能够定义参数列表、返回类型外,还能够获取被定义时的词法范围内的状态(比如局部变量),并且在一定条件下(比如使用__block变量)能够修改这些状态。此外,这些可修改的状态在相同词法范围内的多个block之间是共享的,即便出了该词法范围(比如栈展开,出了作用域),仍可以继续共享或者修改这些状态。通常来说,block都是一些简短代码片段的封装,适用的工作单元,通常用来做并发任务、遍历、以及回调。
三、block如何申明(对比于c语言中的函数申明)
![点击查看原始大小图片](http://dl.iteye.com/upload/attachment/0073/2411/7a1efad7-39a4-3dd7-a965-9da1e12bde70.png)
四、c函数指正和blocks调用
int (*CFunc) (int a) 函数调用
int result = CFunc(10);
int (^BFunc) (int a) 函数调用
int result = BFunc(10);
五、__block 关键字
一个Block的内部时可以引用自身作用域外的变量的,包括static变量,extern变量或自由变量(定义一个变量的时候,如果不加存储修饰符,默认情况下就是自由变量auto,auto变量保存在stack中的。除了auto之外还存在register,static等存储修饰符),对于自由变量,在Block中只读的。在引入block的同时,还引入了一种特殊的__block关键字变量存储修饰符。
六、block的几个小例子
- #import <Cocoa/Cocoa.h>
- int main(int argc, char *argv[])
- {
- @autoreleasepool {
- NSLog(@"Hello world");
- void (^myblocks) (void) = NULL;
- myblocks = ^(void) {
- NSLog(@"in blocks");
- };
- NSLog(@"before myblocks");
- myblocks();
- NSLog(@"after myblocks");
- int (^myblocks2) (int a, int b) = ^(int a, int b) {
- int c = a + b;
- return c;
- };
- NSLog(@"before blocks2");
- int ret = myblocks2(10, 20);
- NSLog(@"after blocks2 ret %d", ret);
- //此处如果不加__block会报错
- __blockint sum = 0;
- int (^myblocks3) (int a, int b) = ^(int a, int b) {
- sum = a + b;
- return3;
- };
- myblocks3(20, 30);
- NSLog(@"sum is %d", sum);
- }
- returnNSApplicationMain(argc, (constchar **)argv);
- }
打印结果如下
2012-09-03 10:23:20.878 blockTest[407:403] Hello world
2012-09-03 10:23:20.880 blockTest[407:403] before myblocks
2012-09-03 10:23:20.881 blockTest[407:403] in blocks
2012-09-03 10:23:20.881 blockTest[407:403] after myblocks
2012-09-03 10:23:20.882 blockTest[407:403] before blocks2
2012-09-03 10:23:20.882 blockTest[407:403] after blocks2 ret 30
2012-09-03 10:23:20.882 blockTest[407:403] sum is 50
七、block写的回调例子
1、Dog.h
- #import <Foundation/Foundation.h>
- @interface Dog : NSObject {
- int _ID;
- NSTimer *timer;
- int barkCount;
- //定义一个blocks变量
- void (^BarkCallback) (Dog *thisDog, int count);
- }
- @property (assign) int ID;
- //向外暴露一个接口
- -(void) setBark:( void (^) (Dog *thisDog, int count) ) eachBark;
- @end
2、Dog.m
- #import "Dog.h"
- @implementation Dog
- @synthesize ID = _ID;
- -(id) init
- {
- self = [superinit];
- if (self) {
- //每隔1s调用一次updateTimer方法
- timer = [NSTimerscheduledTimerWithTimeInterval:1.0ftarget:selfselector:@selector(updateTimer:) userInfo:nilrepeats:YES];
- }
- returnself;
- }
- -(void) updateTimer:(id) arg
- {
- barkCount ++;
- NSLog(@"dog %d bark count %d", _ID, barkCount);
- //向Person对象进行汇报
- if (BarkCallback) {
- //调用从Person传过来的Blocks
- BarkCallback(self, barkCount);
- }
- }
- -(void) setBark:(void (^)(Dog *, int))eachBark
- {
- [BarkCallbackrelease];
- BarkCallback = [eachBark copy];
- }
- -(void) dealloc
- {
- [BarkCallbackrelease];
- [superdealloc];
- }
- @end
3、Person.h
- #import <Foundation/Foundation.h>
- #import "Dog.h"
- @interface Person : NSObject
- {
- Dog *_dog;
- }
- @property (retain) Dog *dog;
- @end
4、Person.m
- #import "Person.h"
- @implementation Person
- @synthesize dog = _dog;
- -(void) setDog:(Dog *)dog
- {
- if (_dog != dog) {
- [_dogrelease];
- _dog = [dog retain];
- [_dogsetBark:^(Dog *thisDog, int count) {
- NSLog(@"person dog %d count %d", [thisDog ID], count);
- }];
- }
- }
- -(Dog *) dog
- {
- return_dog;
- }
- -(void) dealloc
- {
- self.dog = nil;
- [superdealloc];
- }
- @end
5、Main.m
- #import <Foundation/Foundation.h>
- #import "Person.h"
- #import "Dog.h"
- int main(int argc, constchar * argv[])
- {
- @autoreleasepool {
- // insert code here...
- NSLog(@"Hello, World!");
- Person *person = [[Personalloc] init];
- Dog *dog = [[Dogalloc] init];
- [dog setID:10];
- [person setDog:dog];
- [dog release];
- while (1) {
- [[NSRunLoopcurrentRunLoop] run];
- }
- [person release];
- }
- return 0;
- }
6、打印结果
2012-09-03 11:21:08.551 blockDelegate[549:403] Hello, World!
2012-09-03 11:21:09.554 blockDelegate[549:403] dog 10 bark count 1
2012-09-03 11:21:09.555 blockDelegate[549:403] person dog 10 count 1
2012-09-03 11:21:10.554 blockDelegate[549:403] dog 10 bark count 2
2012-09-03 11:21:10.555 blockDelegate[549:403] person dog 10 count 2
2012-09-03 11:21:11.553 blockDelegate[549:403] dog 10 bark count 3
2012-09-03 11:21:11.554 blockDelegate[549:403] person dog 10 count 3
2012-09-03 11:21:12.554 blockDelegate[549:403] dog 10 bark count 4
2012-09-03 11:21:12.555 blockDelegate[549:403] person dog 10 count 4
2012-09-03 11:21:13.553 blockDelegate[549:403] dog 10 bark count 5
2012-09-03 11:21:13.553 blockDelegate[549:403] person dog 10 count 5
2012-09-03 11:21:14.553 blockDelegate[549:403] dog 10 bark count 6
2012-09-03 11:21:14.554 blockDelegate[549:403] person dog 10 count 6
Demo:把数组从大到小排序的一个例子
NSArray *ary = @[@1, @2, @3, @4, @5];
[ary sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1 intValue] > [obj2 intValue];
}];
for (int i = 0; i < ary.count; i++) {
debugLog(@"%@", ary[i]);
}
二、IOS中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 called a "closure".
关于闭包,我觉得阮一峰的一句话解释简洁明了:闭包就是能够读取其它函数内部变量的函数。
这个解释用到block来也很恰当:一个函数里定义了个block,这个block可以访问该函数的内部变量。
一个简单的Block示例如下:
- int (^maxBlock)(int, int) = ^(int x, int y) { return x > y ? x : y; };
如果用Python的lambda表达式来写,可以写成如下形式:
- f = lambda x, y : x if x > y else y
不过由于Python自身的语言特性,在def定义的函数体中,可以很自然地再用def语句定义内嵌函数,因为这些函数本质上都是对象。
如果用BNF来表示block的上下文无关文法,大致如下:
- block_expression ::= ^ block_declare block_statement
- block_declare ::= block_return_type block_argument_list
- block_return_type ::= return_type | 空
- block_argument_list ::= argument_list | 空
[1. Why block]
Block除了能够定义参数列表、返回类型外,还能够获取被定义时的词法范围内的状态(比如局部变量),并且在一定条件下(比如使用__block变量)能够修改这些状态。此外,这些可修改的状态在相同词法范围内的多个block之间是共享的,即便出了该词法范围(比如栈展开,出了作用域),仍可以继续共享或者修改这些状态。
通常来说,block都是一些简短代码片段的封装,适用作工作单元,通常用来做并发任务、遍历、以及回调。
比如我们可以在遍历NSArray时做一些事情:
- - (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
其中将stop设为YES,就跳出循环,不继续遍历了。
而在很多框架中,block越来越经常被用作回调函数,取代传统的回调方式。
- 用block作为回调函数,可以使得程序员在写代码更顺畅,不用中途跑到另一个地方写一个回调函数,有时还要考虑这个回调函数放在哪里比较合适。采用block,可以在调用函数时直接写后续处理代码,将其作为参数传递过去,供其任务执行结束时回调。
- 另一个好处,就是采用block作为回调,可以直接访问局部变量。比如我要在一批用户中修改一个用户的name,修改完成后通过回调更新对应用户的单元格UI。这时候我需要知道对应用户单元格的index,如果采用传统回调方式,要嘛需要将index带过去,回调时再回传过来;要嘛通过外部作用域记录当前操作单元格的index(这限制了一次只能修改一个用户的name);要嘛遍历找到对应用户。而使用block,则可以直接访问单元格的index。
这份文档中提到block的几种适用场合:
- 任务完成时回调处理
- 消息监听回调处理
- 错误回调处理
- 枚举回调
- 视图动画、变换
- 排序
[2. About __block_impl]
Clang提供了中间代码展示的选项供我们进一步了解block的原理。
以一段很简单的代码为例:
使用-rewrite-objc选项编译:
得到一份block0.cpp文件,在这份文件中可以看到如下代码片段:
从命名可以看出这是block的实现,并且得知block在Clang编译器前端得到实现,可以生成C中间代码。很多语言都可以只实现编译器前端,生成C中间代码,然后利用现有的很多C编译器后端。
从结构体的成员可以看出,Flags、Reserved可以先略过,isa指针表明了block可以是一个NSObject,而FuncPtr指针显然是block对应的函数指针。
由此,揭开了block的神秘面纱。
不过,block相关的变量放哪里呢?上面提到block可以capture词法范围内(或者说是外层上下文、作用域)的状态,即便是出了该范围,仍然可以修改这些状态。这是如何做到的呢?
[3. Implementation of a simple block]
先看一个只输出一句话的block是怎么样的。
生成中间代码,得到片段如下:
首先出现的结构体就是__main_block_impl_0,可以看出是根据所在函数(main函数)以及出现序列(第0个)进行命名的。如果是全局block,就根据变量名和出现序列进行命名。__main_block_impl_0中包含了两个成员变量和一个构造函数,成员变量分别是__block_impl结构体和描述信息Desc,之后在构造函数中初始化block的类型信息和函数指针等信息。
接着出现的是__main_block_func_0函数,即block对应的函数体。该函数接受一个__cself参数,即对应的block自身。
再下面是__main_block_desc_0结构体,其中比较有价值的信息是block大小。
最后就是main函数中对block的创建和调用,可以看出执行block就是调用一个以block自身作为参数的函数,这个函数对应着block的执行体。
这里,block的类型用_NSConcreteStackBlock来表示,表明这个block位于栈中。同样地,还有_NSConcreteMallocBlock和_NSConcreteGlobalBlock。
由于block也是NSObject,我们可以对其进行retain操作。不过在将block作为回调函数传递给底层框架时,底层框架需要对其copy一份。比方说,如果将回调block作为属性,不能用retain,而要用copy。我们通常会将block写在栈中,而需要回调时,往往回调block已经不在栈中了,使用copy属性可以将block放到堆中。或者使用Block_copy()和Block_release()。
[4. Capture local variable]
再看一个访问局部变量的block是怎样的。
生成中间代码,得到片段如下:
可以看出这次的block结构体__main_block_impl_0多了个成员变量i,用来存储使用到的局部变量i(值为1024);并且此时可以看到__cself参数的作用,类似C++中的this和Objective-C的self。
如果我们尝试修改局部变量i,则会得到如下错误:
错误信息很详细,既告诉我们变量不可赋值,也提醒我们要使用__block类型标识符。
为什么不能给变量i赋值呢?
因为main函数中的局部变量i和函数__main_block_func_0不在同一个作用域中,调用过程中只是进行了值传递。当然,在上面代码中,我们可以通过指针来实现局部变量的修改。不过这是由于在调用__main_block_func_0时,main函数栈还没展开完成,变量i还在栈中。但是在很多情况下,block是作为参数传递以供后续回调执行的。通常在这些情况下,block被执行时,定义时所在的函数栈已经被展开,局部变量已经不在栈中了(block此时在哪里?),再用指针访问就⋯⋯。
所以,对于auto类型的局部变量,不允许block进行修改是合理的。
[5. Modify static local variable]
于是我们也可以推断出,静态局部变量是如何在block执行体中被修改的——通过指针。
因为静态局部变量存在于数据段中,不存在栈展开后非法访存的风险。
上面中间代码片段与前一个片段的差别主要在于main函数里传递的是i的地址(&i),以及__main_block_impl_0结构体中成员i变成指针类型(int *)。
然后在执行block时,通过指针修改值。
当然,全局变量、静态全局变量都可以在block执行体内被修改。更准确地讲,block可以修改它被调用(这里是__main_block_func_0)时所处作用域内的变量。比如一个block作为成员变量时,它也可以访问同一个对象里的其它成员变量。
[6. Implementation of __block variable]
那么,__block类型变量是如何支持修改的呢?
我们为int类型变量加上__block指示符,使得变量i可以在block函数体中被修改。
此时再看中间代码,会多出很多信息。首先是__block变量对应的结构体:
由第一个成员__isa指针也可以知道__Block_byref_i_0也可以是NSObject。
第二个成员__forwarding指向自己,为什么要指向自己?指向自己是没有意义的,只能说有时候需要指向另一个__Block_byref_i_0结构。
最后一个成员是目标存储变量i。
此时,__main_block_impl_0结构如下:
__main_block_impl_0的成员变量i变成了__Block_byref_i_0 *类型。
对应的函数__main_block_func_0如下:
亮点是__Block_byref_i_0指针类型变量i,通过其成员变量__forwarding指针来操作另一个成员变量。 :-)
而main函数如下:
通过这样看起来有点复杂的改变,我们可以修改变量i的值。但是问题同样存在:__Block_byref_i_0类型变量i仍然处于栈上,当block被回调执行时,变量i所在的栈已经被展开,怎么办?
在这种关键时刻,__main_block_desc_0站出来了:
此时,__main_block_desc_0多了两个成员函数:copy和dispose,分别指向__main_block_copy_0和__main_block_dispose_0。
当block从栈上被copy到堆上时,会调用__main_block_copy_0将__block类型的成员变量i从栈上复制到堆上;而当block被释放时,相应地会调用__main_block_dispose_0来释放__block类型的成员变量i。
一会在栈上,一会在堆上,那如果栈上和堆上同时对该变量进行操作,怎么办?
这时候,__forwarding的作用就体现出来了:当一个__block变量从栈上被复制到堆上时,栈上的那个__Block_byref_i_0结构体中的__forwarding指针也会指向堆上的结构。
三、阐述一下Block中如何的使用外部变量以及block本身的内存管理。
- typedef void(^BlockCC)(void);
- BlockCC _block;
- int number = 1;
- _block = ^(){
- NSLog(@"number %d", number);
- };
- int number = 1;
- _block = ^(){
- NSLog(@"number %d", number);
- };
- number = 2;
- _block();
- __block int number = 1;
- _block = ^(){
- number++;
- NSLog(@"number %d", number);
- };
- - (void)viewDidLoad
- {
- [superviewDidLoad];
- int number = 1;
- _block = ^(){
- NSLog(@"number %d", number);
- };
- }
- - (IBAction)testDidClick:(id)sender {
- _block();
- }
- _block = ^(){
- NSLog(@"number %d", number);
- };
- _block = [_block copy];
- [array addObject:[[^{
- NSLog(@"hello!");
- } copy] autorelease]];
3、循环引用
- @interface ViewController : UIViewController
- {
- NSString *_string;
- }
- @end
- _block = ^(){
- NSLog(@"string %@", _string);
- };
- __block ViewController *controller = self;
- _block = ^(){
- NSLog(@"string %@", controller->_string);
- };
- Block详解
- block详解
- Block详解
- block详解
- Block详解
- block详解
- Block详解
- block详解
- Block 详解
- block详解
- block详解
- block详解
- BLock详解
- 详解Block: block循环引用
- inode与block详解
- objective-c block 详解
- objective-c block 详解
- objective-c block 详解
- 开源日志系统比较【转载+整理】
- HDU 4010 Query on The Trees
- libcurl 示例代码
- OpenCV HaarTraining代码解析(一)icvGetBackgroundImage
- oracle 简单处理空白符
- block详解
- spring mvc常用的注解:
- 一次调试无法打印日志问题经历
- httppost 302 错误,HttpPost、HttpGet关于URL重定向区别
- Effective C++之1 让自己习惯C++
- 2014.10.17 DWZ框架中ajax提交文件表单的处理(关闭当前dialog + 刷新父级navTab)
- bzoj 3712 [PA2014]Fiolki(LCA)
- easyUI 后台存的数据,前台显示文字 或者是前台是下拉框,选择是汉字的情况
- NAT配置