Objective-C的block特性(2)

转载请注明出处:http://www.cnblogs.com/idalink/articles/4427375.html  

已经说过,block具有捕获变量(包括全局变量、成员变量、局部变量)的特性,这个特别使其完全区别于c语言函数,而block的魅力正在于此。

1、从block捕获变量谈起

既然block能够捕获变量,如果block对象没有被销毁,而局部变量被销毁,这算不算逻辑权限呢?很自然的一点,那就是block访问局部变量,这个变量毫无疑问会被调用栈回收掉,而这个时候block访问的又是哪位呢?假设被访问的对象不在栈上,而是堆区,这个对象依然存在被过早释放的可能,那block是不是要持有这个对象,并增加引用计数呢?

2、block的内存分配

被捕获的变量受block的生命周期影响,那block的内存分配是个怎样的机制?我们创建一个伪本地block,并观察。

- (void)viewDidLoad {

    [super viewDidLoad];

    MyBlock localBlock1 = ^(){    };

    NSLog(@"地址--%@", localBlock1);

    int var = 1001;

    MyBlock localBlock2 = ^(){

        NSLog(@"var = %d", var);

    };

    NSLog(@"地址--%@", localBlock2);

}

会得到如下输出:

2015-04-14 23:54:13.201 test_block[787:42086] 地址--<__NSGlobalBlock__: 0xc4048>

2015-04-14 23:54:13.202 test_block[787:42086] 地址--<__NSMallocBlock__: 0x7965a8c0>

 

这个block从代码上看似是个局部变量,其实是个(静态)全局变量。因为在ARC模式下,block总是分配在静态存储区。非ARC下还没测验。可以参考:http://blog.csdn.net/hherima/article/details/38586101

在此上传一个前人大牛的很有意义的论果:

图片来自:http://blog.csdn.net/hherima/article/details/38586101

既然可以得知,ARC模式下,block总是被分配在静态存储区,自然不会面对stack到static的转变,那么block的生命周期也就大大简化。在ARC模式下,block被copy并不会生成新的block对象,只是使block的引用计数自增一。

3、block对局部变量的影响

局部变量总是随着栈的销毁而销毁,block必然无法访问栈的这块内存。所以在无__block修饰下,block只会把这个变量当成常量使用,并在block内部创建同名变量,并且无法修改这个变量。编译级别,block无法修改访问对象。既然创建了一个新的变量,则block外部对该变量的修改也不会涉及到block的内部同名变量。这是已经是完全两个变量了。

//代码
//外围修改变量不会设计block内部
- (void)viewDidLoad { [super viewDidLoad]; int var = 1001; MyBlock myblock = ^{ NSLog(@"block内部输出var %d", var); }; var ++; myblock(); }

输出:

2015-04-15 00:06:35.028 test_block[867:46317] block内部输出var  1001

要使block具备修改外部对象的能力,必须要对对象添加__block修饰。这是不是会与堆栈调用冲突呢?这个逻辑怎么处理呢?

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block int var = 1001;
    NSLog(@"新对象的地址  %p", &var);
    MyBlock myblock = ^{
        NSLog(@"block内部输出地址  %p", &var);
    };
    
    NSLog(@"被block访问后的地址  %p", &var);
    myblock();
}

输出:

2015-04-15 00:11:22.360 test_block[907:48126] 新对象的地址  0xbff2df08
2015-04-15 00:11:22.361 test_block[907:48126] 被block访问后的地址  0x7bf29460
2015-04-15 00:11:22.361 test_block[907:48126] block内部输出地址  0x7bf29460

发生了一个神奇一幕,局部变量被block捕获之后,地址竟然完全变了。不得不怀疑,block把变量从栈复制到静态存储区了。

4、block对全局变量的影响

全局变量的生命周期不会受到stack的约束,自然而然只需要能对对象的持续引用即可保持对象的持久性。可以推测,block 访问全局变量,不会拷贝对象,而只会调整引用计数。

@interface TSObject : NSObject
@end

@implementation TSObject
@end

@interface ViewController ()
@end

typedef void (^MyBlock)();

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    TSObject *tsObject1 = [[TSObject alloc] init];
    NSLog(@"引用计数1-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject1));

    TSObject *tsObject2 = tsObject1;
    NSLog(@"引用计数2-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject2));
    
    MyBlock localBlock1 = ^(){
        NSLog(@"输出地址2 %@", tsObject1);
    };
    NSLog(@"输出地址1 %@", tsObject1);
    NSLog(@"引用计数3-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject2));
    localBlock1();
}
@end

输出:

2015-04-15 00:21:42.214 test_block[1077:53288] 引用计数1-->1
2015-04-15 00:21:42.215 test_block[1077:53288] 引用计数2-->2
2015-04-15 00:21:42.215 test_block[1077:53288] 输出地址1 <TSObject: 0x7af25ef0>
2015-04-15 00:21:42.215 test_block[1077:53288] 引用计数3-->4
2015-04-15 00:21:42.215 test_block[1077:53288] 输出地址2 <TSObject: 0x7af25ef0>

可以看出,对象地址并为发生改变,而饮用技术却增加了。为什么会增加2呢,而不是1呢?因为block最开始位于常量区,后来移动到了全局堆区,发生了copy,自然而然所持有的对象自增2。还有疑问,这里先用一个新的指针tsObject2赋值对象,这是为了比较__block指针对引用计数的影响。稍后分析。

 

如果把指针加上__block修饰符会如何呢?

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block TSObject *tsObject1 = [[TSObject alloc] init];
    NSLog(@"引用计数1-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject1));
    NSLog(@"输出地址1 %@", tsObject1);

    TSObject *tsObject2 = tsObject1;
    NSLog(@"引用计数2-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject2));
    
    MyBlock localBlock1 = ^(){
        NSLog(@"输出地址3 %@", tsObject1);
    };
    NSLog(@"输出地址2 %@", tsObject1);
    NSLog(@"引用计数3-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject2));
    localBlock1();
}

输出:

2015-04-15 00:29:18.048 test_block[1149:55775] 引用计数1-->1
2015-04-15 00:29:18.048 test_block[1149:55775] 输出地址1 <TSObject: 0x7b0b35a0>
2015-04-15 00:29:18.049 test_block[1149:55775] 引用计数2-->2
2015-04-15 00:29:18.049 test_block[1149:55775] 输出地址2 <TSObject: 0x7b0b35a0>
2015-04-15 00:29:18.049 test_block[1149:55775] 引用计数3-->2
2015-04-15 00:29:18.049 test_block[1149:55775] 输出地址3 <TSObject: 0x7b0b35a0>

这是为什么,block没有改变引用计数,对象不会被销毁吗?继续修改代码测试:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block TSObject *tsObject1 = [[TSObject alloc] init];
    NSLog(@"引用计数1-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject1));
    NSLog(@"输出地址1 %@", tsObject1);

    TSObject *tsObject2 = tsObject1;
    NSLog(@"引用计数2-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject2));
    
    MyBlock localBlock1 = ^(){
        NSLog(@"输出地址3 %@", tsObject2);
    };
    NSLog(@"输出地址2 %@", tsObject1);
    NSLog(@"引用计数3-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject2));
    localBlock1();
}

输出:

2015-04-15 00:31:03.878 test_block[1178:56611] 引用计数1-->1
2015-04-15 00:31:03.878 test_block[1178:56611] 输出地址1 <TSObject: 0x7bf6b810>
2015-04-15 00:31:03.879 test_block[1178:56611] 引用计数2-->2
2015-04-15 00:31:03.879 test_block[1178:56611] 输出地址2 <TSObject: 0x7bf6b810>
2015-04-15 00:31:03.879 test_block[1178:56611] 引用计数3-->4
2015-04-15 00:31:03.879 test_block[1178:56611] 输出地址3 <TSObject: 0x7bf6b810>

block的引用计数又“复活”了?这该如何解释?__block到底做了什么?编译器难道在做非逻辑吧必须性优化?

5、block对成员变量的影响 

现在创建两个成员变量,并且其中一个指针由_block修饰。看看引用计数变化和地址的变化。

typedef void (^MyBlock)();

@interface TSObject :NSObject

@end

@implementation TSObject

@end

@interface ViewController ()
{
    TSObject *tsObject1;
    __block TSObject *tsObject2;
}
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    tsObject1 = [[TSObject alloc] init];
    NSLog(@"输出地址1 %@", tsObject1);
    NSLog(@"引用计数1-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject1));
    
    MyBlock localBlock1 = ^(){
        NSLog(@"输出地址2 %@", tsObject1);
        NSLog(@"引用计数3-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject1));
    };
    
    NSLog(@"引用计数2-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject1));
    localBlock1();
    
    tsObject2 = [[TSObject alloc] init];
    NSLog(@"成员变量输出地址1 %@", tsObject2);
    NSLog(@"成员变量引用计数1-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject2));
    
    MyBlock localBlock2 = ^(){
        NSLog(@"成员变量输出地址2 %@", tsObject2);
        NSLog(@"成员变量引用计数3-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject2));
    };
    
    NSLog(@"成员变量引用计数2-->%ld", CFGetRetainCount((__bridge CFTypeRef)tsObject2));
    localBlock2();
}
@end

输出:

2015-04-15 12:23:50.854 test_block[7249:1675955] 输出地址1 <TSObject: 0x17400f410>
2015-04-15 12:23:50.855 test_block[7249:1675955] 引用计数1-->1
2015-04-15 12:23:50.855 test_block[7249:1675955] 引用计数2-->1
2015-04-15 12:23:50.855 test_block[7249:1675955] 输出地址2 <TSObject: 0x17400f410>
2015-04-15 12:23:50.855 test_block[7249:1675955] 引用计数3-->1
2015-04-15 12:23:50.856 test_block[7249:1675955] 成员变量输出地址1 <TSObject: 0x170017fb0>
2015-04-15 12:23:50.856 test_block[7249:1675955] 成员变量引用计数1-->1
2015-04-15 12:23:50.856 test_block[7249:1675955] 成员变量引用计数2-->1
2015-04-15 12:23:50.856 test_block[7249:1675955] 成员变量输出地址2 <TSObject: 0x170017fb0>
2015-04-15 12:23:50.856 test_block[7249:1675955] 成员变量引用计数3-->1

可以看出,对象地址不变,引用计数没有影响。

6、block的捕获变量的总结

(1)无法修改局部变量,除非使用__block修饰。

(2)被__block修饰的对象,如果对象在栈上,则将改对象移动到堆上。

(3)被捕获的变量通过增加引用计数持有对象。至于被__block修饰的指针,引用计数并不变。

 

posted @ 2015-04-15 00:39  Dalink  阅读(235)  评论(0编辑  收藏  举报