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

iOS 中单例的使用详解

2016-03-10 10:57 507 查看
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中)代码:

[objc] view
plain copy

#import <Foundation/Foundation.h>

@interface WDSingletonManager : NSObject

//去掉前缀,share + 类名

+ (instancetype)shareSingletonManager;

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

//+ (instancetype)shareDataHandle;

@end

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

[objc] view
plain copy

@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和返回我们自己的静态变量实质是一样的。

[objc] view
plain copy

#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返回的对象地址是一致的,当然这只是个例,不过我还是希望大家注意。

[objc] view
plain copy

NSMutableArray *array = [NSMutableArray alloc];

NSLog(@"%p",array);

array = [array init];

NSLog(@"%p",array);

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

第四步:多线程问题

[objc] view
plain copy

+ (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是在整个程序的生命周期中只运行一次,所以有效的避免了各种问题,包括多线程访问的问题。

[objc] view
plain copy

+ (instancetype)shareSingletonManager{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

singletonManager = [[self alloc]init];

});

return singletonManager;

}

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