iOS单例对象:延迟加载和线程安全的最佳实践

1,804 阅读4分钟

单例模式是iOS开发中常见的设计模式之一,作为一种经典的设计模式,它能够保证在整个应用程序中只会创建一个对象实例。在iOS开发中,我们常常需要在多个地方使用同一个对象,这时候通过单例模式可以方便地实现对象的共享。

苹果的框架自带的单例模式

  1. FileManager.default:这是一个全局的单例对象,用于提供文件管理功能
  2. UserDefaults.standard:这是一个全局的单例对象,用于提供用户默认设置的读写功能
  3. UIApplication.shared:这是一个全局的单例对象,用于提供应用程序级别的配置和交互功能
  4. NotificationCenter.default:这是一个全局的单例对象,用于提供通知机制的处理和分发功能

Swift单例

Static Property + Private Initializer(推荐)

Swift中,使用static let来定义单例属性是线程安全的,因为Swift语言本身支持线程安全的延迟初始化(lazy initialization)。即只有在第一次访问这个属性时才会创建它,且只会创建一次,保证了单例的唯一性

另外可以注意到,由于构造器使用了private关键字,即不允许在类的外部创建实例,保证单例的原子性

class Singleton {
     
    static let shared = Singleton(with: "param")
    
    private init(with param: String) {}
}

个人觉得通过该方式实现单例,代码可读性强,且直观,故比较推荐

Global Variables

最直接简洁的方式:直接声明一个全局实例变量

let shared = Singleton(with: "param")
class Singleton {
    init(with param: String) {}
}

Swift中,全局变量使用延迟加载(lazy initialization)的方式进行初始化。这意味着全局变量的初始化器只会在第一次访问该变量时才被调用。

Swift的这种实现方式有一个额外的好处,就是使用了dispatch_once函数来保证全局变量仅被初始化一次。这一点非常重要,因为仅需要初始化单例对象一次。在以前的Objective-C中,我们通常使用@synchronized关键字来保证线程安全并保证只有一个实例被创建。但是,Swift中使用dispatch_once函数可以更好地保证全局变量的唯一性和线程安全性。

OC单例

GCD是iOS中的一种多线程编程技术,也是一种常见的OC单例实现方式。以下是一个使用GCD实现的单例类示例:

@interface Singleton : NSObject

+ (instancetype)sharedInstance;

@end

@implementation Singleton

+ (instancetype)sharedInstance {
    static Singleton *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static dispatch_once_t onceToken;
    static Singleton *sharedInstance = nil;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super allocWithZone:zone];
    });
    return sharedInstance;
}

- (instancetype)init {
    static Singleton *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super init];
    });
    return sharedInstance;
}

- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    return self;
}

@end

在上述示例中,我们使用了dispatch_once来确保在整个应用程序生命周期中只会创建一个对象实例。同时,我们重写了allocWithZoneinitcopyWithZonemutableCopyWithZone方法,以确保在对象被复制时依然只会创建一个对象实例。

优点

  1. 提供了一种方便的方式来访问和共享唯一的对象实例,避免了重复创建相同的对象
  2. 由于单例只有一个实例,因此可以减少内存消耗和资源占用,提高应用程序的性能
  3. 在多线程环境下,单例可以提供一种线程安全的方式来访问和修改共享数据,避免竞态条件和死锁等问题

缺点

  1. 单例模式可能会导致代码耦合度较高,因为单例类被广泛使用,它的修改可能会影响整个应用程序
  2. 单例在应用程序中的生命周期非常长,往往从应用程序启动到关闭都存在,因此可能会导致内存泄漏、资源占用等问题
  3. 单例状态的混乱,一般需要对单例的状态进行监控,这样会使程序过于复杂,同时也产生很多无谓的监听
  4. 单例类的创建和初始化往往比较复杂,所以在设计和实现时需要考虑到其可扩展性和可维护性

总结

单例模式是iOS开发中常见的设计模式之一,它能够确保在整个应用程序中只会创建一个对象实例。在OC和Swift两种语言中,实现单例模式的方法各有不同。在OC中,我们常用懒加载和GCD来实现单例,而在Swift中,推荐使用全局常量或static变量来实现单例。无论使用哪种方法,单例模式都能够方便地实现对象的共享,提高应用程序的性能。

参考文献

What Is a Singleton and How To Create One In Swift