设计模式

设计模式第二篇、单例设计模式

2018-09-13  本文已影响34人  意一ineyee
OC编程之道:设计模式

目录

1、什么是单例设计模式
2、单例设计模式的简单实现
3、单例设计模式面临的两个问题及其完整实现
4、单例设计模式的应用场景
1、什么是单例设计模式

单例设计模式是指在App的整个生命周期中,某个类的实例只可能被创建一个,同时该类需要向外界提供一个sharedInstance方法来创建这个实例。

所以单例设计模式有三个设计思想:一不死性,即单例对象的生命周期和App的生命周期一样,只有在App杀死的时候单例对象才会被销毁;二唯一性,即在App的整个生命周期中单例对象只可能被创建一个;三、我们需要向外界提供一个sharedInstance方法来创建这个实例

2、单例设计模式的简单实现

要实现单例设计模式,我们只需要抓住它的三个设计思想就可以了。

+ (instancetype)sharedSingleton;

所以写代码如下:

ProjectSingleton *singleton = nil;
+ (instancetype)sharedSingleton {
    
    if (singleton == nil) {
        
        singleton = [[ProjectSingleton alloc] init];
    }
    
    return singleton;
}

但是使用全局变量来指向单例对象有一个很明显弱点是:外界使用extern关键字,可以很随便的就能修改掉这个单例对象,类型甚至都可以不是单例类的类型,那这不炸了嘛。如下:

extern ProjectSingleton *singleton;

singleton = @"222";

你看单例对象本来是ProjectSingleton类的实例,现在外界直接把它改成了字符串,都没报错。所以我们要把全局变量改成静态全局变量,这样这个变量就只能在单例类内部访问了,外界访问会直接报错。

static ProjectSingleton *singleton = nil;
+ (instancetype)sharedSingleton {
    
    if (singleton == nil) {
        
        singleton = [[ProjectSingleton alloc] init];
    }
    
    return singleton;
}

这样看起来好像没错了,单例设计模式的三个设计思想就算实现了。

3、单例设计模式面临的两个问题及其完整实现

上一小节,我们实现了单例设计模式的三个设计思想,但其实这只能算是简单的实现,因为这样实现单例会面临两个重要的问题:多线程问题多创建入口问题,这两个问题都会导致单例的唯一性被破坏,我们需要慎重解决。

(1)多线程问题

如果按照上面的方式实现单例,那当我们的代码中有多个线程同时访问单例的创建方法+ (instancetype)sharedSingleton;时,我们是不能保证单例的唯一性的。比如说线程一已经访问到了singleton = [[ProjectSingleton alloc] init];创建单例对象这一步,但是还没有创建出来,此时线程二恰好正在访问if (singleton == nil)这句代码,那么线程二得到的结果将是YES,也会走创建单例对象的代码,这样我们就会创建两个单例对象,违背了单例设计模式唯一性的设计思想。

因此,我们需要在懒加载这一步加锁,通过懒加载+锁的组合来保证多线程场景下单例创建的唯一性。当然了加锁的方式也有好几种,如@synchronizedNSLockGCD信号semaphore,由于@synchronized的性能更高,所以我们用它。如下:

static ProjectSingleton *singleton = nil;
+ (instancetype)sharedSingleton {
    
    @synchronized(self) {
        
        if (singleton == nil) {
            
            singleton = [[ProjectSingleton alloc] init];
        }
    }
    
    return singleton;
}

但其实我们在这里并没有采用懒加载+锁的方式来保证单例在多线程场景下的唯一性,而是采用GCD的dispatch_once,一方面它是线程安全的(可以替代锁的功能),另一方面它可以保证某段代码在整个App生命周期中只执行一次(可以替代懒加载)。这么做主要是因为用dispatch_once来保证线程安全要比加锁的性能高几十倍(详见此文章),它的性能之所以如此高,是因为它的内部并不是靠pthread等锁来实现的,而是通过大量的原子操作来实现,这些原子操作直接用的是锁的汇编指令,靠CPU指令来支持。这样我们就dispatch_once代替了原来懒加载的设计方案来保证单例的唯一性,也同时保证了多线程场景下单例的唯一性。如下:

static ProjectSingleton *singleton = nil;
+ (instancetype)sharedSingleton {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        singleton = [[ProjectSingleton alloc] init];
    });

    return singleton;
}
(2)多创建入口问题

好了,通过GCD的dispatch_once我们解决了单例创建的唯一性问题,但真得解决完了吗?没有啊,因为别人完全可以不用我们提供的+ (instancetype)sharedSingleton;方法来创建单例,而是用系统提供的alloc、new、copy、mutableCopy来创建,这样的话我们在外界使用单例时是不能完全确保单例的唯一性的。

因此,我们需要把系统的这些创建入口给封死。我们知道调用alloc、new的时候会调用+ (instancetype)allocWithZone:(struct _NSZone *)zone;方法,调用copy和mutableCopy的时候会调用- (instancetype)copyWithZone:(NSZone *)zone;- (instancetype)mutableCopyWithZone:(NSZone *)zone;方法,所以我们可以在单例类里重写这些方法,让它们调用我们的+ (instancetype)sharedSingleton;方法来返回单例对象就可以了。如下:

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

- (instancetype)copyWithZone:(NSZone *)zone {
    
    return [ProjectSingleton sharedSingleton];
}

- (instancetype)mutableCopyWithZone:(NSZone *)zone {
    
    return [ProjectSingleton sharedSingleton];
}

哇,终于可以松口气了,其实解决了这两个问题之后我们才算完成了单例设计模式的实现。所以一个要实现完整的单例,代码应该如下:

@implementation ProjectSingleton

static ProjectSingleton *singleton = nil;
+ (instancetype)sharedSingleton {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        singleton = [[ProjectSingleton alloc] init];
    });

    return singleton;
}

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

- (instancetype)copyWithZone:(NSZone *)zone {
    
    return [ProjectSingleton sharedSingleton];
}

- (instancetype)mutableCopyWithZone:(NSZone *)zone {
    
    return [ProjectSingleton sharedSingleton];
}

- (instancetype)init {
    
    self = [super init];
    if (self != nil) {
        
        // 一些属性的设置
    }
    
    return self;
}

@end
4、单例设计模式的应用场景

单例设计模式的应用场景也是由它的设计思想所决定的,单例不是具有唯一性和不死性嘛,所以我们可以得到它的应用场景:

但是我们也要慎重的使用单例设计模式,不能想什么时候用就什么时候用,特别是不要大材小用,要在真得有必要的时候才用,因为单例对象的生命周期很长,它内部的变量一旦强引用了外部的大对象,这些对象就只能等到App结束时才会被销毁,很容易造成内存问题。

上一篇下一篇

猜你喜欢

热点阅读