首页 > 代码库 > 单例模式(Singletond)

单例模式(Singletond)

定义

单例模式是为了使得整个程序的单例类只有一个对象,整个程序共同使用一个该类型的对象。单例模式确保某一个类只有一个实例,这个类称为单例类。其定义如下:

技术分享

单例模式其实很容易理解的,我只要一个对象,至始至终都是这一个对象。对于C++实现单例模式是比较简单的,把构造函数(包括拷贝构造函数)、析构函数的访问权限设置为private,然后提供获取单例对象的接口即可(一般不提供销毁对象的接口,防止不小心销毁了又创建)。Objective-C实现单例模式的思想大致也是相同的,但是Objective-C实现起来却比C++难的多。这主要是Objective-C的类不是单纯的类,每个类几乎都需要继承自NSObject,而NSObject很多人不是很了解,因此对于Objective-C由什么方式去创建对象、销毁对象、复制对象不是太清楚,因此未能保证单个创建对象,防止销毁对象的情况发生。Objective-C不像C++提供了构造函数,因此不能通过屏蔽构造函数来防止对象的创建,相反,Objective-C的创建对象的接口对外都是可用的,所以我们需要的不是屏蔽创建对象的方法,而是修改定制这些创建对象的方法,使得这些创建对象的方法返回的都是同一个对象。


单例模式的实现

如果你没有看过Objective-C中实现单例模式的例子,你大概会这样实现单例模式:

#import "WrongSingleton.h"

static WrongSingleton* sharedInstances = nil;

@implementation WrongSingleton
+ (id) shareInstances
{
    if(sharedInstances == nil)
    {
        sharedInstances = [[WrongSingleton alloc] init];
    }
    return sharedInstances;
}

- (id) init
{
    if (self == nil)
    {
        self = [super init];
        
        // 其他的初始化
    }
    return self;
}
@end
其中头文件声明了接口+ (id) shareInstances,但是这种实现却实实在在是有问题的,最直接的证据是下面的代码创建了不同的对象。
/**
 *  单例模式不支持copy和mutableCopy方法
 */

/**
 *  错误的单例模式测试用例
 */
WrongSingleton* w1 = [WrongSingleton shareInstances];
WrongSingleton* w2 = [[WrongSingleton alloc] init];
NSLog(@"1-%@  2-%@", w1, w2);
/**
 *  输出结果是
 *  1-<WrongSingleton: 0x100111130>  2-<WrongSingleton: 0x1001111c0>
 *  两个不同的对象,所以WrongSingleton的不符合单例模式的
 */
这时因为两行代码都使用了系统定义的alloc方法,所以能够创建两个不同的对象,那么现在如何去改正呢?


Objective-C关于对象修改的主要接口方法列表如下所示:

/** 
 *  @author Arbboter, 15-01-15 22:01:57 
 *  创建一个当前类的实例变量所需的内存 
 *  @param zone 已经忽略 
 *  @return 分配的对象 
 */  
+ (instancetype)allocWithZone:(struct _NSZone *)zone;  
  
/** 
 *  @author Arbboter, 15-01-15 23:01:06 
 *  由于历史问题,该方法会调用allocWithZone方法创建对象内存 
 *  @return 分配了内存的对象 
 */  
+ (instancetype)alloc;  
  
/** 
 *  初始化对象的isa为描述类的数据结构,其他的实例变量为0 
 */  
- (instancetype)init;  
  
/** 
 *  @author Arbboter, 15-01-15 23:01:57 
 *  相当于 [[类名 alloc] init] 
 *  @return 初始化好的对象 
 */  
+ (instancetype)new;  
  
/** 
 *  @author Arbboter, 15-01-15 23:01:49 
 *  由NSCopying协议定义的copyWithZone:方法返回的对象 
 *  @return copy出的对象 
 */  
- (id)copy;  
  
/** 
 *  @author Arbboter, 15-01-15 23:01:49 
 *  由NSMutableCopying协议定义的mutableCopyWithZone:方法返回的对象 
 *  @return copy出的对象 
 */  
- (id)mutableCopy;  
  
/** copy协议 */  
+ (id)copyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;  
+ (id)mutableCopyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;  
  
/** 释放销毁对象 */  
- (void)dealloc;  
当然,上述的接口主要是NSObject的方法,还有其他的比如retain之类的没有列举。上述的接口中,需要注意的是+ (instancetype)allocWithZone:(struct _NSZone *)zone;接口,这是因为这是Objective-C编程所能看到的分配对象内存的源头,alloc也是会调用该接口来分配内存创建对象的。因此我们一定是要对这个接口动手术的,保证这个接口返回的对象都是同一个对象。此外,有一点需要注意到,代码[super init]返回的都是同一个对象的。了解了Objective-C对象的修改对象接口,我们差不多就可以开始实现自己的单例类了。为什么是差不多了,这是因为我们还得知道我们的单例类是面向ARC代码还是非ARC代码。


ARC的单例模式设计

ARC的单例模式相对简单些。其单例类的实现代码示例如下所示:

#import "ARCSingleton.h"

static ARCSingleton* sharedInstances = nil;

@implementation ARCSingleton
+ (id) shareInstances
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstances = [[super allocWithZone:nil] init];
        // 其他代码
    });
    return sharedInstances;
}

// allocWithZone是创建对象分配内存的源头,alloc也会调用该方法
+ (id)allocWithZone:(struct _NSZone *)zone
{
    return [[self class] shareInstances];
}
@end
上述代码的+ (id) shareInstances接口中使用GCD来确保线程安全问题,保证只会调用一次创建对象的代码。其测试用例如下所示:
/**
 *  ARC版本的单例模式测试
 */

ARCSingleton* a2 = [[ARCSingleton alloc] init];
ARCSingleton* a1 = [ARCSingleton shareInstances];
NSLog(@"1-%@  2-%@", a1, a2);
/**
 *  输出结果是
 *  1-<ARCSingleton: 0x100111c30>  2-<ARCSingleton: 0x100111c30>
 *  地址相同,是相同的对象
 */
重点在于自定义的+ (instancetype)allocWithZone:(struct _NSZone *)zone;接口,防止外部直接或者间接调用该接口分配内存创建对象,而且这里的+ (id) shareInstances接口使用GCD+父类的allocWithZone:接口分配内存创建对象。

非ARC模式的单例模式设计

非ARC模式的,APPLE有个例子,其实现相对而言复杂一点,需要考虑的因素比较多,下面的代码主要根据APPLE官方提供的代码修改出来的,知识增加了多线程安全部分的控制,其大致形式如下所示:

#import "Singleton.h"

static Singleton* sharedInstances = nil;

// 非ARC版本的单例模式
@implementation Singleton
+ (id) shareInstances
{
    // 线程同步,Apple官方的例子没有加synchronized
    @synchronized(self)
    {
        if(sharedInstances == nil)
        {
            sharedInstances = [[super allocWithZone:nil] init];
        }
    }
    return sharedInstances;
}

// allocWithZone是创建对象分配内存的源头,alloc也会调用该方法
+ (id)allocWithZone:(NSZone *)zone
{
    return [[self shareInstances] retain];
}

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

- (NSUInteger)retainCount
{
    //denotes an object that cannot be released
    return NSUIntegerMax;
}

- (oneway void)release
{
    // never release
}

- (id)autorelease
{
    return self;
}

- (id)init
{
    /**
     *  在同一个类里面调用[super init]返回的对象都是相同的
     */
    if (self = [super init])
    {
        
    }
    return self;
}
- (void)dealloc
{
    [super dealloc];
}

@end
测试用例为:
/**
 *  非ARC版本的单例模式测试
 */
Singleton* s2 = [[Singleton alloc] init];
Singleton* s1 = [Singleton shareInstances];

NSLog(@"1-%@  2-%@", s1, s2);
/**
 *  输出结果是
 *  1-<Singleton: 0x100106f60>  2-<Singleton: 0x100106f60>
 *  各个对象的地址都是相同的
 */


总结

单例模式是为了整个程序共享使用同一个对象,因此该单例类不提供copy之类的方法。

参考

  • APPLE官方的单例模式
  • Objective-C对象模型分析
  • 破船之家的单例模式实现


单例模式(Singletond)