天天看点

Objective-C中的单例模式(singleton)

以我自己做过的项目来看,单例模式(singleton)在所有设计模式中应该是用得频繁的。其中心思想和作用也是在设计模式中最简单的,在接触这这个模式后没有程序员不会喜欢它。好了,废话不多说,立即看一下在Objective-C语言里是怎么实现单例模式。在Objective-C中实现单例和其它语言实现的思想基本一致。

苹果官方实现方法

在苹果的官方文档库里可以找到一篇关于如何创建单例的一些建议(Creating a Singleton Instance)(但时代有点久远了,里面的内容也有点过时,这篇文章也就被苹果遗弃了)

先给里面给的建议写出来:

在Objective-C 中要实现一个单例类,至少需要做以下四个步骤:

1、为单例对象实现一个静态实例,并初始化,然后设置成nil;

2、实现一个实例构造方法检查上面声明的静态实例是否为nil,如果是则新建并返回一个本类的实例;

3、重写allocWithZone方法,用来保证其他人直接使用alloc和init试图获得一个新实力的时候不产生一个新实例;

4、适当实现allocWithZone,copyWithZone,release和autorelease。

注: 单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。

先看看与官方例子差不多的代码:

/* Singleton.h */
#import <Foundation/Foundation.h>

@interface Singleton : NSObject
+ (Singleton *)sharedInstance;
@end

/* Singleton.m */
#import "Singleton.h"
static Singleton *instance = nil;

@implementation Singleton

+ (Singleton *)sharedInstance {
    if (!instance) {
        instance = [[super allocWithZone:NULL] init];
    }
    return instance;
}

+ (id)allocWithZone:(NSZone *)zone {
    return [self sharedInstance];
}

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

- (id)init {
    if (instance) {
        return instance;
    }
    self = [super init];
    return self;
}

- (id)retain {
    return self;
}

- (oneway void)release {
    // Do nothing
}

- (id)autorelease {
    return self;
}

- (NSUInteger)retainCount {
    return NSUIntegerMax;
}

@end
           

这段代码看起来很多,它是建立在非ARC(Automatic Reference Counting)环境下,而且并不是线程安全的。

我们先实现一下ARC环境下的单例模式

1.首先来个简单的,看如下代码:

+ (instancetype)sharedInstance {
    static dispatch_once_t  onceToken;
    static Singleton * sharedInstance = nil;

    dispatch_once(&onceToken, ^{
        sharedInstance = [[Singleton alloc] init];
    });

    return sharedInstance;
}
           

说明:

使用了Objective-C中的GCD(Grand Central Dispatch)解决方案,其中利用了dispatch_once的特性。

但是,这种方法还有个缺陷,因为在其它地方可通过alloc和copy任意创建对象,所以把这两个方法也重写一下,而且原来实现的方式也要改一下,这样就有了第二个版本。

2.改良后的版本

+ (instancetype)sharedInstance {
    static dispatch_once_t  onceToken;
    static Singleton * sharedInstance = nil;

    dispatch_once(&onceToken, ^{
        sharedInstance = [[super allocWithZone:NULL] init];
    });

    return sharedInstance;
}

+ (id)allocWithZone:(NSZone *)zone { 
    return [self sharedInstance];
}

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

说明:

重写了allocWithZone和copyWithZone后,调用alloc和copy消息后也不会重复创建新的对象了。如果还要防止调用mutableCopy的话,还要重写mutableCopyWithZone函数,不过如果默认不实现的话,调用mutableCopy是会有异常的,程序会立即崩溃掉。

但是这个实现也有问题,就是init函数也会有可能被调用多次。所以有了以下版本。

3.再改良后的版本

@implementation Singleton

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

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

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

- (instancetype)init {
    return self;
}

- (instancetype)initPrivate {
    if (self = [super init]) {
        // do the stuff
    }
    return self;
}

@end
           

根据非ARC版我们可以很容易实现一个ARC版

@implementation Singleton

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

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

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

- (instancetype)init {
    return self;
}

- (instancetype)retain {
    return self;
}

- (oneway void)release {
    // Do nothing
}

- (instancetype)autorelease {
    return self;
}
- (NSUInteger)retainCount {
    return NSUIntegerMax;
}


- (instancetype)initPrivate {
    if (self = [super init]) {
        // do the stuff
    }
    return self;
}

@end
           

然后我们可以用宏封装一下

用ARC实现的单例和没用ARC的单例有一些不一样的地方,要注意。所以如果用到宏的地方可以用以下宏来区别ARC环境下和非ARC环境下的宏:

#if __has_feature(objc_arc)

// ARC

#else

// MRC

#endif
           

用宏实现:

#define SINGLETON_IN_HEADER(className) +(instancetype)sharedInstance;

// Begin define SINGLETON_IN_IMPLEMENTATION
#if __has_feature(objc_arc)

// ARC version
#define SINGLETON_IN_IMPLEMENTATION(className) \
+ (instancetype)sharedInstance {\
    static dispatch_once_t onceToken;\
    static className * sharedInstance = nil;\
    \
    dispatch_once(&onceToken, ^{\
        sharedInstance = [[super allocWithZone:NULL] initPrivate];\
    });\
    \
    return sharedInstance;\
}\
\
+ (instancetype)allocWithZone:(NSZone *)zone {\
    return [self sharedInstance];\
}\
\
- (instancetype)copyWithZone:(NSZone *)zone {\
    return self;\
}\
\
- (instancetype)init {\
    return self;\
}


#else

// MRC version
#define SINGLETON_IN_IMPLEMENTATION(className) \
+ (instancetype)sharedInstance {\
    static dispatch_once_t onceToken;\
    static className * sharedInstance = nil;\
    dispatch_once(&onceToken, ^{\
        sharedInstance = [[super allocWithZone:NULL] initPrivate];\
    });\
    return sharedInstance;\
}\
\
+ (instancetype)allocWithZone:(NSZone *)zone {\
    return [self sharedInstance];\
}\
\
- (instancetype)copyWithZone:(NSZone *)zone {\
    return self;\
}\
\
- (instancetype)init {\
    return self;\
}\
\
- (instancetype)retain {\
    return self;\
}\
\
- (oneway void)release {\
}\
\
- (instancetype)autorelease {\
    return self;\
}\
\
- (NSUInteger)retainCount {\
    return NSUIntegerMax;\
}

#endif  // End define SINGLETON_IN_IMPLEMENTATION