ios中block的深入理解

作者: 夕醒 | 来源:发表于2016-04-29 13:24 被阅读609次

    block有三种类型:NSGlobalBlock,NSStackBlock,NSMallocBlock。

    1.    NSGlobalBlock:类似函数,未引用外部变量,位于text段;

    float (^ss)(float a) = ^ (float a) {

    return a;

    };

    NSLog(@"global block %@", ss);

    2.    NSStackBlock:位于栈内存,函数返回后Block将无效;

    3.    NSMallocBlock:位于堆内存。


    对于在堆中的block和栈中的block有的时候在使用过程中出现问题,举例分析一下:

    - (void)main

    int num = 888;

    NSMutableArray* array =  [self getBlockArray:num];

    for (int i = 0; i < array.count; i++) {

    NSLog(@"aBlock %@",array[i]);

    void(^bcc)() = array[i];

    bcc();

    }

    - (NSMutableArray*) getBlockArray:(int)num

    {

    return [[NSMutableArray alloc] initWithObjects:

    ^{ NSLog(@"this is block 0:%i", num); },

    ^{ NSLog(@"this is block 1:%i", num); },

    ^{ NSLog(@"this is block 2:%i", num); },

    nil];

    }

          上段程序做了这几件事,得到一个含有block的数组,并进行打印。但在程序运行到 i=1 时会出现crash,原因就是EXC_BAD_ACCESS错误,出现野指针,这是为什么呢?

           block是object c 语言中的对象,只是是分配在栈上的,一般类的实例对象是分配在堆上的。就如苹果在block的文档中所说的那样:   “As an optimization, block storage starts out on the stack—just like blocks themselves do.

           但为什么数组中的第一个block可以运行通过呢?打印后你会发现,第一个block是NSMallocBlock, 是因为malloc创造的数组是在堆上的,后来的2个block还是NSStackBlock。你如果把代码改成这样,就能通过了:

    - (NSMutableArray*) getBlockArray:(int)num

    {

    return [[NSMutableArray alloc] initWithObjects:

    ^{ NSLog(@"this is block 0:%i", num); },

    [^{ NSLog(@"this is block 1:%i", num); } copy],

    [^{ NSLog(@"this is block 2:%i", num); } copy],

    nil];

    }

          因为在使用copy方法后,block会被copy到堆上,你会发现,后面的两个block都变成了NSMallocBlock对象。根本原因就是,Block对象在栈上分配,block的引用指向栈帧内存,而当方法调用过后,指针指向的内存上写的是什么数据就不确定了。但是,如果你把NSMutableArray* array =  [self getBlockArray:num]; 改成

    NSMutableArray* array = [[NSMutableArray alloc] initWithObjects:

    ^{ NSLog(@"this is block 0:%i", num); },

    ^{ NSLog(@"this is block 1:%i", num); },

    ^{ NSLog(@"this is block 2:%i", num); },

    nil];

          此时,不用函数体来返回array也能通过的。因为,当一个函数调用完返回后它会释放该函数中所有的栈空间,所以函数体内的block就变成了野指针。而直接使用initWithObjects此时block还没有进行释放,换句话说,就是block还在进行引用。

          所以说,你要知道如何使用block的引用,参考以下几点:

    1   Block_copy与copy等效,Block_release与release等效;

    2    对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;

    3    NSGlobalBlock:retain、copy、release操作都无效;

    4    NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],(补:在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlockcopy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。

    5     NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;

    6     尽量不要对Block使用retain操作, 尽量使用copy。

    如何理解好block的内存管理,我会在使用中慢慢理解以及消化,此次深入只是一次探讨,希望对大家有用吧。

    相关文章

      网友评论

        本文标题:ios中block的深入理解

        本文链接:https://www.haomeiwen.com/subject/rqohrttx.html