• GCD:嵌套dispatch_async时__block对象的一个内存陷阱


    啥也不说,先上代码:

        dispatch_async(whatever_queue, ^{

            NSNumber* number = nil;

            number = @123;

            dispatch_async(main_queue, ^{

                NSLog(@"%@", number);

            });

        });

    嗯,很简单,目的很明确,异步生成个数字并在主线程中使用

    现在有一个需求,这个number和io操作有关,我想用一个同步串行queue的方式保证io的“加锁”操作

    ok 很简单 只要把number的取得dispatch_sync倒一个serial的queue就好了,like:

        dispatch_async(whatever_queue, ^{
            
            NSNumber* number = nil;
         //同步串行queue dispatch_sync(serial_queue,
    ^{ number = @123; }); dispatch_async(main_queue, ^{ NSLog(@"%@", number); }); });

    熟悉block的一看就能看出点问题,serial_queue那个block在block内修改了外部变量,所以得把number加上__block标记:

        dispatch_async(whatever_queue, ^{
            
            __block NSNumber* number = nil;//__block标记
            dispatch_sync(serial_queue, ^{
                number = @123;
            });
            dispatch_async(main_queue, ^{
                NSLog(@"%@", number);
            });
        });

    这下很符合我的世界观了

    乍一看,看上去没啥问题了,运行时挂掉:

    *** -[CFNumber respondsToSelector:]: message sent to deallocated instance 0x7411a90

    追踪之后发现挂在main_queue的NSLog上,访问number时候number已经被销毁了,why?

    ------探索之后发现:

    __block标记类型的对象会随着声明这个对象的block一同从栈空间拷贝到堆空间

    但__block的另一个特点就是不会因block内的引用而增加retain count

    直白来讲就是__block对象的生命区间的block被销毁之后、这个对象也会被销毁。

    再看刚才的代码

    声明number的区间是最外层的那个dispatch_async的block

    dispatch_async 之后block被提交到queue中、当然是运行了block的copy

    当外层block被运行后,运行到内部的async时不会等到内部async的block运行之后再退出,而是直接结束退出(了解GCD的就很好理解了)

    这时,外层的block已经完成使命,被释放了,同时释放的还有那个__block的number

    而这时,内层async的block很可能还没有开始执行,等到内层block执行时,用到外层已经释放了的对象,果断报错挂掉了。

    如果不加__block就没有这个问题 因为内部block使用的话会把引用的对象retain一次

    而面对我遇到的状况就只能找点方法解决了,我有一个简单的方法:

        dispatch_async(whatever_queue, ^{
            
            __block NSNumber* number = nil;
            dispatch_sync(serial_queue, ^{
                number = @123;
            });
            [number retian];//暂时retain使之不被释放
            dispatch_async(main_queue, ^{
                NSLog(@"%@", number);
                [number autorelease];//保证手动release
            });
        });    

    这里用autorelease的原因是因为有可能把这个对象外传给主线程或传给其他地方使用,这时传一个autorelease对象才对

    ---

    总结:这个陷阱可能有更优雅的解决办法,留底分享下。

    ps:GCD是个很不错的多线程工具,很容易上手,我们的目标是:主线程永远顺畅

  • 相关阅读:
    HTTP状态码表
    统计各国访客插件 flag counter
    获取和设置cookie的方法(5种)
    最简单的方式创建node.js 静态服务器
    js中map,forEach,filter,reduce等方法是ECMAScript5 Array新增方法
    ECMAScript各个版本简介及特性
    JSON的parse()和stringfy()方法
    javascript中的错误对象error
    JS window对象的top、parent、opener含义介绍
    postman的使用总结
  • 原文地址:https://www.cnblogs.com/sunnyxx/p/2742326.html
Copyright © 2020-2023  润新知