陈江川

邮箱:jiangchuanc@gmail.com

Objective-C中的Block<三>

注意:文中涉及到的代码及结论如果没有特别说明,那么都是在MRC环境下测试!!!

这节主要探讨Block中变量和对象的问题。

变量

Example A

void block1() {
    __block int localVal = 1;

    printf("1-%p-(%d)\n", &localVal, localVal);

    void (^blk)() = ^{
        localVal += 1;
        printf("2-%p-(%d)\n", &localVal, localVal);
    };

    localVal += 1;
    printf("3-%p-(%d)\n", &localVal, localVal);
    blk();
    printf("4-%p-(%d)\n", &localVal, localVal);
}

log:
1-0x7fff5fbff758-(1)
3-0x7fff5fbff758-(2)
2-0x7fff5fbff758-(3)
4-0x7fff5fbff758-(3)

从打印信息可知道,blk内部访问的变量localVal和局部变量localVal是同一个。

Example B

void block2() {
    __block int localVal = 1;

    printf("1-%p-(%d)\n", &localVal, localVal);

    void (^blk)() = ^{
        localVal += 1;
        printf("2-%p-(%d)\n", &localVal, localVal);
    };

    localVal += 1;
    printf("3-%p-(%d)\n", &localVal, localVal);
    blk = [blk copy];
    blk();
    printf("4-%p-(%d)\n", &localVal, localVal);

    [blk release];
}

log:
1-0x7fff5fbff758-(1)
3-0x7fff5fbff758-(2)
2-0x100602928-(3)
4-0x100602928-(3)

分析:

  1. 第一行打印,说明变量localVal在栈区;
  2. 然后localVal自加,打印的localVal内存地址在栈区,值为2;
  3. 调用blk = [blk copy],把Block及内部访问的变量从栈区复制到堆区;
  4. 执行blk(),从打印的信息看出localVal在堆区了,也就是现在的localVal和前面的localVal不是同一个;
  5. 第四处打印函数,访问的明明是blk外部的变量localVal,那为什么打印的内存地址却是堆区的?

    还记得Objective-C中的Block<一>中说道__block修饰符本质是一结构体,在结构体中有一个__forwarding成员:

当调用[blk copy],把整个Block和localVal都复制到堆区:

也就是说,访问栈区的localVal实际上是访问了堆区中的localVal,这也就能解释第4行访问了栈区的localVal,打印的却是堆区中localVal。

对象

Example A

typedef void (^blk_t)(id obj);

int main(int argc, const char * argv[]) {
    blk_t blk;

    {
        __strong id array = [[NSMutableArray alloc] init];

        blk = ^(id obj){
            [array addObject:obj];
            NSLog(@"array count = %ld", [array count]);
        };

        [array release];
    }

    NSObject *obj1 = [[NSObject alloc] init];
    NSObject *obj2 = [[NSObject alloc] init];
    NSObject *obj3 = [[NSObject alloc] init];

    blk(obj1);
    blk(obj2);
    blk(obj3);

    [obj1 release];
    [obj2 release];
    [obj3 release];

    return 0;
}

这个程序能不能正常运行?blk属于哪种Block类?
分析:

  1. blk中访问了对象,所以该Block属于_NSStackBlock,也就是说blk的作用域在大括号里面,出来大括号blk就会被释放;
  2. 在大括号外调用了blk(obj1),访问了一块不存在的内存地址:EXC_BAD_ACCESS!!

Example B

typedef void (^blk_t)(id obj);

int main(int argc, const char * argv[]) {
    blk_t blk;

    {
        id array = [[NSMutableArray alloc] init];

        blk = [^(id obj){
            [array addObject:obj];
            NSLog(@"array count = %ld", [array count]);
        } copy];

        [array release];
    }

    NSObject *obj1 = [[NSObject alloc] init];
    NSObject *obj2 = [[NSObject alloc] init];
    NSObject *obj3 = [[NSObject alloc] init];

    blk(obj1);
    blk(obj2);
    blk(obj3);

    [obj1 release];
    [obj2 release];
    [obj3 release];
    [blk release];

    return 0;
}

这个示例比上面多了一个copy,也就是把Block从栈区复制到堆区,那么可以在程序的任何地方访问它,所以在大括号外面使用blk是完全没有问题的。

« Runtime - 消息转发 Objective-C中的Block<二> »