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