您的位置:首页 > 移动开发 > IOS开发

iOS单例设计模式详解教程

2015-06-09 16:01 447 查看
iOS中有很多的设计模式,当然任何一门语言都有很多的设计模式,单例是其中一种,单例,字面上理解来说就是单独的实例,首先它是单独唯一的,其次它是一个实例。我们知道在iOS开发中使用的Objective—C语言是面向对象语言,我们说的实例通常就是指我们创建的对象。而用来创建单例(唯一实例)的类就是单例类,这一点不难理解。

单例:

无论一个应用程序请求多少次,单例类都始终返回的是同一个实例对象。一个典型的类在用户需要的情况下会创建很多个对象,而一个单例类在一个应用程序当中只会创建唯一的一个实例。一个单例对象提供一个资源点供全局访问。单例通常在单点控制值得使用的时候使用,比如一个类要提供一些通用的服务和资源。

单例类:

我们通常通过一个工厂方法从单例类中获取一个全局的实例变量。当第一次被请求的时候,这个类会懒加载它的唯一实例(单例),以此确保此后没有其它实例被创建。一个单例类同时也阻止用户copy、retain或者release这个实例。如果我们需要,我们可以创建我们自己的单例类(系统有很多单例类,下面补充说明)。比如我们需要一个类为其它应用程序中的对象提供声音的时候,我们可以创建一个单例。

补充:

有几个Cocoa框架的类是单例类,包括NSFileManager、NSWorkSpace,并且在UIKit当中,UIApplication,UIAccelerometer都是单例类。按照惯例,工厂方法返回单一实例的名称格式,形式是shareClassType(share + 类型名)。就拿Cocoa框架举例来说,工厂方法都称之为“sharedFileManager、sharedColorPanel和sharedWorkspace”。

图示:

左图表示普通类创建对象,当外界需要的时候创建一次对象,但是每次创建的对象都不一样。右图表示单例类创建的对象,无论外界请求多少,返回的对象都是唯一的,这样在实际开发当中,我们就可以通过单例来给外界提供共有的资源和服务来使用,比如多个页面需要同一个数据,就可以在多个页面访问唯一的单例。



准备:

在学习单例之前,除了OC基本的语法之外,你需要这些知识储备:

一、必备知识

1、创建一个对象的基本形式。

2、内存管理的基本知识。

3、工厂方法的了解。

二、相关知识:

1、对象的拷贝

2、内存管理高级。

3、多线程知识。

如果你在这些方面有一些欠缺,可能不会直接导致你理解不了单例设计模式,但是会增加难度,所以我还是希望我们有一定的学习基础再去涉及设计模式的知识,当然大家完全可以通过搜索或者查阅资料做一些先决的知识储备。

单例的基本知识我们已经介绍完了,现在我们开始去学习如何创建一个单例。我们先来创建一个最简单的单例,也就是“伪单例”,是我们常用的方式,然后再一步一步健全它。

第一步:我们创建一个空工程(EmptyApplication),并且在工程中搜索,gar,启用MRC(方便我们研究和学习一个完整的单例)。



第二步:我们创建一个单例类(WDSingletonManager,当然名字自定义,你可以起名为“A”,只要你开心,别忘了继承自NSObject),并且分别在.h、.m中书写以下代码。

接口部分(.h中)代码:

#import <Foundation/Foundation.h>

@interface WDSingletonManager : NSObject

//去掉前缀,share + 类名
+ (instancetype)shareSingletonManager;

//当然你的类名如果是 XX + DataHandle(XX,代表开发者自定义的前缀,一般是你称呼的首字母组合或者团队的缩写),那么你的方法名按照苹果推荐的命名规范应该如下书写(只是建议,对程序本身无影响):
//+ (instancetype)shareDataHandle;

@end


实现部分(.m中)代码:

@implementation WDSingletonManager

//实现创建单例的工厂方法
+ (instancetype)shareSingletonManager{

//声明一个静态指针变量用于保持指针指向的对象的唯一,static这一行代码在整个工程中只执行一次。
static WDSingletonManager *singletonManager = nil;
//懒创建方式,如果没有才创建,有的话直接返回,if的分支语句只有外界第一次调用的时候才执行。
if (nil == singletonManager) {
singletonManager = [[self alloc]init];
}
return singletonManager;
}

@end


好了,至此,一个伪单例类就创建完成了,这样我们每次调用的时候,看一下对象是否唯一呢?一起来验证一下。



验证发现,结果是我们想要的,也就是说我们无论创建多少个对象,实际上都是同一个实例(因为始终在堆区始终是同一块内存空间),当然,在实际开发中,你也可以给这个单例加一个属性或者方法,达到每个页面或者每个页面的每个地方都可以访问或者修改的目的。这就是我们基本的一个单例,当然这也是不完整的。不完整在哪里,我们一起来思考一下。通常创建一个对象都是可以使用动静态方法结合来创建的,比如id obj
= [[NSObject alloc]init];//或者 id
_obj = [NSObject new];,那么我们在使用单例的时候没有谁规定一定使用我们shareClassType类似的方法去创建,所以为了保证对象的唯一性,我们需要在使用alloc + init的方法的时候也需要保证创建的对象唯一性。那怎么办到呢?其实我们每次在调用alloc方法的时候,系统都会去调用一个allocWithZone的方法,所以我们需要重写此方法。那么单例是唯一的,我们在程序中多个页面都需要使用,那么其中的一个页面或者一个地方万一release掉呢?retain呢?这时候我们的单例都无法保证它的特性。所以,我们要把内存管理的问题综合考虑进去,我们需要完善一下。

第三步:完善步骤(在我们的.m中书写)。

注:在静态方法(类方法)中,self代表的的是类,在动态方法中(对象方法,也就是俗称的“减号方法”),self代表的是当前的对象,因为单例类的唯一性,所以我们在调用静态方法的时候,返回self和返回我们自己的静态变量实质是一样的。

#import "WDSingletonManager.h"
@implementation WDSingletonManager
//声明一个静态指针变量用于保持指针指向的对象的唯一,static这一行代码在整个工程中只执行一次。
static WDSingletonManager *singletonManager = nil;

//实现创建单例的工厂方法
+ (instancetype)shareSingletonManager{

//懒创建方式,如果没有才创建,有的话直接返回,if的分支语句只有外界第一次调用的时候才执行。
if (nil == singletonManager) {
singletonManager = [[self alloc]init];
}
return singletonManager;
}

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

if (!singletonManager) {
//记住这里不是self,而是super,因为self调用alloc或者allocWithZone都会导致递归,用super可以有效的避免
singletonManager = [super allocWithZone:zone];
}
return singletonManager;
}

//重写init方法,保证初始化返回的对象唯一。
- (instancetype)init{

if (!self) {
self = [super init];
}
return self;
}

//直接返回自己,不让内部做任何操作,确保对象唯一
- (instancetype)retain{

return self;
}
//直接返回自己,不让内部做任何操作,确保对象唯一
- (id)copy{

return self;
}
//直接返回自己,不让内部做任何操作,确保对象唯一,不过需要先遵守NSCopying协议
- (id)copyWithZone:(NSZone *)zone{

return self;
}
//不让释放,重写方法,不做任何操作即可。(one way代表此操作无法“回滚”,即不能撤销)
- (oneway void)release{

}
//区别于release,autorelease是有返回值的,我们只需要返回自己(self)或者返回我们的单例(singletonManager)就可以
- (instancetype)autorelease{

return self;

}
//以下方法可不写,当然包括dealloc方法也不需要重写

//不让外界访问我们的引用计数(模仿系统的类簇比如NSString的retainCount),给出一个不可能的值(无实际意义,装逼用)
- (NSUInteger)retainCount{

return NSIntegerMax;//或者-1,也可以。
}

@end


补充:很多人告诉大家,关于整个单例类的设计中,init的相关方法可以不写,因为alloc已经分配了空间,这是错误的结论。我希望大家对此有一个认知。alloc,是静态的空间分配,init是动态的初始化过程。一般情况下他们返回的地址是一样的,但是也会有例外。所以我们无法保证。下面这段代码(大家拷贝到自己的工程去验证)就告诉大家不要轻易的相信alloc和init返回的对象地址是一致的,当然这只是个例,不过我还是希望大家注意。

NSMutableArray *array = [NSMutableArray alloc];
NSLog(@"%p",array);
array = [array init];
NSLog(@"%p",array);


较完善的单例创建就已经OK了,这是我们目前在MRC情况下考虑比较全面的情况,也是依照苹果官方的建议书写的单例。但是随着我们学习知识的全面性,依然有一些问题暴露出来了,比如多线程,那么,多线程会出现什么问题呢?当两个线程同时访问一个单例,这时候如果单例为nil,那么他们同时去创建单例,又两个线程同时创建单例对象,显然又不符合我们的要求。那么我们需要怎么做呢?我们需要进行多线程情况下的保护操作,只需要给下面这三个方法加锁就可以了。

第四步:多线程问题

+ (instancetype)shareSingletonManager{

//确保每次只有一个线程可以访问
@synchronized(self){

if (nil == singletonManager) {
singletonManager = [[WDSingletonManager alloc]init];
}

}

return singletonManager;
}

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

@synchronized(self){

if (nil == singletonManager) {
singletonManager = [super allocWithZone:zone];
}

}
return singletonManager;
}
- (instancetype)init{

@synchronized(self){

if (!self) {
self = [super init];
}
}
return self;
}


当然,这还不是唯一的,因为我们知道多线程也可以创建单例,也省去了我们考虑多线程在内的因素。就是用下面的方法去创建单例(替换掉第一个加号方法就可以),可以有效的避免,至于涉及到的多线程知识,那就是dispatch_once了,我们知道dispatch_once是在整个程序的生命周期中只运行一次,所以有效的避免了各种问题,包括多线程访问的问题。

+ (instancetype)shareSingletonManager{

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singletonManager = [[self alloc]init];
});
return singletonManager;
}


好了,我知道赘述到这里,相信大家已经对单例有了一个详细的了解了,希望可以帮到大家。当然,实际开发中我们可能只需要一个伪单例,或者是在ARC的情况下去处理一些问题,有时候不会碰到这么多的问题。所以我们只是做一个详细的解释,供大家参考。不要一味的追寻完整性,使代码冗余,高效的代码才是我们追求的。当然,单例是一种很好的设计模式,做解析数据的公共数据源,界面传值等等都是很好的方案。不过也不要乱用,因为一旦创建了单例,只有程序被kill掉单例才会消失,所以对于内存精确控制的学生,一定要不要滥用单例。

有疑问欢迎随时沟通,我的新浪微博:己怽。

我也会更新我的视频提供给大家,欢迎有需要的朋友互相交流学习。

后续更新:http://v.youku.com/v_show/id_XMTMyMTA4NjM5Mg==.html(原视频删除,新录了一次,更新和完善了一些内容,请选择至少高清画质播放)

注:以上所以代码和演示,均在Xcode6.3.2版本验证。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: