返回

dispatch_once 的隐形陷阱与注意事项

IOS

单例模式在 Objective-C 开发中十分常见,而利用 dispatch_once 实现单例更是简便易行。然而,近期在为维护中的老项目增添新功能时,我却无意中掉入了 dispatch_once 的陷阱。

具体来说,我们的问题如下:公司测试表明,在安装应用一段时间后,重新启动应用会导致不可预期的崩溃。

经过排查,我们发现问题出在利用 dispatch_once 创建单例的代码上。在 init 方法中,我们使用了如下代码:

dispatch_once(&token, ^{
  // 初始化单例实例
});

这会导致在应用启动时创建单例,并将 token 标记为已执行。然而,当应用从后台重新启动时,dispatch_oncetoken 仍然标记为已执行,但此时单例实例已经被释放。

结果就是,在重新启动时,代码会尝试再次初始化单例,这会导致崩溃。为了解决这个问题,我们必须考虑应用重新启动的情况,并在 init 方法中添加以下代码:

if (dispatch_once(&token)) {
  // 单例实例已存在
  return;
}

这样,在应用重新启动时,当 dispatch_oncetoken 仍然标记为已执行时,代码会直接返回,避免再次创建单例。

除了这个陷阱,在使用 dispatch_once 时还有一些其他注意事项:

  • 避免在多线程环境中使用: dispatch_once 并不是线程安全的,因此在多线程环境中使用它可能会导致数据竞争和崩溃。
  • 正确处理 dispatch_once 宏的参数: dispatch_once 宏接受两个参数:dispatch_once_t 类型的 token 和一个包含初始化代码的块。确保 token 是唯一的,并且初始化块只执行一次。
  • 考虑单例的销毁: dispatch_once 创建的单例在整个应用生命周期中都是存在的。如果单例持有大量资源或状态,则需要在应用退出时释放这些资源。

总而言之,dispatch_once 是实现单例模式的便捷方法,但需要谨慎使用以避免潜在的陷阱。通过了解其限制并采取适当的预防措施,我们可以确保 dispatch_once 为我们的 Objective-C 代码提供稳定可靠的单例实现。