美文网首页
block 一定要用copy修饰吗?

block 一定要用copy修饰吗?

作者: 言霏 | 来源:发表于2020-04-20 15:25 被阅读0次

block分为三种类型:

  • __NSStackBlock
  • __NSMallocBlock
  • __NSGlobalBlock

不同类型的block储存在内存上的位置也不相同。内存分布:


image1.png

MRC下

  • 对于没有引用外界变量的block(不管用什么修饰的)都为__NSGlobalBlock,例子:
@property (nonatomic, copy) void (^copyBlock)(void);
@property (nonatomic, retain) void (^retainBlock)(void);

- (void)testBlock {
   void(^normalBlock)(int, int) = ^(int a, int b) {
       NSLog(@"normalBlock = %d", a + b);
   };
   NSLog(@"normalBlock %@", normalBlock);
   
   self.copyBlock = ^() {
       NSLog(@"copyBlock");
   };
   NSLog(@"self.copyBlock %@", self.copyBlock);
   
   self.retainBlock = ^() {
       NSLog(@"strongBlock");
   };
   NSLog(@"self.retainBlock %@", self.retainBlock);
}

结果:

2020-04-20 11:44:11.309201+0800 normalBlock <__NSGlobalBlock__: 0x10b37c038>
2020-04-20 11:44:11.309342+0800 self.copyBlock <__NSGlobalBlock__: 0x10b37c078>
2020-04-20 11:44:11.309445+0800 self.strongBlock <__NSGlobalBlock__: 0x10b37c098>
  • 引用了外界局部变量:
- (void)testBlock {
    
    int i = 0;
    void(^normalBlock)(int, int) = ^(int a, int b) {
        NSLog(@"normalBlock = %d %d", a + b, i);
    };
    NSLog(@"normalBlock %@", normalBlock);
    
    self.copyBlock = ^() {
        NSLog(@"copyBlock %d", i);
    };
    NSLog(@"self.copyBlock %@", self.copyBlock);
    
    self.retainBlock = ^() {
        NSLog(@"retainBlock %d", i);
    };
    NSLog(@"self.retainBlock %@", self.retainBlock);
}

结果:

2020-04-20 11:52:34.301115+0800 normalBlock <__NSStackBlock__: 0x7ffeecec40d8>
2020-04-20 11:52:34.301309+0800 self.copyBlock <__NSMallocBlock__: 0x6000029d5770>
2020-04-20 11:52:34.301398+0800 self.retainBlock <__NSStackBlock__: 0x7ffeecec4088>

此时normalBlock、self.retainBlock变为__NSStackBlock__,作用域和局部变量一样,出了方法外再调用就很有可能产生崩溃或者其引用的外部变量变为野指针(例子中的i)。
如果block内引用的为对象属性结果也是相同的:

@property (nonatomic, assign) int i;
  • 引用外界全局变量,block就都会变成__NSGlobalBlock类型的,例子:
static int i = 0;
- (void)testBlock {
    
    void(^normalBlock)(int, int) = ^(int a, int b) {
        NSLog(@"normalBlock = %d %d", a + b, i);
    };
    NSLog(@"normalBlock %@", normalBlock);
    
    self.copyBlock = ^() {
        NSLog(@"copyBlock %d", i);
    };
    NSLog(@"self.copyBlock %@", self.copyBlock);
    
    self.strongBlock = ^() {
        NSLog(@"strongBlock %d", i);
    };
    NSLog(@"self.strongBlock %@", self.strongBlock);
}

结果:

2020-04-20 15:07:07.417933+0800 normalBlock <__NSGlobalBlock__: 0x100002030>
2020-04-20 15:07:07.418412+0800 self.copyBlock <__NSGlobalBlock__: 0x100002070>
2020-04-20 15:07:07.418588+0800 self.strongBlock <__NSGlobalBlock__: 0x100002090>
  • __NSStackBlock类型的block对其进行copy可以变成__NSMallocBlock。例如:
- (void)testBlock {
    
    void(^normalBlock)(int, int) = ^(int a, int b) {
        NSLog(@"normalBlock = %d %d", a + b, self.i);
    };
    NSLog(@"normalBlock %@", normalBlock);
    NSLog(@"normalBlock copy %@", [normalBlock copy]);
    
    self.copyBlock = ^() {
        NSLog(@"copyBlock %d", self.i);
    };
    NSLog(@"self.copyBlock %@", self.copyBlock);
    
    self.retainBlock = ^() {
        NSLog(@"retainBlock %d", self.i);
    };
    NSLog(@"self.retainBlock %@", self.retainBlock);
    NSLog(@"self.retainBlock copy %@", [self.retainBlock copy]);
}

结果:

2020-04-20 14:34:57.815682+0800 normalBlock <__NSStackBlock__: 0x7ffeefbff540>
2020-04-20 14:34:57.816208+0800 normalBlock copy <__NSMallocBlock__: 0x10050c1e0>
2020-04-20 14:34:57.816285+0800 self.copyBlock <__NSMallocBlock__: 0x100502760>
2020-04-20 14:34:57.816379+0800 self.retainBlock <__NSStackBlock__: 0x7ffeefbff4f0>
2020-04-20 14:34:57.816456+0800 self.retainBlock copy <__NSMallocBlock__: 0x100600200>

__NSGlobalBlock类型的block对其进行copy依旧为__NSGlobalBlock

ARC下

  • 对于没有引用外界变量的block(不管用什么修饰的)依旧都为__NSGlobalBlock,例子:
@property (nonatomic, copy) void (^copyBlock)(void);
@property (nonatomic, strong) void (^strongBlock)(void);

- (void)testBlock {
    void(^normalBlock)(int, int) = ^(int a, int b) {
        NSLog(@"normalBlock = %d", a + b);
    };
    NSLog(@"normalBlock %@", normalBlock);
    
    self.copyBlock = ^() {
        NSLog(@"copyBlock");
    };
    NSLog(@"self.copyBlock %@", self.copyBlock);
    
    self.strongBlock = ^() {
        NSLog(@"strongBlock");
    };
    NSLog(@"self.strongBlock %@", self.strongBlock);
}

结果:

2020-04-20 14:58:14.180547+0800 normalBlock <__NSGlobalBlock__: 0x100002040>
2020-04-20 14:58:14.181382+0800 self.copyBlock <__NSGlobalBlock__: 0x100002080>
2020-04-20 14:58:14.181483+0800 self.strongBlock <__NSGlobalBlock__: 0x1000020a0>
  • 引用了外界局部变量:
- (void)testBlock {
    
    int i = 0;
    void(^normalBlock)(int, int) = ^(int a, int b) {
        NSLog(@"normalBlock = %d %d", a + b, i);
    };
    NSLog(@"normalBlock %@", normalBlock);
//    NSLog(@"normalBlock copy %@", [normalBlock copy]);
    
    self.copyBlock = ^() {
        NSLog(@"copyBlock %d", i);
    };
    NSLog(@"self.copyBlock %@", self.copyBlock);
    
    self.strongBlock = ^() {
        NSLog(@"strongBlock %d", i);
    };
    NSLog(@"self.strongBlock %@", self.strongBlock);
//    NSLog(@"self.strongBlock copy %@", [self.strongBlock copy]);
}

结果:

2020-04-20 15:12:14.453760+0800 normalBlock <__NSMallocBlock__: 0x100545fc0>
2020-04-20 15:12:14.454190+0800 self.copyBlock <__NSMallocBlock__: 0x10066fef0>
2020-04-20 15:12:14.454296+0800 self.strongBlock <__NSMallocBlock__: 0x100670700>

ACR下会将MRC下本为__NSStackBlock__类型的block copy__NSMallocBlock类型。
如果block内引用的为对象属性结果也是相同的:

@property (nonatomic, assign) int i;
  • 引用外界全局变量,block就都会变成__NSGlobalBlock类型的,例子:
static int i = 0;
- (void)testBlock {
    
    void(^normalBlock)(int, int) = ^(int a, int b) {
        NSLog(@"normalBlock = %d %d", a + b, i);
    };
    NSLog(@"normalBlock %@", normalBlock);
    
    self.copyBlock = ^() {
        NSLog(@"copyBlock %d", i);
    };
    NSLog(@"self.copyBlock %@", self.copyBlock);
    
    self.strongBlock = ^() {
        NSLog(@"strongBlock %d", i);
    };
    NSLog(@"self.strongBlock %@", self.strongBlock);
}

结果:

2020-04-20 15:18:01.315760+0800 normalBlock <__NSGlobalBlock__: 0x100002040>
2020-04-20 15:18:01.316288+0800 self.copyBlock <__NSGlobalBlock__: 0x100002080>
2020-04-20 15:18:01.316351+0800 self.strongBlock <__NSGlobalBlock__: 0x1000020a0>

小注意事项

- (void)testBlock {
    int x = 0;
    void(^normalBlock)(void) = ^{
        NSLog(@"%d", x);
    };
    NSLog(@"normalBlock: %@", normalBlock);
    NSLog(@"partReferencedBlock: %@", ^{
        NSLog(@"%d", x);
    });
    static int i = 0;
    NSLog(@"staticReferencedBlock: %@", ^{
        NSLog(@"%d", i);
    });
    NSLog(@"nothingReferencedBlock: %@", ^{
        NSLog(@"nothingReferencedBlock");
    });
}
MRC下
2020-04-20 16:55:45.363625+0800 normalBlock: <__NSStackBlock__: 0x7ffeefbff538>
2020-04-20 16:55:45.364103+0800 partReferencedBlock: <__NSStackBlock__: 0x7ffeefbff510>
2020-04-20 16:55:45.364178+0800 staticReferencedBlock: <__NSGlobalBlock__: 0x100002058>
2020-04-20 16:55:45.364258+0800 nothingReferencedBlock: <__NSGlobalBlock__: 0x100002078>
ARC下
2020-04-20 16:53:13.033638+0800 normalBlock: <__NSMallocBlock__: 0x1006a8140>
2020-04-20 16:53:13.034048+0800 partReferencedBlock: <__NSStackBlock__: 0x7ffeefbff510>
2020-04-20 16:53:13.034188+0800 staticReferencedBlock: <__NSGlobalBlock__: 0x100002068>
2020-04-20 16:53:13.034268+0800 nothingReferencedBlock: <__NSGlobalBlock__: 0x100002078>

直接打印partReferencedBlock都为__NSStackBlock__类型。
ARC下normalBlock、partReferencedBlock 的实现是相同的,为什么一个在堆区,一个在栈区?
这个现象叫做运算符重载。定义 normalBlock 的时候 = 实际上执行了一次 copy,为了管理 normalBlock 的内存,它被转移到了堆区。
只要block内部引用了全局变量(或者啥都没引用),就一定为__NSGlobalBlock__类型。

结论:

MRC 下,在定义 block 属性时,使用 copy 是为了把 block 从栈区拷贝到堆区,因为栈区中的变量出了作用域之后就会被销毁,无法在全局使用,而把栈区的属性拷贝到堆区中全局共享,就不会被销毁了。
ARC 下,不需要特别使用 copy 修饰,因为 strong 下的 block 属性也就在堆区。

参考:
https://www.jianshu.com/p/f0870fa95aac

相关文章

  • block学习

    block为什么是用copy修饰 block属性的声明,首先需要用copy修饰符,因为只有copy后的block才...

  • block 一定要用copy修饰吗?

    block分为三种类型: __NSStackBlock __NSMallocBlock __NSGlobalBlo...

  • OC中的copy

    1.所有被copy修饰的对象都会进行深copy吗? 答案:NO,例如block 全局block被copy修饰不会有...

  • iOS中为什么block用copy属性

    Block的声明和线程安全 Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆...

  • [转] iOS: ARC和非ARC下使用Block属性的问题

    1. Block的声明和线程安全 Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才...

  • block作为类的属性时用copy

    1. block作为类的属性时用copy Block属性的声明,首先需要用copy修饰符,因为只有copy后的Bl...

  • iOS随笔

    *Property属性 *沙盒 内存区域划分 Block为什么要用copy修饰 Block为什么不用retain修...

  • 9.Block的写法,及使用注意

    1.Block为什么要用copy来修饰 默认情况下,block是存档在栈中,可能被随时回收,通过copy操作可以使...

  • 关于block的浅层认识

    1.block要用copy修饰,还是用strong block本身是像对象一样可以retain,和release。...

  • block

    目录 一、通常作用; 二、block用来解决什么问题? 三、为什么要用copy修饰Block; 四、为什么Bloc...

网友评论

      本文标题:block 一定要用copy修饰吗?

      本文链接:https://www.haomeiwen.com/subject/lodrihtx.html