内存-②Block

内存-②Block

[toc]

iOS-Block本质

iOS-Block本质

block本质上也是一个OC对象,它内部也有个isa指针,最终继承NSObject。

1
NSGlobalBlock <--__NSGlobalBlock <--NSBlock <-- NSObject

Q:block有哪几种类型 及 各类型的block在内存中如何分配的?

block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型。

:动态分配内存,需要程序员自己申请,程序员自己管理

:自动分配内存,自动销毁,先入后出,栈上的内容存在自动销毁的情况

  • NSGlobalBlock 在数据区

    1
    2
    3
    void (^block1)(void) = ^{
    NSLog(@"block1");
    };
  • NSMallocBlock 在堆区

    1
    2
    3
    4
    int age1 = 1;
    void (^block2)(void) = ^{
    NSLog(@"block2:%d", age1);
    };
  • NSStackBlock 在栈区

    1
    2
    3
    4
    int age2 = 2;
    NSLog(@"%@", [^{
    NSLog(@"block3:%d", age2);
    } class]);

附:打印block类型NSLog(@"%@", [block1 class]);

问:以下各情况,Person的对象什么时候才销毁?或Person对象在block上是如何操作的?

有Person类如下:

1
2
3
4
5
6
7
8
9
#import "Person.h"

@implementation Person

- (void)dealloc {
NSLog(@"Person dealloc");
}

@end

了解解题的基础知识:

1、当对象类型的auto变量被block内部访问时,是强引用还是弱引用?(延伸:引用过后,block内部无法修改auto变量值,除非使用__block)

  • 如果block在空间,不管外部变量是强引用还是弱引用,block都会弱引用访问对象
  • 如果block在空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用

2、GCD API的方法参数block,在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上。

附:在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上的几种情况?

  • 1.block作为函数返回值时

  • 2.将block赋值给__strong指针时

  • 3.block作为Cocoa API中方法名含有usingBlock的方法参数时

  • 4.block作为GCD API的方法参数时

    1
    2
    3
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

    });

1、问: 下列代码中的Person的对象什么时候才销毁?

1
2
3
4
5
6
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 10;

NSLog(@"age:%d", person.age);
}

答:方法代码执行完就person就销毁了

2、问:以下gcd的block中引用 Person的对象什么时候才销毁?

1
2
3
4
5
6
7
8
9
10
11
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 10;

// 不使用__weak修饰
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"age:%d", person.age); // 堆block会对Person强引用,故而也就只有block销毁时候Person才会被释放。这里是2秒后销毁。
});

NSLog(@"touchesBegan");
}

答:

1、gcd的block默认会做copy操作,即dispatch_after的block是堆block。

2、我们知道如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用

所以这里外部的Person没有声明为__weak,所以堆block会对Person强引用,故而也就只有block销毁时候Person才会被释放。这里是2秒后销毁。(如果没有gcd)

3、续问:如果上述Person被添加上__weak修饰,那Person什么时候释放?会造成什么问题?

续答:此时上述的代码会变为

1
2
3
4
5
6
7
8
9
10
11
12
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 10;

// 不使用__weak修饰
__weak Person *weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"person:%p", weakPerson); // 当Person被添加上__weak修饰后,堆block会对Person弱引用。而在touchesBegan函数结束后,由于Person就会被释放,所以2秒后gcd无法捕捉到Person的后果。所以此处person 是 nil,打印的%p地址,是0x0。
});

NSLog(@"touchesBegan");
}

同样,还是我们知道如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用。所以当Person被添加上__weak修饰后,堆block会对Person弱引用。而在touchesBegan函数结束后,由于Person就会被释放,所以2秒后gcd无法捕捉到Person的后果。即上述的输出结果是

1
person 是 nil,打印的%p地址,是0x0。

4、上述代码使用__weak后,变量被释放了,那怎么防止block持有的对象提前释放

Q:block的属性修饰词为什么是copy?

block一旦没有进行copy操作,就不会在堆上
block在堆上,程序员就可以对block做内存管理等操作,可以控制block的生命周期

Q:当block被copy到堆时,对__block修饰的变量做了什么?

  • 会调用block内部的copy函数
  • copy函数内部会调用_Block_object_assign函数
  • _Block_object_assign函数会对__block变量形成强引用(retain)
  • 对于__block 修饰的变量 assign函数对其强引用;对于外部对象 assign函数根据外部如何引用而引用

Q:当block从堆中移除时,对__block修饰的变量做了什么?

  • 会调用block内部的dispose函数
  • dispose函数内部会调用_Block_object_dispose函数
  • _Block_object_dispose函数会自动释放引用的__block变量(release)

二、__block修饰符

Q:__block int age = 10,系统做了哪些?

Q:__block 修饰符作用?