iOS模式之四:单例模式

单例模式

什么是单例模式?

单例模式想一个大独裁者,他规定在他的国度里面,所有数据的访问和请求都得经过他,甚至你要调用相关的函数也得经过它。学术一点就是,单例模式,为某一类需求和数据提供了统一的程序接口。主要的实现技术就是,确保全局只有一个对象的实例存在。举个例子把,比如NSNotificationCenter中的 defaultCenter 负责全局的消息分发、NSFileManager 的 defaultManager统一负责物理文件的管理、NSUserDefaults 的 standardUserDefaults

统一管理用户的配置文件……不一而足。在整个iOS框架中,可以说是大规模使用了单例模式。

单例模式的原理及实现:

在非ARC情况下实现一个单例

@implementation DZSinglonNoARC

+ (DZSinglonNoARC*) shareInstance

{

static DZSinglonNoARC* share = nil;

@synchronized(self)

{

if (!share) {

share = [[super allocWithZone:NULL] init];

}

}

return share;

}

+ (instancetype) allocWithZone:(struct _NSZone *)zone

{

return [self shareInstance];

}

- (instancetype) copyWithZone:(NSZone*)zone

{

return self;

}

- (id) retain

{

return [DZSinglonNoARC shareInstance];

}

- (oneway void) release

{

}

- (instancetype) autorelease

{

return self;

}

- (unsigned) retainCount

{

return UINT_MAX;

}

@end

@implementation DZSinglonNoARC

+(DZSinglonNoARC*)shareInstance

{

static DZSinglonNoARC*share=nil;

@synchronized(self)

{

if(!share){

share=[[super allocWithZone:NULL]init];

}

}

returnshare;

}

+(instancetype)allocWithZone:(struct_NSZone*)zone

{

return[self shareInstance];

}

-(instancetype)copyWithZone:(NSZone*)zone

{

return self;

}

-(id)retain

{

return[DZSinglonNoARC shareInstance];

}

-(onewayvoid)release

{

}

-(instancetype)autorelease

{

return self;

}

-(unsigned)retainCount

{

return UINT_MAX;

}

@end

首先要初始化一个该类的静态化变量

static DZSinglonNoARC* share = nil;

@synchronized(self)

{

if (!share) {

share = [[super allocWithZone:NULL] init];

}

}

return share;

static DZSinglonNoARC*share=nil;

@synchronized(self)

{

if(!share){

share=[[super allocWithZone:NULL]init];

}

}

return share;

在这里进行了加锁处理,是为了防止多线程重入的情况下,造成静态变量多次分配内存和初始化,从而会导致数据混乱。这里加锁的对象是self,实际上是[DZSinglonNoARC class]

[DZSinglonNoARC class]类DZSinglonNoARC的class对象,也就是说加锁对象是全局唯一的一个Class对象。而且在这里share的定义放在了函数shareInstance之内,是要让share变成一个函数内的局部变量这样可以防止,外部的异常访问。看到网上有些教程中,把share的定义放在函数之外,变成了文件内的一个全局静态变量,这样会存在其他函数异常操作share的情况。注意在初始化share的时候我们使用的是share = [[super allocWithZone:NULL] init];

share=[[super allocWithZone:NULL]init];之所以这样做,是为了防止想用[DZSinglonNoARC alloc]等函数的时候,会与allocWithZone引起的死锁和死循环。

而关于此处加锁的处理还有一个优化版本,在4.0以上的SDK有了闭包之后,我们可以这么做

+ (DZSinglonNoARC*) share2

{

static DZSinglonNoARC* share = nil;

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

share = [[super allocWithZone:NULL] init];

});

return share;

}

+(DZSinglonNoARC*)share2

{

static DZSinglonNoARC*share=nil;

static dispatch_once_tonceToken;

dispatch_once(&onceToken,^{

share=[[super allocWithZone:NULL]init];

});

return share;

}

使用dispatch_once体带原来的加锁操作,这样做的好处就是可以减少每次加锁的时间,优化了程序性能。dispatch_once函数接收一个dispatch_once_t用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用)。它还接收一个希望在应用的生命周期内仅被调度一次的代码块,对于本例就用于share实例的实例化。dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的。完全可以替代相对低效的加锁操作。

然后是重载了一些列的函数,重载这些函数的目的,就是修改原有的NSOjbect的内存操作相关的函数,保持内存中有且只有一个该类的对象。

arc下的实现:

我们先看一个例子

@implementation DZSinglonARC

+  (DZSinglonARC*) shareInstance

{

static DZSinglonARC* share = nil;

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

share = [[super allocWithZone:NULL] init];

});

return share;

}

+ (instancetype) allocWithZone:(struct _NSZone *)zone

{

return [self shareInstance];

}

@end

@implementation DZSinglonARC

+(DZSinglonARC*)shareInstance

{

static DZSinglonARC*share=nil;

static dispatch_once_tonceToken;

dispatch_once(&onceToken,^{

share=[[super allocWithZone:NULL]init];

});

return share;

}

+(instancetype)allocWithZone:(struct_NSZone*)zone

{

return[self shareInstance];

}

@end

通过上面的代码我们能够看出其实在ARC下单例的实现方式与非ARC下大同小异,只不过是没有重载内存管理的函数而已。而这也得益于ARC这种技术。

单例工厂

在实际的编程工作中,随着项目规模的不断扩大,我们往往发现在整个项目中存在着大量的单例。于是我们就会遇到一个问题如何去管理这些单例。

同时,也会遇到每一次都要按照上面的实现方式从头来一遍来实现一个单例,从编程效率上看难免有些低下,毕竟很多代码都是相同的。使用设计模式的目标之一就是合理的干掉重复的代码。那么有没有一个好的方式来管理这些单例呢,我们很自然的想到了工厂模式。

工厂方法模式(英语:Factory method pattern)是一种实现了“工厂”概念的面向对象设计模式。就像其他创建型模式一样,它也是处理在不指定对象具体类型的情况下创建对象的问题。工厂方法模式的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。” 创建一个对象常常需要复杂的过程,所以不适合包含在一个复合对象中。创建对象可能会导致大量的重复代码,可能会需要复合对象访问不到的信息,也可能提供不了足够级别的抽象,还可能并不是复合对象概念的一部分。工厂方法模式通过定义一个单独的创建对象的方法来解决这些问题。由子类实现这个方法来创建具体类型的对象。(引用自WIKI)

工厂模式解决的就是这种,重建同类型对象的问题。而这里,我们可以把单例看成同类型的一系列对象。那就创建一个单例工厂吧。项目地址:https://github.com/yishuiliunian/DZSinglonFactory.git

先看一下如何实现一个简单的单例工厂

@interface DZSingletonFactory()

{

NSMutableDictionary* data;

}

@end

@implementation DZSingletonFactory

- (id) init

{

self = [super init];

if (self) {

data = [[NSMutableDictionary alloc] init];

}

return self;

}

+ (DZSingletonFactory*) shareFactory

{

static DZSingletonFactory* share = nil;

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

share = [[DZSingletonFactory alloc] init];

});

return share;

}

- (id) copyWithZone:(NSZone*)zone

{

return self;

}

//over singlong

- (void) setShareData:(id)shareData  forKey:(NSString*)key

{

if (shareData == nil) {

return;

}

[data setObject:shareData forKey:key];

}

- (id) shareDataForKey:(NSString*)key

{

return [data objectForKey:key];

}

- (id) shareInstanceFor:(Class)aclass

{

NSString* className = [NSString stringWithFormat:@"%@",aclass];

@synchronized(className)

{

id shareData = [self shareDataForKey:className];

if (shareData == nil) {

shareData = [[NSClassFromString(className) alloc] init];

[self setShareData:shareData forKey:className];

}

return shareData;

}

}

- (id) shareInstanceFor:(Class)aclass category:(NSString *)key

{

NSString* className = [NSString stringWithFormat:@"%@",aclass];

NSString* classKey = [NSString stringWithFormat:@"%@-%@",aclass,key];

@synchronized(classKey)

{

id shareData = [self shareDataForKey:classKey];

if (shareData == nil) {

shareData = [[NSClassFromString(className) alloc] init];

[self setShareData:shareData forKey:classKey];

}

return shareData;

}

}

@end

@interface DZSingletonFactory()

{

NSMutableDictionary*data;

}

@end

@implementation DZSingletonFactory

-(id)init

{

self=[superinit];

if(self){

data=[[NSMutableDictionary alloc]init];

}

return self;

}

+(DZSingletonFactory*)shareFactory

{

static DZSingletonFactory*share=nil;

static dispatch_once_tonceToken;

dispatch_once(&onceToken,^{

share=[[DZSingletonFactory alloc]init];

});

return share;

}

-(id)copyWithZone:(NSZone*)zone

{

return self;

}

//over singlong

-(void)setShareData:(id)shareDataforKey:(NSString*)key

{

if(shareData==nil){

return;

}

[datasetObject:shareDataforKey:key];

}

-(id)shareDataForKey:(NSString*)key

{

return[dataobjectForKey:key];

}

-(id)shareInstanceFor:(Class)aclass

{

NSString*className=[NSString stringWithFormat:@"%@",aclass];

@synchronized(className)

{

id shareData=[self shareDataForKey:className];

if(shareData==nil){

shareData=[[NSClassFromString(className)alloc]init];

[selfsetShareData:shareDataforKey:className];

}

return shareData;

}

}

-(id)shareInstanceFor:(Class)aclasscategory:(NSString*)key

{

NSString*className=[NSString stringWithFormat:@"%@",aclass];

NSString*classKey=[NSString stringWithFormat:@"%@-%@",aclass,key];

@synchronized(classKey)

{

id shareData=[self shareDataForKey:classKey];

if(shareData==nil){

shareData=[[NSClassFromString(className)alloc]init];

[self setShareData:shareDataforKey:classKey];

}

return shareData;

}

}

@end

其实单例工厂类也是一个实例,之所以这么做,是因为我们所生产的单例总得有个仓库存着吧。而这个单例工厂类除了有生产单例的功能,也担负着仓库存储生产的单例的功能。私有变量NSMutableDictionary* data;就是仓库。

而生产的车间是

- (id) shareInstanceFor:(Class)aclass

{

NSString* className = [NSString stringWithFormat:@"%@",aclass];

@synchronized(className)

{

id shareData = [self shareDataForKey:className];

if (shareData == nil) {

shareData = [[NSClassFromString(className) alloc] init];

[self setShareData:shareData forKey:className];

}

return shareData;

}

}

-(id)shareInstanceFor:(Class)aclass

{

NSString*className=[NSStringstringWithFormat:@"%@",aclass];

@synchronized(className)

{

id shareData=[self shareDataForKey:className];

if(shareData==nil){

shareData=[[NSClassFromString(className)alloc]init];

[self setShareData:shareDataforKey:className];

}

return shareData;

}

}

这个函数及其简单,我们使用了Objective-C一些动态语言的特性,直接通过类Class对象来生成实例,如果你对objc的底层有些了解的话,应该知道其实Class也是一个对象,他也能够执行objc的方法。也就是说,如果我们要生成类A的一个实例,只要我们有了类A的类型对象(Class)实例就OK,然后通过[(Class*)aClass new]你就能轻而易举的生成一个实例。

在我们通过这种技术生成了一个单例的实例之后,将其存储在仓库data里面,下次再次请求这个类的实例的时候,只要从仓库中取出来用就行了。

我们甚至为了编程时再少写点代码可以写一个函数和宏:

id  DZSingleForClass(Class a)

{

return [DZShareSingleFactory shareInstanceFor:a];

}

..........

#define DZShareSingleFactory [DZSingletonFactory shareFactory]

#ifdef __cplusplus

extern "C" {

#endif

id  DZSingleForClass(Class a);

#ifdef __cplusplus

}

#endif id DZSingleForClass(Classa)

{

return[DZShareSingleFactory shareInstanceFor:a];

}

..........

#define DZShareSingleFactory [DZSingletonFactory shareFactory]

#ifdef __cplusplus

extern"C"{

#endif

idDZSingleForClass(Classa);

#ifdef __cplusplus

}

#endif

这样我们在需要创建单例的时候一句话就能搞定:

+ (DZSingletonFactory*) shareInstance

{

return DZSingleForClass([DZShareSingleFactory class]);

}

+(DZSingletonFactory*)shareInstance

{

return DZSingleForClass([DZShareSingleFactoryclass]);

}

不过这只是一个极度简化版的单例工厂,很多保护性的措施还都没做。比如对于allocWithZone的重载等,还有一些单例注销的操作。

其实这是一种这种的策略,我们没有重载内存管理函数,是为了能够在后面为了节省内存,在单例较长时间不用的时候将其销毁掉,等下次用的时候再创建。

- (void) destoryInstanceFor:(Class)aclass

{

NSString* className = [NSString stringWithFormat:@"%@",aclass];

@synchronized(className) {

if ([self shareDataForKey:className]) {

[data removeObjectForKey:className];

}

}

}

-(void)destoryInstanceFor:(Class)aclass

{

NSString*className=[NSStringstringWithFormat:@"%@",aclass];

@synchronized(className){

if([selfshareDataForKey:className]){

[dataremoveObjectForKey:className];

}

}

}

当然这中使用方式,看起来不太像是严格意义上的单例模式,但是他却完成单例模式最根本的意图,把接口和功能统一。模块管理系统还记得文章的标题吗?单例模式的进化,那么这里单例模式要进化到什么地步呢?在实际的编码过程中,随着工程规模的不断扩大,我们可能会在我们的项目中大规模的时候单例模式。就像是世界中,有了N多个独裁者,协调这些独裁者,对任何一个系统来说都是困难的。这个时候,就会遇到一个问题:如何有效的管理

数量较多的单例。虽然我们使用单例工厂的方式,解决了单例统一构建的问题,但是没有能够解决统一管理的问题。这里统一管理的问题包括:

单例初始化的控制,不同的单例在初始化的时候可能需要不同的参数。甚至有些单例可以延迟加载。

单例注销的统一管理。

能够让开发者或者SDK使用者,直接了当的看到整个工程中使用了多少单例,每个单例的大概模样是怎么样子的。

管理各个单例之间的依赖关系。比如地理位置信息单例可能会依赖数据库的一个单例。

根据不同的需求,动态的加载某些单例。

其他等等……

这些需求一列,你会感觉怎么像是在说一个包管理系统比如debian的apt-get或者node的npm之类的东西呢,甚至还有点像windows的动态链接库或者linux的模块化机制。

其实如果我们剖析一下的话,其实单例模式是一种实现程序架构模块化的有利工具。他将某些内聚性非常高的功能,聚合在其一起,通过单一实例与外界交互。同时也降低了与其他单例之间的耦合性。从宏观的角度来看,我们可以把一个个单例看成一个个功能性模块,用一个形象的比喻就是插件。这样,一个充斥着大量单例,并且能够对这些单例有效管理的系统,从宏观的角度看,就像是一个插件系统(准确说是模块管理系统)。

参考博客:

http://blog.jobbole.com/56439/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 143,809评论 1 304
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 61,651评论 1 257
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 95,178评论 0 213
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 41,241评论 0 181
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 49,047评论 1 259
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,899评论 1 178
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,503评论 2 274
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,249评论 0 168
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 29,125评论 6 235
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,605评论 0 213
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,368评论 2 215
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,723评论 1 232
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,285评论 0 32
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,190评论 2 214
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,634评论 3 209
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,651评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,052评论 0 167
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,638评论 2 232
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,760评论 2 237

推荐阅读更多精彩内容

  • *7月8日上午 N:Block :跟一个函数块差不多,会对里面所有的内容的引用计数+1,想要解决就用__block...
    炙冰阅读 2,435评论 1 14
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 26,997评论 29 471
  • iOS开发多线程篇—单例模式(ARC) 一、简单说明: 设计模式:多年软件开发,总结出来的一套经验、方法和工具 j...
    不会打滚儿的狮子阅读 437评论 0 2
  • 单例:意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单...
    CoderZS阅读 583评论 1 13
  • 订阅战隼老师的微信公众号warfalcon有蛮长时间了,在阅读、提高效率、时间管理、自我成长等方面时有获益。今天看...
    115c3253903d阅读 3,475评论 0 20