美文网首页
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 一定要用copy修饰吗?

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