一个由于C++初始化失败导致Realm初始化失败的Crash

iOS中C++静态全局变量的动态初始化时序

日志

+ (void)load
{
    [RLMRealm defaultRealm];
}
// Global realm state
static std::mutex& s_realmCacheMutex = *new std::mutex();

......

RLMRealm *RLMGetThreadLocalCachedRealmForPath(std::string const& path) {
    std::lock_guard<std::mutex> lock(s_realmCacheMutex);  // <--- Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
    return [s_realmsPerPath[path] objectForKey:(__bridge id)pthread_self()];
}

最终的报错位置在这里,可以看出s_realmCacheMutex是一个空对象,能看出是初始化出了问题。

可是为什么没有初始化成功呢?

C++的静态初始化与动态初始化

  1. static initialization: 静态初始化指的是用常量来对变量进行初始化,主要包括 zero initialization 和 const initialization,静态初始化在程序加载的过程中完成,对简单类型(内建类型,POD等)来说,从具体实现上看,zero initialization 的变量会被保存在 bss 段,const initialization 的变量则放在 data 段内,程序加载即可完成初始化,这和 c 语言里的全局变量初始化基本是一致的。
  2. dynamic initialization:动态初始化主要是指需要经过函数调用才能完成的初始化,比如说:int a = foo(),或者是复杂类型(类)的初始化(需要调用构造函数)等。这些变量的初始化会在 main 函数执行前由运行时调用相应的代码从而得以进行(函数内的 static 变量除外)。

需要明确的是:静态初始化执行先于动态初始化! 只有当所有静态初始化执行完毕,动态初始化才会执行。

Objective-C的静态库与动态库

静态库与动态库的区别在于:

一、静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。

二、动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。

如果你在CocoaPods中使用了use_frameworks!,则Pods会将依赖库都打包成动态库

加载时序

静态库 + (void)load -> C++静态全局变量的动态初始化 -> 动态库 + (void)load -> main()

原因

不少人应该都猜出原因了吧,正是由于我在静态库的Load中初始化了Realm,导致Realm在获取互斥锁的时候出现了初始化失败。

解决方案

  1. 在+ (void)load中添加一个GCD异步任务,在异步中初始化Realm,此时GCD会将任务推迟到main之后执行。
  2. 改用动态库。
  3. 将Realm初始化延后。

参考资料

c++ 全局变量初始化的一点总结