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

这里的单例无效,指的是虽然用了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相互依赖的情况,在物理层面上完全杜绝。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 静态库与动态库的区别 首先来看什么是库,库(Library)说白了就是一段编译好的二进制代码,加上头文件就可以供别...
    吃瓜群众呀阅读 12,043评论 3 42
  • 仅以方便自己查阅记录前言1.静态库和动态库有什么异同?静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗...
    190CM阅读 4,261评论 0 4
  • 简介 cocoapods在1.4.0推出了static framework,先扒扒历史原因. dymanic fr...
    sea_biscute阅读 37,882评论 8 83
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,859评论 18 139
  • 大概是几年前看了一部电影深受感触,写下“做自己,潇洒点”,成了我不变的QQ签名,这也是我一直向往着的生活态度。 置...
    向荣洵阅读 1,147评论 7 14