【设计模式】五、单例模式(独一无二的对象)

一、概述:

有一些对象我们只需要一个,比方说:线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表对象、日志对象、充当打印机、显卡等设备的驱动程序的对象。事实上这些对象只需要一个实例,如果制造出多个实例就会导致很多问题发生。(利用静态类变量、静态方法和适当的访问修饰符的确也可以做到只存在一个实例。)

苏格拉底诱导式回答:(参考《Head First 设计模式》)

如何创建一个对象? new MyObject()
万一另外一个对象想创Myobject会怎样?可以再次new MyObject吗? 是的,当然可以。
所以一旦有一个类,我们是否都能多次的实例化它? 如果是公开的类 就可以
如果不是的话,会怎样?

如果不是公开的类,只有同一个包内的类可以实例化它,但是仍可以实例化它很多次

可以这么做吗?

public Myclass
{
    private Myclass(){}
}

 

 我没想过,但是这是合法的定义,有一定的道理。
 怎么说呢?  我认为含有私有的构造器类不能呗实例化。
 又可以使用私有的构造器对象吗?  恩,我想Myclass内的代码是唯一能调用此构造器的代码,但是这又不太合乎常理。
 WHY?  因为只有Myclass的实例才能调用Myclass的构造器,但是因为没有其他类能够实例化Myclass,所以我们得不到这样的实例。

 嘿,我有个想法。

你认为如何?

public Myclass
{
  public static MyClass getInstance(){}
}

MyClass有一个静态方法,我们可以这样调用这个方法:

MyClas.getInstance();

 
 为何调用的时候用MyCLass类名,而不是对象名。  因为getInstance是类方法,是一个静态方法,你需要使用类名。

 有意思,假如把这些合在一起“是否”就可以初始化一个MyClass?

public MyClass
{
  private MyClass(){}
  public static Myclass getInstance()
   {
        return new MyClass();
   }
}

 

当然可以 
 好了,你能想出第二种实例化对象的方式吗?  MyClass.getInstance();
 你能够完成代码是MyClass只有一个实例被产生吗?  恩,大概可以吧。。。。

 

单例模式优点

  1. 正如前面说的,单例模式在内存中只有一个实例,减少了内存开支。特别是一个对象需要频繁的创建、销毁时,而创建与销毁的性能有无法优化,单例模式的优势就非常明显。
  2. 单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
  3. 单例模式可以避免对资源的多重占用。
  4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问。

单例模式缺点

  1. 单例模式一般没有接口,扩展很困难,除了修改代码基本上没有第二种途径实现。
  2. 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的。
  3. 单例模式与单一职责原则有冲突。

二、在IOS中的应用

单例模式在iOS开发中的使用还是蛮多的,许多FoundationCocoaUIKit中的类都实现了单例模式,比如应用程序本身UIApplication、文件操作类NSFileManager、消息中心NSNotificitonCenter等系统都已经给我们实现单例,我们只需要使用就好了。在iOS中使用单例模式要使用类方法,通过类方法返回该类的唯一对象。

在ios中的应用主要有以下三种方式

1、

static Singleton *instance = nil;

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

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

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

- (id)retain
{
    return self;
}

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

- (void)release
{
    //do nothing
}

- (id)autorelease
{
    return self;
}

可以看到这种方式,使用静态成员维持了一个永久存在的对象,而且覆盖了alloc方法(alloc方法会调用allocWithZone:方法),并且也覆盖了所有与引用技术有关的方法,这都使这个对象不会被销毁。这样看上去基本实现了我们需要的,但是写起来麻烦不说,还有很大的一个问题,那就是多线程问题,如果是在多线程中那么该种方法就不能保证只产生一个对象了。所以这种方式只是介绍一下,并不推荐使用。

2、引入头文件

程序员都是偷懒的,现在流行使用一个宏定义来搞定这许多的事,而且考虑的更加周全。

单例包含以下接口 

+ (MyClass*) sharedInstance; 
+ (void) purgeSharedInstance;

调用sharedInstance会创建并返回单例

调用purgeSharedInstance会销毁单例

手动调用alloc也可以保证是单例,你可以这样调用

[[MyClass alloc] initWithParam:firstParam secondParam:secondParam];

只是要保证在sharedInstance之前调用,因为只有一次创建机会。

下面是使用宏的写法“ 

MyClass.h: 
======================================== 
    #import "SynthesizeSingleton.h" 

    @interface MyClass: SomeSuperclass 
    { 
        ... 
    } 
    SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(MyClass); 

    @end 
======================================== 


    MyClass.m: 
======================================== 
    #import "MyClass.h" 

    @implementation MyClass 

    SYNTHESIZE_SINGLETON_FOR_CLASS(MyClass); 

    ... 

    @end 
========================================

开源库下载地址

3、

iOS在4.0以后推出了blockGCD,这两个特性给iOS开发带来的很大的便利,也使开发变得更加趣味话。那么如何通过GCD+block来实现单例模式呢,这主要归功于dispatch_once(dispatch_once_t *predicate, ^(void)block)这个GCD的函数,他有两个参数第一参数是一个指向dispatch_once_t类型结构体的指针,用来测试block是否执行完成,该指针所指向的结构体必须是全局的或者静态的,第二个参数是一个返回值与参数均为空的block,在block体中进行对象的初始化即可。dispatch_once在程序的生命周期中保证只会被调用一次,所以在多线程中也不会有问题。 该种方法使用方法:

 

+ (Singleton *)sharedInstance
{
    static Singleton *instance = nil;

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

    return instance;
}


dispatch_once的作用就是执行且在整个程序的声明周期中,仅执行一次某一个block对象。简直就是为单例而生的嘛。而且,有些我们需要在程序开头初始化的动作,如果为了保证其,仅执行一次,也可以放到这个dispatch_once来执行。

然后我们看到它需要一个断言来确定这个代码块是否执行,这个断言的指针要保存起来,相对于第一种方法而言,还需要多保存一个指针。

 

方法简介中就说的很清楚了:对于在应用中创建一个初始化一个全局的数据对象(单例模式),这个函数很有用。

如果同时在多线程中调用它,这个函数将等待同步等待,直至该block调用结束。

这个断言的指针必须要全局化的保存,或者放在静态区内。使用存放在自动分配区域或者动态区域的断言,dispatch_once执行的结果是不可预知的。

 

总结:1.这个方法可以在创建单例或者某些初始化动作时使用,以保证其唯一性。2.该方法是线程安全的,所以请放心大胆的在子线程中使用。(前提是你的dispatch_once_t onceToken

对象必须是全局或者静态对象。这一点很重要,如果不能保证这一点,也就不能保证该方法只会被执行一次。)

 


参考博客:

水滴石穿 Keeping faith.      --- wtlucky's Blog

 

posted @ 2014-01-10 14:18  ymonke  阅读(786)  评论(0编辑  收藏  举报