美文网首页
ARC下声明的block在堆区、栈区、全局区?

ARC下声明的block在堆区、栈区、全局区?

作者: 迷路的安然和无恙 | 来源:发表于2019-03-18 10:32 被阅读0次

    分两种情况

    • Block不引用外部变量
    
    typedef void(^SDBlock)(void);
    
    @interface ViewController ()
    //@property (nonatomic, strong) NSDictionary *dict;
    @property (nonatomic, copy) NSMutableString *mStr;
    @property (nonatomic, strong) SDBlock block;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // 全局区
        static int b = 20;
        // 栈区
        int a = 10;
        typeof(self) __weak weakSelf = self;
        
        // block作为属性
        self.block = ^{
    //        weakSelf.array;
        };
        NSLog(@"\n 栈: %p", &a);
        NSLog(@"\n 全局: %p", &b);
        NSLog(@"\n block: %p", self.block);
        
        // block作为参数
        [Person testBlockSectionCallback:^(NSString * _Nonnull str) {
            
        }];
    /* Person内部实现
       + (void)testBlockSectionCallback:(void(^)(NSString *str))callback {
        if (callback) {
            NSLog(@"\nmethod block: %@", callback);
        }
    }
    **/
    }
    

    输出结果

    // 内存地址
    2019-06-25 14:41:17.405952+0800 Test[12104:1070611] 
     栈: 0x16d6651b4
    2019-06-25 14:41:17.405991+0800 Test[12104:1070611] 
     全局: 0x10283ffe8
    2019-06-25 14:41:17.406005+0800 Test[12104:1070611] 
     block: 0x102820a98
    2019-06-25 14:41:17.406047+0800 Test[12104:1070611] 
    method block: 0x102820ad8
    
    // *****打印block类型查看******
    2019-06-25 14:43:29.251564+0800 Test[12116:1071345] 
     栈: 0x16d9091b4
    2019-06-25 14:43:29.251599+0800 Test[12116:1071345] 
     全局: 0x10259bfe8
    2019-06-25 14:43:29.251639+0800 Test[12116:1071345] 
     block: <__NSGlobalBlock__: 0x10257ca98>
    2019-06-25 14:43:29.251691+0800 Test[12116:1071345] 
    method block: <__NSGlobalBlock__: 0x10257cad8>
    
    

    在不引用外部变量时,无论block作为属性还是作为方法中的参数,它都在内存中的全局区。

    • 引用外部变量
        // 全局区
        static int b = 20;
        // 栈区
        int a = 10;
        typeof(self) __weak weakSelf = self;
        
        // block作为属性
        self.block = ^{
            NSLog(@"%@", weakSelf.array);
        };
        NSLog(@"\n 栈: %p", &a);
        NSLog(@"\n 全局: %p", &b);
        NSLog(@"\n block: %@", self.block);
        
        // block作为参数
        [Person testBlockSectionCallback:^(NSString * _Nonnull str) {
            NSLog(@"%@", weakSelf.array);
        }];
    

    // 输出结果

    2019-06-25 14:49:24.994145+0800 Test[12152:1073586] 
     栈: 0x16d31119c
    2019-06-25 14:49:24.994192+0800 Test[12152:1073586] 
     全局: 0x102b93fd8
    2019-06-25 14:49:24.994253+0800 Test[12152:1073586] 
     block: <__NSMallocBlock__: 0x282a15110>
    2019-06-25 14:49:24.994337+0800 Test[12152:1073586] 
    method block: <__NSStackBlock__: 0x16d311130>
    

    从表象看,作为属性的block在堆区内存,而方法中的block引用了外部变量后,内存在栈区。

    带着上述表象问题,来看下GCD中的block在内存中的哪个区?

    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    

    GCD中使用的是dispatch_block_t,为了方便获取block内存区段,我仿照GCD将dispatch_block_t作为方法的参数。

    - (void)go:(dispatch_block_t)block {
        NSLog(@"\n gcd: %@", block);
    }
    

    在未引用尾部变量时,dispatch_block_t位于全局区,在引用外部变量后,dispatch_block_t和上面Person方法中的block一致,表现的也是在栈区。

    但是,这和栈内存的内存管理是不相符的,设置在栈上的block,如果其作用域结束,该block就被销毁,但是此处的block内存明显是没有被回收的。

    在查看Apple文档的时候,它的解释如下:

    
     * @typedef dispatch_block_t
     *
     * @abstract
     * The type of blocks submitted to dispatch queues, which take no arguments
     * and have no return value.
     *
     * @discussion
     * When not building with Objective-C ARC, a block object allocated on or
     * copied to the heap must be released with a -[release] message or the
     * Block_release() function.
     *
     * The declaration of a block literal allocates storage on the stack.
     * Therefore, this is an invalid construct:
     * <code>
     * dispatch_block_t block;
     * if (x) {
     *     block = ^{ printf("true\n"); };
     * } else {
     *     block = ^{ printf("false\n"); };
     * }
     * block(); // unsafe!!!
     * </code>
     *
     * What is happening behind the scenes:
     * <code>
     * if (x) {
     *     struct Block __tmp_1 = ...; // setup details
     *     block = &__tmp_1;
     * } else {
     *     struct Block __tmp_2 = ...; // setup details
     *     block = &__tmp_2;
     * }
     * </code>
     *
     * As the example demonstrates, the address of a stack variable is escaping the
     * scope in which it is allocated. That is a classic C bug.
     *
     * Instead, the block literal must be copied to the heap with the Block_copy()
     * function or by sending it a -[copy] message.
    

    结论是,其实这个dispatch_block_t并不在栈上,而是在其他内存区域。
    我没懂这个解释,我也没有验证出来苹果上面说的 "这是个经典的 C bug"。
    在这里有人和我遇到相同的疑惑。

    在ARC模式下,blockcopy操作是发生在以下时机:

    • 主动调用[Block copy]方法
    • Block作为函数返回值
    • 将Block赋值给__strong修饰符id类型的类或Block类型的成员变量时。
    • 方法名中含有usingBlock的Cocoa框架方法或GCD的API传递block时。

    了解了copy操作发生的时机,就能够解释为什么会有上面的现象了,作为引用外部变量属性的block,从栈区copy到堆区发生在初始化的时候,也就是

    // 这个操作会触发[block copy]
    self.block = ^{
        NSLog(@"%@", weakSelf.array);
    };
    

    在方法中的block的copy,发生在方法调用时,也就是

    if (block) {
        block();
    }
    

    所以,第一次获取的栈地址是原始的未发生copy操作之前的内存。

    结论:
    未引用外部变量时

    block无论作为属性还是参数,都在全局区。

    引用外部变量时

    block声明在栈区,但在block调用、赋值给其它strong修饰的变量时,会发生copy操作,从栈区copy到堆区,在GCD及其它方法中的block都是在调用时发生copy操作。

    参考

    天下文章一大抄,也不知道下面的文章是谁抄谁的,我没有找到源文档,只好引用你们吧
    Block被copy堆上的时机
    Block copy时机

    相关文章

      网友评论

          本文标题:ARC下声明的block在堆区、栈区、全局区?

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