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 = [[NSObjectalloc]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 版本验证。
来源: http://lib.csdn.net/article/ios/42106