单例模式的使用:通过dispatch_once创建单例

什么是单例呢?Wikipedia是如此定义的:
    在软件工程中,单例是一种用于实现单例的数学概念,即将类的实例化限制成仅一个对象的设计模式。
 
附上我自己对单例的理解:
    单例就是类,但这是一个只能被实例化一次的类,或者说该类永远只能被实例化出一个对象。
    这就和readonly属性的懒加载(lazy)非常相像,重写属性的getter,如果字段为空则为其赋值然后返回,之后的每一次getter所得,都是该字段第一次的赋值。
    单例亦是如此,当我们第一次需要对象的时候,就将其实例化,之后的每一次加载我取得的都是第一次实例化出来的对象。
 
举例:如NSScreen,通过它的类方法mainScreen获取他的单例。因为应用所需要的屏幕对象只有一个,所以屏幕对象完全可以设计成单例对象。
    NSScreen *screen = [NSScreen mainScreen];
 
 
使用Objective-C实现单例模式的最佳方式向来有很多争论,开发者(包括Apple在内)似乎每几年就会改变他们的想法。
当Apple引入了 Grand Central Dispatch (GCD) (Mac OS 10.6和iOS4.0)之后,他们也引入了一个很适合用于实现单例模式的函数。
 
该函数就是:dispatch_once
void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);  
该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用)。   
它还接收一个希望在应用的生命周期内仅被调度一次的代码块,对于本例就用于shared实例的实例化。
dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题。
Apple的GCD Documentation明确说明了这一点:如果被多个线程调用,该函数会同步等至代码块执行完毕。
 
 
那么要如何来使用该方法呢?
 
   假设我们登陆应用时,需要将信息保存到YHAccountManager类,然后我们想在整个程序中访问该类的单例,我们可以实现如下代码:

   先创建一个名字为YHAccountManager的类

   再重载allocWithZone方法,该方法会在alloc中被调用,这就意味着程序在分配内存的时候,就已经开始创建单例


+ (instancetype)allocWithZone:(struct _NSZone *)zone{ // 加注static关键词,可以让当前实例存在直至程序关闭 static YHAccountManager *instance; // dispatch_once是线程安全的, // onceToken默认为0 该参数是一个长整型long,实际上作为BOOL使用,用于检查该代码块是否已经被调度 static dispatch_once_t onceToken; // dispatch_once宏可以保证块代码中的指令只被执行一次 dispatch_once(&onceToken, ^{ // 在多线程环境下,永远只会被执行一次,instance只会被实例化一次 instance = [super allocWithZone:zone]; }); return instance; }
 
那么我们现在尝试调用刚才创建的单例模式对象:
#import <Foundation/Foundation.h>
#import "YHAccountManager.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool { 
        for (int i = 0; i < 10; i++) {
            YHAccountManager *account = [[YHAccountManager alloc] init];
            NSLog(@"%p",account);
        }
//执行结果
//2015-04-14 20:11:06.372 CGDDemo_OnceInstance[464:10226] 0x1001001a0 //2015-04-14 20:11:06.372 CGDDemo_OnceInstance[464:10226] 0x1001001a0 //2015-04-14 20:11:06.372 CGDDemo_OnceInstance[464:10226] 0x1001001a0 //2015-04-14 20:11:06.372 CGDDemo_OnceInstance[464:10226] 0x1001001a0 //2015-04-14 20:11:06.372 CGDDemo_OnceInstance[464:10226] 0x1001001a0 //2015-04-14 20:11:06.372 CGDDemo_OnceInstance[464:10226] 0x1001001a0 //2015-04-14 20:11:06.372 CGDDemo_OnceInstance[464:10226] 0x1001001a0 //2015-04-14 20:11:06.373 CGDDemo_OnceInstance[464:10226] 0x1001001a0 //2015-04-14 20:11:06.373 CGDDemo_OnceInstance[464:10226] 0x1001001a0 //2015-04-14 20:11:06.373 CGDDemo_OnceInstance[464:10226] 0x1001001a0 } return 0; }

由以上结果可以看出,不管我们创建多少次实例,我们创建出的实例内存地址都是同一个,这就意味着我们的单例模式已经成功实现。

  

那么为了更好的看出该方法是单例,我们在向外提供一个类方法:

+ (instancetype)sharedAccountManager{
    return [[self alloc] init];
}

这样可以更好的让过别人知道对象为一个单例对象。

 

总结:该方法有很多优势
1:他是线程安全的,多个线程同时创建,始终被创建的都只有一个单例
2:很好满足静态分析器要求
3:和自动引用计数(ARC)兼容
4:仅需要少量代码
posted @ 2015-04-14 21:02  星星Star&#128523;  阅读(355)  评论(0编辑  收藏  举报