Objective-C block

1 关于__block变量为什么可以在block体内修改值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void foo()
{
    __block int i = 1024;//此时i在栈上
    int j = 1;//此时j在栈上
    void (^blk)(void);
    blk = ^{printf("%d, %d\n", i, j); };//此时,blk已经初始化,它会拷贝没有__block标记的常规变量自己所持有的一块内存区,这块内存区现在位于栈上,而对于具有__block标记的变量,其地址会被拷贝置前述的内存区中
    blk();//1024, 1
    void(^blkInHeap)(void) = Block_copy(blk);//复制block后,block所持有的内存区会被拷贝至堆上,此时,我们可以说,这个block现在位于堆上
    blkInHeap();//1024,1
    i++;
    j++;
    blk();//1025,1
    blkInHeap();//1025,1
}

让我们一步步剖析:

首先,我们在栈上创建了变量ij,并赋予初始值,然后创建一个block变量名为blk,但未赋值。

然后我们初始化这个blk,赋值为一个只有一句printf的block,值得注意的是,一个block一旦创建,其引用到的常规变量会进行如下操作:

没有__block标记的变量,其值会被复制一份到block私有内存区

有__block标记的变量,其地址会被记录在block私有内存区

然后调用blk,打印1024, 1很好理解

接下来复制blk到堆,名曰blkInHeap,调用之,打印1024, 1也很好理解

接下来我们为ij增值,使其变为1025和2,此时再调用blk或者blkInHeap,会发现结果为1025, 1,这是因为变量j早已在创建原始的block时,被赋值进block的私有内存区,后续对i的操作并非操作的私有内存区的复制品,当调用blk或者blkInHeap时,其打印使用的是私有内存区的复制品,故而打印结果依旧为1;而变量j的修改会实时生效,因为block记录的是它的地址,通过地址来访问其值,使得外部对j的修改在block中得以生效。

 

2 关于使用

转载自:http://fuckingblocksyntax.com/

 

How Do I Declare A Block in Objective-C?

 

As a local variable:

returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

 

As a property:

@property (nonatomic, copy) returnType (^blockName)(parameterTypes);

As a method parameter:

- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;

As an argument to a method call:

[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];

As a typedef:

typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};

 

3 为什么要用block与其他回调方式的区别

 

 

Block除了能够定义参数列表、返回类型外,还能够获取被定义时的词法范围内的状态(比如局部变量),并且在一定条件下(比如使用__block变量)能够修改这些状态。此外,这些可修改的状态在相同词法范围内的多个block之间是共享的,即便出了该词法范围(比如栈展开,出了作用域),仍可以继续共享或者修改这些状态。

 

通常来说,block都是一些简短代码片段的封装,适用作工作单元,通常用来做并发任务、遍历、以及回调。

 

 

 

而在很多框架中,block越来越经常被用作回调函数,取代传统的回调方式。

 

  • 用block作为回调函数,可以使得程序员在写代码更顺畅,不用中途跑到另一个地方写一个回调函数,有时还要考虑这个回调函数放在哪里比较合适。采用block,可以在调用函数时直接写后续处理代码,将其作为参数传递过去,供其任务执行结束时回调。
  • 另一个好处,就是采用block作为回调,可以直接访问局部变量。比如我要在一批用户中修改一个用户的name,修改完成后通过回调更新对应用户的单元格UI。这时候我需要知道对应用户单元格的index,如果采用传统回调方式,要嘛需要将index带过去,回调时再回传过来;要嘛通过外部作用域记录当前操作单元格的index(这限制了一次只能修改一个用户的name);要嘛遍历找到对应用户。而使用block,则可以直接访问单元格的index。

 

这份文档中提到block的几种适用场合:

 

  • 任务完成时回调处理
  • 消息监听回调处理
  • 错误回调处理
  • 枚举回调
  • 视图动画、变换
  • 排序

4、block对象的生存期

在Objective-C语言中,一共有3种类型的block:

  1. _NSConcreteGlobalBlock 全局的静态block,当block里面没有局部变量的时候,就会是一个全局静态的block。
  2. _NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。
  3. _NSConcreteMallocBlock 保存在堆中的block,当引用计数为0时会被销毁。

通常在Objective-C中,block是在栈上声明的,当调用copy后 类型从__NSStackBlock__变为了__NSMallocBlock__(__block标记的变量,这个变量会随着block对象一块上堆)

当一个block对象上堆了,他的声明周期就和一个普通的NSObject对象的方法一样了(这个应该是__NSMallocBlock__这个类的设计参考了NSObject对象的设计)

在非ARC环境下,copy了block后一定要在使用后release,不然会有内存泄露,而且泄露点是在系统级

另外还有一种类型block的类型__NSGlobalBlock__,当block里面没有局部变量的时候会block会变为这个类型,这个类型的retain/copy/release全都不会对对象有影响,可以当做静态block理解。
__NSMallocBlock__对象再次copy,不会再产生新的对象而是对原有对象进行retain。
经过实验几个block类型的retain/copy/release的功能如下(非ARC环境):

 

 

关于ARC下的block。因为在ARC下有个原则,只要block在strong指针底下过一道都会放到堆上。
看下面这个实验:

复制代码
    {
        __blockint val = 10;
        __strong blk strongPointerBlock = ^{NSLog(@"val = %d", ++val);};
        NSLog(@"strongPointerBlock: %@", strongPointerBlock); //1
        
        __weak blk weakPointerBlock = ^{NSLog(@"val = %d", ++val);};
        NSLog(@"weakPointerBlock: %@", weakPointerBlock); //2
        
        NSLog(@"mallocBlock: %@", [weakPointerBlock copy]); //3
        
        NSLog(@"test %@", ^{NSLog(@"val = %d", ++val);}); //4
    }
复制代码

得到的日志

2013-05-29 16:03:58.773 BlockTest[3482:c07] strongPointerBlock: <__NSMallocBlock__: 0x7625120>
2013-05-29 16:03:58.776 BlockTest[3482:c07] weakPointerBlock: <__NSStackBlock__: 0xbfffdb30>
2013-05-29 16:03:58.776 BlockTest[3482:c07] mallocBlock: <__NSMallocBlock__: 0x714ce60>
2013-05-29 16:03:58.777 BlockTest[3482:c07] test <__NSStackBlock__: 0xbfffdb18>

分析一下:
strong指针指向的block已经放到堆上了。
weak指针指向的block还在栈上(这种声明方法只在block上有效,正常的weak指针指向堆上对象,直接就会变nil,需要用strong指针过一道,请参考ARC的指针使用注意事项)
第三行日志同非ARC一样,会将block从栈移动到堆上。
最后一行日志,说明在单独声明block的时候,block还是会在栈上的。

在ARC下的另外一种情况,将block作为参数返回

- (__unsafe_unretained blk) blockTest {
    int val = 11;
    return ^{NSLog(@"val = %d", val);};
}

调用方

NSLog(@"block return from function: %@", [self blockTest]);

得到的日志:

2013-05-29 16:09:59.489 BlockTest[3597:c07] block return from function: <__NSMallocBlock__: 0x7685640>

分析一下:
在ARC环境下,当block作为参数返回的时候,block也会自动被移到堆上。

在ARC下,只要指针过一下strong指针,或者由函数返回都会把block移动到堆上

 

posted on 2015-06-23 15:05 城之内 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/HypeCheng/p/4595504.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值