OC对象 - Block-对象类型的auto变量

OC对象 - Block-对象类型的auto变量

前言

本章主要了解Block访问对象类型auto变量时,底层主要做了什么,对变量有什么影响

1. Block内部访问了对象类型的auto变量

block内部访问了对象类型的auto变量时,此时block可能在栈上,也可能在堆上不同类型的block所表现的动作是不一样的

1.1 基础代码

创建一个ZSXPerson类,在- (void)dealloc打印一下,这样我们可以清楚看到ZSXPerson对象什么时候销毁

@interface ZSXPerson : NSObject

@end

@implementation ZSXPerson

- (void)dealloc {
    [super dealloc];
    NSLog(@"ZSXPerson --- %s", __func__);
}

@end

main.m

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        {
            ZSXPerson *person = [[ZSXPerson alloc] init];
        }
        NSLog(@"-----");
    }
    return 0;
}

{}内,初始化一个ZSXPerson对象,然后在在{}外打了断点

可以看到person对象出了{}马上销毁

接下来我们在此基础上,尝试不同 block 对person对象销毁时机的影响

1.2 Block访问person对象

增加一个block,内部访问person对象

ZSXBlock block;
{
    ZSXPerson *person = [[ZSXPerson alloc] init];
    person.age = 10;
    
    block = ^ {
        NSLog(@"%d", person.age);
    };
    
}
NSLog(@"block - %@", [block class]);
NSLog(@"-----");


此时断点停留在{}之后,按理说 person 已经出了作用域了,但实际却没有销毁

我们断点继续往下走

出了@autoreleasepool之后,person才销毁。这时候刚好 block 也已经出了作用域,block是会销毁的。因此,block里面对person有强引用,所以出了第一个{}的时候,虽然person已经走出作用域,但是此时block还在作用域内,它还持有person,所以person并不会释放

1.2.1 查看底层实现


block访问对象类型的auto变量后

  • block结构体中持有person成员的指针
  • __main_block_desc_0结构体中多了copydispose两个函数。这两个函数是用来管理block所持有变量的持有关系的

因为block里面持有了对象,对象本身在内存中是通过引用计数来管理内存的,因此block也需要对其负责内存管理

1.3 NSStackBlock类型block访问对象类型

NSStackBlock类型block本身就是随时可能释放的,所以NSStackBlock类型的block没有必要强持有访问对象

1.3.1 修改代码

代码中我们不使用strong变量接收block,这时候的block就是NSStackBlock类型的

ZSXBlock block;
{
    ZSXPerson *person = [[ZSXPerson alloc] init];
    person.age = 10;
    
    ^ {
        NSLog(@"%d", person.age);
    };
}
NSLog(@"block - %@", [block class]);
NSLog(@"-----");

打印结果:

person出了作用域马上就释放了,因此可以说明这时候的block并没有持有person

1.4 __weak修饰

使用__weak修饰person变量

ZSXBlock block;
        {
            ZSXPerson *person = [[ZSXPerson alloc] init];
            person.age = 10;
            
            __weak typeof(person) weakPerson = person;
            block = ^ {
                NSLog(@"%d", weakPerson.age);
            };
        }
        NSLog(@"block - %@", [block class]);
        NSLog(@"-----");

这时候又会发现:

  • person出了作用域就销毁了
  • block是堆上的__NSMallocBlock__类型

这是因为,底层实现中

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

2. 总结

block内部访问了对象类型的auto变量时

  • 如果block是在栈上,将不会对auto变量产生强引用

  • 如果block被拷贝到堆上

    1. 会调用block内部的copy函数
    2. copy函数内部会调用_Block_object_assign函数
    3. _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
  • 如果block从堆上移除

    1. 会调用block内部的dispose函数
    2. dispose函数内部会调用_Block_object_dispose函数
    3. _Block_object_dispose函数会自动释放引用的auto变量(release)

3. 拓展

block作为GCD API的方法参数时,系统会帮我们copy到堆上

如下代码,打印的是什么

3.1 aoto对象捕获

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

打印结果:

3.1.1 分析

black会捕获person,直到GCD执行完,blockperson一起释放

3.2 __weak对象

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

打印结果:

3.2.1 分析
  • 点击屏幕后,person立刻销毁了
  • 2秒后black执行

对于弱引用的对象,black内部也是弱引用,所以person出了作用域就销毁了

3.3 嵌套

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    ZSXPerson *person = [[ZSXPerson alloc] init];
    __weak typeof(person) weakPerson = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"weakPerson-------------%@", weakPerson);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"person-------------%@", person);
        });
    });
    
    NSLog(@"touchesBegan:withEvent");
}

打印结果:

3.3.1 分析
  • 点击屏幕
  • 1秒后执行第一个定时器
  • 2秒后执行第二个定时器
  • person等到两个定时器都执行完,也就是3秒后才销毁

再来一个例子:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    ZSXPerson *person = [[ZSXPerson alloc] init];
    __weak typeof(person) weakPerson = 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)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"weakPerson-------------%@", weakPerson);
        });
    });
    
    NSLog(@"touchesBegan:withEvent");
}

打印结果:

3.3.2 分析
  • 点击屏幕
  • 1秒后执行第一个定时器,同时person也销毁了
  • 2秒后执行第二个定时器

不管嵌套方式怎么样,person不会被提前销毁。这是因为程序会看整体里面有没有使用强引用,保证在该释放的时候才释放

@oubijiexi

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值