iOS单例模式

单例模式大概是设计模式种较简单的一种设计模式。但在实际的开发过程中仍然存在一些坑。所以本文总结了下iOS中的单例模式。

什么是单例模式?

  • ensures a class only has one instance
  • provides a global point of access to it
  • 确保一个类永远只有一个实例
  • 提供一个全局的访问入口访问这个实例

苹果官方文档的一副图描述了请求普通类和单例的区别:


请求普通类与单例的区别

如何实现基本的单例模式?

Singleton *sharedInstance = nil;

+ (instancetype)sharedIntance {
    if (sharedInstance == nil) {
        sharedInstance = [[Singleton alloc] init];
    }
    
    return sharedInstance;
}

全局的变量sharedInstance有个缺点,可以被外部随意修改,为了隔离外部修改,可以设置成局部静态变量。

+ (instancetype)sharedInstance {
    static Singleton *sharedInstance = nil;
    if (sharedInstance == nil) {
        sharedInstance = [[Singleton alloc] init];
    }
    
    return sharedInstance;
}

单例的核心思想就算实现了。

多线程如何处理?

上述例子虽然实现了单例的核心思想,但依然存在问题。在多线程情况下即多个线程同时访问sharedInstance工厂方法,并不能保证只创建一个实例对象。

那么,如何保证在多线程的下依旧能够只创建一个实例对象呢?iOS下我们可以使用NSLock@synchronized等多种线程同步技术。

+ (instancetype)sharedInstance {
    static Singleton *sharedInstance = nil;
    @synchronized (self) {
        if (sharedInstance == nil) {
            sharedInstance = [[Singleton alloc] init];
        }
    }

    return sharedInstance;
}

@synchronized虽然保证了在多线程下调用sharedInstance工厂方法只会创建一个实例对象,但是@synchronized的性能较差。OC内部提供了一种更加高效的方式,那就是dispatch_once@synchronized性能相较于dispatch_once要差几倍,甚至几十倍。关于二者的性能对比,请参考这里

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

    return sharedInstance;
}

Objective-C中实现单例存在的坑

上述实现单例的方式看起来很完美了。虽然我们提供了一个方便的工厂方法返回单例,但是用户依然能够调用alloc方法创建对象。这样外部使用的时候依旧能够创建多个实例。解决上述问题有两种方案:

1. 技术上实现无论怎么调用都返回同一个单例对象

To create a singleton as the sole allowable instance of a class in the current process. This code does the following:

  • It declares a static instance of your singleton object and initializes it to nil.
  • In your class factory method for the class (named something like “sharedInstance” or “sharedManager”), it generates an instance of the class but only if the static instance is nil.
  • It overrides the allocWithZone: method to ensure that another instance is not allocated if someone tries to allocate and initialize an instance of your class directly instead of using the class factory method. Instead, it just returns the shared object.
  • It implements the base protocol methods copyWithZone:, release, retain, retainCount, and autorelease to do the appropriate things to ensure singleton status. (The last four of these methods apply to memory-managed code, not to garbage-collected code.)
+ (instancetype)sharedInstance {
    static Singleton *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[super allocWithZone:NULL] init];
    });

    return sharedInstance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [self sharedInstance];
}

- (id)copy {
    return [Singleton sharedInstance];
}

- (id)mutableCopy {
    return [Singleton sharedInstance];
}

如下图所示,不管使用何种方式创建对象都返回相同的实例对象。


实例变量的地址

2.利用编译器特性给用户提示,但不强制约束

利用编译器特性直接告诉外部newalloccopymutableCopy方法不能直接调用,否则编译不通过。

+ (instancetype)sharedInstance;

+ (instancetype)new OBJC_UNAVAILABLE("use sharedInstance instead.");
+ (instancetype)alloc OBJC_UNAVAILABLE("use sharedInstance instead.");
- (id)copy OBJC_UNAVAILABLE("use sharedInstance instead.");
- (id)mutableCopy OBJC_UNAVAILABLE("use sharedInstance instead.");

若直接调用alloc等方法创建对象,编译器则会给出错误提示:

编译器错误提示

单例模式潜在的问题

  • 内存问题
    单例模式实际上是延长了对象的生命周期,那么就存在内存的问题,因为单例对象在程序的整个生命周期里都存在,直到程序退出才会释放。

参考文献:
Singleton
Creating a Singleton Instance

你可能感兴趣的:(iOS单例模式)