Block 使用的小结 有转的成分

block 简介
block 作为在 C语言的扩展,并不是高新技术,和其他语言的闭包或 lambda 表达式是一回事.需要注意的是由于 Objective-C在 iOS 中不支持 GC机制,使用 block 必须自己管理内存,而内存管理正是使用 bloc 坑最多的地方,错误的内存管理会导致 return cycle 内存泄露要么内存被提前释放了导致 crash.block 的使用很想函数指针,不过于函数最大的不同是:block 可以访问函数意外.词法作用域以内的外部变量的值.换句话说,block 不仅实现函数的功能,还能携带函数的执行环境.
可以这样理解,block 其实包含两个部分内容.
1.block 执行的代码,这是在编译的饿时候已经生成的;
2.一个包含 block 执行是需要的所有外部变量值的数据结构.block 将使用到.作用于附近到的变量的值简历一份快照拷贝到栈上.
block 与函数另一个不同是,block 类似 Obj 的队形,可以使用自动释放池管理内存(但 block 并不完全等于 Obj 对象,后面详细说明).
block 的类型与内存管理
根据 block 在内存中的位置分为三种类型
NSGlobalBlock,NSStackBlock,NSMallockBlobk.
NSGlobalBlock:类似函数 位于 text 段;
NSStackBlock:位于栈内存,函数返回后 Block 将无效;
NSMallocBlock:位于堆内存;
一.NSGlobalBlock 如下,我们可以通过是否引用外部变量识别,未饮用外部变量即为 NSGlobalBlock,可以当做函数使用

int main(int argc, const char * argv[]) {
    //创建一个 NSGlobalBlock
    float(^sum)(float,float)=^(float a,float b){
        return a+b;
    };
    NSLog(@"block is %@",sum);//打印结果 block is <__NSGlobalBlock__: 0x100001050>

    return 0;
}

NSStackBlock如下

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

    NSArray *testArr=@[@"1",@"2"];

    void(^TestBlock)(void)=^{

        NSLog(@"testArr:%@",testArr);

    };
    NSLog(@"block is %@",^{

        NSLog(@"testArr :%@",testArr);

    });//打印结果 block is <__NSStackBlock__: 0x7fff5fbff7d8>
    //打印可看出 block 是一个 NSStackBlock,即在栈上,当函数返回时 block 将无效


    NSLog(@"block is %@",TestBlock);
    //打印结果 block is <__NSMallocBlock__: 0x100103b50>

    //上面这句话在 非 ARC 中打印是 NSStackBlock. 但是在 ARC中就是 NSMallocBlock
    //即在 ARC 中默认会将 block 从栈复制到堆上,而在非 ARC 中需要手动 copy.


    return 0;
}

3.NSMallocBlock 只需要对 NSStackBlock 进行 copy 操作就可以获取,但是 retain 操作不可以,会在下面说明 Block 的 copy,retain,release 操作:
不同于 NSObjiect 的 copy,retain,release 操作:
Block_copy 与 copy等效,Block_release 于 release 等效:
对 Block 不管是 retain,copy,release 操作都无效:
NSStackBlock:retain,release操作无效,必须注意的是 ,NSStackBlock在函数返回后,Block 内存将被回收.即使 retain 也没用.容易犯的错误是[mutableArray addObject:stackBlock],(在 ARC 中不用担心此问题)
NSMallocBlock 支持 retain,release,虽然 retain count 始终是1,但内存管理器中仍然会增加,减少计数,copy 之后不会生成新的对象,只是增加了一次引用.类似 retain;
尽量不要对 Block使用retain操作
Block 对外部变量的存取管理
基本数据类型
1.局部布局
局部自动变量,在 Block 中只读.Block 定义时 copy 变量的值.在 Block 中作为常量使用,所以即使变量的值在 Block 外改变,也不影响他在 Block 中的值

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

    int base=100;

    long(^sum)(int ,int)=^long(int a,int b){

        return base+a+b;

    };

    base=0;

    printf("%ld\n",sum(1,2));

    //这里输出的是103,而不是3,因为快内的 base 拷贝的常量100
    return 0;
}

2.static 修饰符修饰的全局变量
以为全局变量或者静态变量在内存中的地址是固定的,Block 在读取该变量的时候是直接从其所在的呢村读出的,获取到的是最新值,而不是在定义时 copy 的常量,

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

    static int base=100;

    long(^sum)(int,int)=^long(int a,int b){

        base ++;

        return base+a+b;
    };

    base=0;
    printf("%ld\n",sum(1,2));

    //这里输出的是4,而不是103,因为 base 被设置为0

    printf("%d\n",base);

    //这里输出1,在块中被自加了

    return 0;
}

__BLOCK修饰的变量
Block 变量,被__block 修饰的变量称为 Block 变量.基本类型的 Block 变量等效于全局变量,或静态变量.Block 被另一个 Block 使用时,另一个 Block 被 copy 到堆上时,被使用的 Block 也会被 copy.但作为参数的 Block 不会不会发生 copy
Objc 对象
Block 对于 objc 对象的内存管理较为复杂.这里要分 static global local block 变量分析
循环引用 retain cycle
循环义勇是指两个对象相互强引用了对方,即 retain 了对方,从而导致谁也释放不了谁的内存泄露问题.如生命一个 delegate 是一般用 assign 而不能用 retain 或 strong,因为一旦你那么做了,很大可能一起循环引用.在以往的项目中,我几次用动态内存检查发现了循环引用导致内存泄露的问题
这里将的是 block 得循环义勇问题,因为 block 在拷贝到堆上的时候,会 retain 起引用的外部变量,那么如果 block 中如果引用了他的宿主对象,那么很有可能引起循环引用

self.myblock = ^{

            [self doSomething];
        };
//为测试循环引用,写了些测试代码用于避免循环引用的方法

- (void)dealloc
{

    NSLog(@"no cycle retain");
}

- (id)init
{
    self = [super init];
    if (self) {

#if TestCycleRetainCase1

        //会循环引用
        self.myblock = ^{

            [self doSomething];
        };
#elif TestCycleRetainCase2

        //会循环引用
        __block TestCycleRetain *weakSelf = self;
        self.myblock = ^{

            [weakSelf doSomething];
        };

#elif TestCycleRetainCase3

        //不会循环引用
        __weak TestCycleRetain *weakSelf = self;
        self.myblock = ^{

            [weakSelf doSomething];
        };

#elif TestCycleRetainCase4

        //不会循环引用
        __unsafe_unretained TestCycleRetain *weakSelf = self;
        self.myblock = ^{

            [weakSelf doSomething];
        };

#endif

        NSLog(@"myblock is %@", self.myblock);

    }
    return self;
}

- (void)doSomething
{
    NSLog(@"do Something");
}

int main(int argc, char *argv[]) {
    @autoreleasepool {
        TestCycleRetain* obj = [[TestCycleRetain alloc] init];
        obj = nil;
        return 0;
    }
}

经过上面的测试发现,在加了__weak 和__unsafe_unretained的变量引入后,TestCycleRetain 方法可以正常执行 dealloc 方法,而不转换和使用)__block 转换的变量都会引起循环引用.
因此防止循环引用的方法就是
__unsafe_unretainedTestCuycleRetain *weakSelf=self;
@end

你可能感兴趣的:(Block 使用的小结 有转的成分)