OC底层-Block本质(5、捕获的变量什么时候销毁)

疑问

block通常使用过程当中都是对对象变量的捕获,那么对象变量的捕获同基本数据类型变量相同吗? 当在block中访问的为对象类型时,对象何时会销毁?ios

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            block = ^{
                NSLog(@"------block内部%d",person.age);
            };
            // 执行完毕,person没有被释放
        }
        NSLog(@"--------");
        // Person --- dealloc
    }
    return 0;
}
复制代码

大括号执行完毕以后,person不会被释放。,person为auto变量,传入的block的变量一样为person,即block有一个强引用引用person,因此block不被销毁的话,person也不会销毁。c++

查看源代码确实如此bash

将上述代码转移到MRC环境下,在MRC环境下即便block还在,person却被释放掉了。由于MRC环境下block在栈上,栈上的block对外面的person不会进行强引用。iphone

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            block = ^{
                NSLog(@"------block内部%d",person.age);
            };
            [person release];
            // Person --- dealloc
        }
        NSLog(@"--------");
        
    }
    return 0;
}

block调用copy操做以后,person不会被释放。

block = [^{
   NSLog(@"------block内部%d",person.age);
} copy];


复制代码

只须要对栈空间的block进行一次copy操做,将栈空间的block会拷贝到堆中,person就不会被释放,说明堆空间的block可能会对person进行一次retain操做,以保证person不会被销毁。堆空间的block本身销毁以后也会对持有的对象进行release操做。函数

也就是说栈空间上的block不会对对象强引用,堆空间的block有能力持有外部调用的对象,即对对象进行强引用或去除强引用的操做。ui

__weak

__weak添加以后,person在做用域执行完毕以后就被销毁了。spa

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            __weak Person *waekPerson = person;
            block = ^{
                NSLog(@"------block内部%d",waekPerson.age);
            };
        }
        NSLog(@"--------");
    }
    return 0;
}

复制代码

将代码转化为c++来看一下上述代码之间的差异。 __weak修饰变量,须要告知编译器使用ARC环境及版本号不然会报错,添加说明-fobjc-arc -fobjc-runtime=ios-8.0.03d

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m指针

__weak修饰的变量,在生成的__main_block_impl_0中也是使用__weak修饰

__main_block_copy_0 和 __main_block_dispose_0

当block中捕获对象类型的变量时,咱们发现block结构体__main_block_impl_0的描述结构体__main_block_desc_0中多了两个参数copy和dispose函数,查看源码:code

copy和dispose函数中传入的都是__main_block_impl_0结构体自己。

copy本质就是__main_block_copy_0函数,__main_block_copy_0函数内部调用_Block_object_assign函数,_Block_object_assign中传入的是person对象的地址,person对象,以及8。

dispose本质就是__main_block_dispose_0函数,__main_block_dispose_0函数内部调用_Block_object_dispose函数,_Block_object_dispose函数传入的参数是person对象,以及8。

_Block_object_assign函数调用时机及做用

当block进行copy操做的时候就会自动调用__main_block_desc_0内部的__main_block_copy_0函数,__main_block_copy_0函数内部会调用_Block_object_assign函数。

_Block_object_assign函数会自动根据__main_block_impl_0结构体内部的person是什么类型的指针,对person对象产生强引用或者弱引用。能够理解为_Block_object_assign函数内部会对person进行引用计数器的操做,若是__main_block_impl_0结构体内person指针是__strong类型,则为强引用,引用计数+1,若是__main_block_impl_0结构体内person指针是__weak类型,则为弱引用,引用计数不变。 _Block_object_dispose函数调用时机及做用

当block从堆中移除时就会自动调用__main_block_desc_0中的__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数。

_Block_object_dispose会对person对象作释放操做,相似于release,也就是断开对person对象的引用,而person到底是否被释放仍是取决于person对象本身的引用计数。

总结

一旦block中捕获的变量为对象类型,block结构体中的__main_block_desc_0会出两个参数copy和dispose。由于访问的是个对象,block但愿拥有这个对象,就须要对对象进行引用,也就是进行内存管理的操做。好比说对对象进行retarn操做,所以一旦block捕获的变量是对象类型就会会自动生成copy和dispose来对内部引用的对象进行内存管理。

当block内部访问了对象类型的auto变量时,若是block是在栈上,block内部不会对person产生强引用。不论block结构体内部的变量是__strong修饰仍是__weak修饰,都不会对变量产生强引用。

若是block被拷贝到堆上。copy函数会调用_Block_object_assign函数,根据auto变量的修饰符(__strong,__weak,unsafe_unretained)作出相应的操做,造成强引用或者弱引用

若是block从堆中移除,dispose函数会调用_Block_object_dispose函数,自动释放引用的auto变量。
复制代码

疑问

person什么时候销毁

一、

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *person = [[Person alloc] init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",person);
    });
    NSLog(@"touchBegin----------End");
}

复制代码

二、

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *person = [[Person alloc] init];
    
    __weak Person *waekP = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",waekP);
    });
    NSLog(@"touchBegin----------End");
}

复制代码

三、

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *person = [[Person alloc] init];
    
    __weak Person *waekP = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        NSLog(@"weakP ----- %@",waekP);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"person ----- %@",person);
        });
    });
    NSLog(@"touchBegin----------End");
}

复制代码

四、

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *person = [[Person alloc] init];
    
    __weak Person *waekP = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        NSLog(@"person ----- %@",person);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"weakP ----- %@",waekP);
        });
    });
    NSLog(@"touchBegin----------End");
}

复制代码

总结疑问

一、block做为GCD API的方法参数时会自动进行copy操做,所以block在堆空间,而且使用强引用访问person对象,所以block内部copy函数会对person进行强引用。当block执行完毕须要被销毁时,调用dispose函数释放对person对象的引用,person没有强指针指向时才会被销毁。

故:在3秒后销毁person对象

二、block中对waekP为__weak弱引用,所以block内部copy函数会对person一样进行弱引用,当大括号执行完毕时,person对象没有强指针引用就会被释放。所以block块执行的时候打印null。

故:立马销毁person对象

第三个和第四个你们可自行验证。

相关文章
相关标签/搜索