美文网首页iOS 深度好文
iOS动态库导致的单例无效问题

iOS动态库导致的单例无效问题

作者: 生光 | 来源:发表于2018-03-06 10:10 被阅读150次

    这里的单例无效,指的是虽然用了singleton模式,本该在APP生命周期内,只有一个单例对象,一个内存地址,却出现多个对象的问题。如下是singleton模式代码:

    @implementation AObject
    
    + (instancetype)shareInstance {
        static dispatch_once_t onceToken;
        static AObject *instance;
        dispatch_once(&onceToken, ^{
            instance = [AObject new];
            NSLog(@"AObject: %p", instance);
            instance.name = @"A";
        });
        return instance;
    }
    @end
    

    基本上通过[AObject shareInstance],在工程任何地方取得的对象,都是同一个内存地址的单例对象。但发现某种存在动态库情况下,出现[AObject shareInstance]取到多个不同对象的情况。

    单例无效demo测试

    有个简单的测试demo

    目录结构:
    .
    ├── LA0
    ├── LB0
    ├── LC0
    ├── LC1
    ├── MainProj
    └── Test.xcworkspace
    工程结构:
    MainProj
    ------------------
    LC0    |    LC1
    ------------------
    LB0
    ------------------
    LA0
    ------------------
    

    Test workspace中,MainProj是主工程,LC0、LC1、LB0、LA0都是依赖的framework。如图示,MainProj依赖LC0、LC1;LC0、LC1依赖LB0;LB0依赖LA0。但在build phases中,上层的link binary with libraries不仅将直属下层加入,同时也将下层的下层加入。例如LC0的配置:


    WX20180306-093802@2x.png

    单例类AObject在LA0中,上层库、主工程都会调用单例对象。

    1. 所有framework都是static library

    设置LC0、LC1、LB0、LA0的Build Settings->Mach-O Type->static library。
    运行工程(注:运行前需要清空products文件夹下之前编译好的framework、.app,保证环境clear),console:

    2018-03-01 16:06:31.850360+0800 MainProj[24755:2092911] AObject: 0x6000000123d0
    

    单例对象[AObject shareInstance]只有一个内存地址。单例模式没有问题。

    2. 设置LA0为dynamic library,其他framework都是static library

    2018-03-01 16:08:29.750429+0800 MainProj[24800:2095054] AObject: 0x600000012a80
    

    单例模式没有问题

    3. 设置LB0为dynamic library,其他framework都是static library

    objc[50214]: Class AObject is implemented in both /Users/hotacool/Library/Developer/Xcode/DerivedData/Test-fsrnousjgrbqxngqgmulsskbrcln/Build/Products/Debug-iphonesimulator/LB0.framework/LB0 (0x10daf6418) and /Users/hotacool/Library/Developer/CoreSimulator/Devices/E925C3DF-D46D-4DCF-9936-9239176C4189/data/Containers/Bundle/Application/BA8871EE-BFBA-4F94-9A05-EE56FF8B5758/MainProj.app/MainProj (0x10d8112b0). One of the two will be used. Which one is undefined.
    (void *) $0 = 0x00006040001ad580
    2018-03-06 09:39:58.536791+0800 MainProj[50214:3929509] AObject: 0x6040002033f0
    2018-03-06 09:39:58.537293+0800 MainProj[50214:3929509] AObject: 0x6000000197e0
    

    单例对象[AObject shareInstance]出现两个不同的内存地址。根据console提示信息,AObject类在LB0.framework和.app中都有实现。

    4. 设置LC0为dynamic library,其他framework都是static library

    objc[50340]: Class AObject is implemented in both /Users/hotacool/Library/Developer/Xcode/DerivedData/Test-fsrnousjgrbqxngqgmulsskbrcln/Build/Products/Debug-iphonesimulator/LC0.framework/LC0 (0x106ed5590) and /Users/hotacool/Library/Developer/CoreSimulator/Devices/E925C3DF-D46D-4DCF-9936-9239176C4189/data/Containers/Bundle/Application/8C4343C0-DF3F-442B-9CF2-3898EC678F39/MainProj.app/MainProj (0x106bef2b0). One of the two will be used. Which one is undefined.
    objc[50340]: Class BObject is implemented in both /Users/hotacool/Library/Developer/Xcode/DerivedData/Test-fsrnousjgrbqxngqgmulsskbrcln/Build/Products/Debug-iphonesimulator/LC0.framework/LC0 (0x106ed55e0) and /Users/hotacool/Library/Developer/CoreSimulator/Devices/E925C3DF-D46D-4DCF-9936-9239176C4189/data/Containers/Bundle/Application/8C4343C0-DF3F-442B-9CF2-3898EC678F39/MainProj.app/MainProj (0x106bef300). One of the two will be used. Which one is undefined.
    (void *) $0 = 0x00006000001bdf80
    2018-03-06 09:47:11.090783+0800 MainProj[50340:3937289] AObject: 0x604000011dd0
    2018-03-06 09:47:11.091422+0800 MainProj[50340:3937289] AObject: 0x604000011d70
    

    同样出现单例对象多个地址问题。并且根据提示信息,AObject 、BObject都出现重复实现。

    5. 所有framework都是dynamic library

    2018-03-06 09:55:09.844002+0800 MainProj[50456:3944829] AObject: 0x604000015eb0
    

    单例模式没有问题

    推测结论

    通过上述测试,只有当dynamic library依赖static library,上层同时添加dynamic library和static library到link binary library中时,会发生单例对象多个地址,如上面demo中static library中对象在.app和dynamic library多个实现的问题。

    原因推测是:编译dynamic library时,会将依赖static library编译为二进制集成到dynamic library中,同时.app也会将static library编译到二进制中,导致有多个二进制实现,单例对象的调用地址会在编译时在二进制中写好,导致调用时有多个对象地址。

    避免上述问题,一方面是梳理清楚依赖关系,如demo中,有很多多余的依赖,例如LC0的link binary libraries中只需加入LB0即可,无需LA0,通过删除多余的依赖,也可以保证不出现单例无效的问题。另一方面,在项目架构设计中,尽量避免dynamic和static相互依赖的情况,在物理层面上完全杜绝。

    相关文章

      网友评论

        本文标题:iOS动态库导致的单例无效问题

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