Block

Block

Block 对象与一般的类实例对象有所不同,一个主要的区别就是分配的位置不同,block 默认在栈上分配,一般类的实例对象在堆上分配。在 MRC 中使用 Block_copy() 和 Block_release() 将 block 变量拷贝到堆内存。在 ARC 中如果 block 参数和返回值中都没有 __strong id ,那么会自动加上 copy 和 release。copy 会将 block 变量拷贝到堆内存中。

_NSConcreteStackBlock:位于栈内存,函数返回后Block将无效
_NSConcreteMallocBlock:位于堆内存
_NSConcreteGlobalBlock:没有引用外部变量


Block内引用外部变量的问题

打印注释 : [指针地址 | 指针指向内存地址 | 对象引用计数]

强引用:
Block 强引用所引用变量。

- (void) blockVariableStrongReferenceTest {
    NSObject *obj = [[NSObject alloc] init];
    BLog(@"%@",obj); // [0x7fff543d0c98(栈) | 0x7fcb1bd22390(堆) | 1]
    void(^testBlock)() = ^(){
        BLog(@"%@",obj); 
    };
    testBlock(); // [0x7fcb1c903fb0(堆) | 0x7fcb1bd22390(堆) | 2]
    // Block 外部尝试将 obj 置为 nil
    obj = nil; // ARC 中 obj = nil 相当于 MRC 中的 [obj release]
    testBlock(); // [0x7fcb1c903fb0(堆) | 0x7fcb1bd22390(堆) | 1] 
}

分析:

方法内部的 obj 变量在栈中,变量内存地址 0x7fff543d0c98,所指堆内存地址 0x7fcb1bd22390,引用计数 1。

Block 中 obj 指针地址变化是因为 block 对象 copy 到堆中,block 对象强引用 obj,obj 也会复制到堆中。此时两个变量(一堆一栈)同时拥有一块堆内存的所有权,obj 引用计数 2。

上面的例子不会造成循环引用,因为 obj 并不是强引用 testBlock 对象。但是,在平时开发过程中,难免会遇到 当前对象强引用 block 对象 :    
@property (nonatomic , copy) void (^blockT) (void); 
- (void) testStrongRef {
    __weak typeof(self) weakSelf = self;
    self.blockT = ^{
        // 正常
        NSLog(@"%@",weakSelf);
    };
    self.blockT();
}

- (void) testBlock {
    self.blockT = ^{
        // 系统会提示造成循环引用
        NSLog(@"%@",self);
    };
    self.blockT();
}

弱引用:

- (void)blockVariableWeakReferenceTest {
    NSObject *obj = [[NSObject alloc] init];
    BLog(@"%@",obj); // [0x7fff543d0c98(栈) | 0x7fcb1bd2fee0(堆) | 1] 
    __weak NSObject *weakObj = obj;
    BLog(@"%@", weakObj); // [0x7fff543d0c90(栈)) | 0x7fcb1bd2fee0(堆) | 1] 
    void(^testBlock)()= ^(){
        BLog(@"%@",weakObj);
    };
    testBlock(); // [0x7fcb1bc072b0(堆) | 0x7fcb1bd2fee0(堆) | 1] 
    obj = nil; 
    testBlock(); // [0x7fcb1bc072b0(堆) | 0x0(堆) | 0] 
}

分析:

第一次调用 testBlock() 会发现 weakObj 的指针地址已经转移到堆上。

使用 __weak 限定符确实可以规避循环引用的问题,但是仔细阅读代码会发现其实还隐藏着另一个问题。 testBlock 弱引用了 weakObj,但是 weakObj 只是 obj 的弱引用,weakObj 只是和 obj 指向同一块堆内存,但是这块内存是否释放与 weakObj 没有关系。那么问题就来了,如果在 testBlock 执行之前,obj 被释放了(__weak 是安全释放 = nil),就会导致 testBlcok 引用一个 nil 对象,虽然不会造成野指针问题,但是达不到我们预期的效果。所以,这种情况下,testBlock 又需要强引用 weakObj :


// 多线程时Block生命周期内对象安全
- (void)blockVariableMutiThreadTest {
    NSObject *obj = [[NSObject alloc]init]; // [0x7fff51888c98(栈) | 0x7f9413c1c040(堆) | 1]          
    __weak NSObject *weakObj = obj; // [0x7fff51888c90(栈) | 0x7f9413c1c040(堆) | 1]
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        __strong NSObject *strongObj = weakObj;
        sleep(3);
        // 此时 obj = nil; 已经调用
        BLog(@"weakObj - block", weakObj); // [0x7f9413d9a880(堆) | 0x7f9413c1c040(堆) | 1]
        BLog(@"strongObj - block", strongObj); // [0x1187e2e08(栈) | 0x7f9413c1c040(堆) | 2]
    });
    sleep(1);
    obj = nil;
    // 此时 strongObj 还未释放,因为 block 还未执行完
    BLog(@"weakObj-1", weakObj); // [0x7fff51888c90(栈) | 0x7f9413c1c040(堆) | 1] 
    sleep(4); 
    // 此时 strongObj 已经释放
    BLog(@"weakObj-2", weakObj); // [0x7fff51888c90(栈) | 0x0(堆) | 0]
}

这样只能相对的减少 blcok 使用中引用对象提前释放带来的影响。如果引用对象在 block 执行之前就被释放,那么我们暂时是无能为力,只能加条件判定了。

__block:

__block 限定符在 MRC 中不会保留对象(不会调用 -retain),在 ARC 中会。

- (void)blockVariable {
    NSObject *obj = [[NSObject alloc]init]; // [0x7fff5dd2ec78(栈) | 0x7fa082661a00(堆) | 1]
    __block NSObject *blockObj = obj; // [0x7fff5dd2ec70(栈) | 0x7fa082661a00(堆) | 2]
    obj = nil;
    void(^testBlock)() = ^(){
        BLog(@"内部blockObj - block",blockObj); // [0x7fa084905838(堆) | 0x7fa082661a00(堆) | 1]
        NSObject *obj2 = [[NSObject alloc]init]; // [0x7fff5dd2eba8(栈) | 0x7fa082661a00(堆) | 1]
        blockObj = obj2;
        BLog(@"blockObj - block",blockObj); // [0x7fa084905838(堆) | 0x7fa082661a00(堆) | 1]
    };
    testBlock();
    BLog(@"外部blockObj -3",blockObj);   // [0x7fa084905838(堆) | 0x7fa084916660(堆) | 1]
}

关于 block 的疑问:
block 是异步的吗?
不是,需要自己开线程。


block 底层分析
iOS Block捕获外部变量和ARC自动拷贝block
推荐 : 深入理解 block

转载于:https://www.cnblogs.com/imock/p/6692432.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值