在iOS中有很多的设计模式,有一本书《Elements of Reusable Object-Oriented Software》(中文名字为《设计模式》)讲述了23种软件设计模式,这本书中的设计模式都是面向对象的,很多语言都有广泛的应用,在苹果的开发中,当然也会存在这些设计模式,我们所使用的无论是开发Mac OX系统的Cocoa框架还是开发iOS系统的Cocoa Touch框架,里面的设计模式也是由这23种设计模式演变而来。本文着重详细介绍在开发iOS时采用的单例模式,从设计过程的演变和细节的完善进行分析,相信大家能够从中获得重要的思路原理而不是仅仅知道应该这么写单例模式却不知为何这么写,当然,理解透彻后,为了我们的开发效率,我们可以将单例模式的代码封装到一个类中然后定义成宏,适配于ARC和MRC模式,让开发效率大大提高。这些操作在本文中都会一一讲到,接下来就进入正题。
在讲述之前,先说明本文的层次结构,本文分成了5个部分,下面依次罗列
1、单例模式中懒汉式的实现
2、单例模式中饿汉式的实现
3、使用GCD代替手动加锁判断处理
4、非ARC情况的单例模式
5、单例模式的代码实用化(封装便于开发直接使用)
前言:
所谓的单例模式,就是要实现在一个应用程序中,对应的一个类只会有一个实例,无论创建多少次,都是同一个对象。大家在开发过程中也见过不少的单例,比如UIApplication、UIAccelerometer(重力加速)、NSUserDefaults、NSNotificationCenter,当然,这些是开发Cocoa Touch框架中的,在Cocoa框架中还有NSFileManager、NSBundle等。在iOS中,懒加载几乎是无处不在的,其实,懒加载在某种意义上也是采用了单例模式的思想(如果对象存在就直接返回,对象不存在就创建对象),那么本文就从大家熟悉的懒加载入手进行讲解(整个过程都用实际的代码进行说明)。
一、单例模式中懒汉式的实现
新建一个工程(本文是single view工程),创建一个继承于NSObject的类,命名为NTMoviePlayer,首先我们尝试下使用懒加载,在viewController里面导入NTMoviePlayer.h,定义一个NTMoviePlayer的对象,然后写出懒加载代码,这样好像真的是可以做到在viewController里面只有一个NTMoviePlayer对象,但是如果又创建一个类,然后进行同样的操作,两次创建的对象还会是一样的吗?答案很明显,不一样,我们可以从这个现象去推导问题发生的根本原因,那就是在不同的类中,创建NTMoviePlayer对象的时候都会进行一个alloc操作,那么这个alloc实际上就是分配内存空间的一个操作,分配了不同的内存区域,那么当然创建了不同的对象,所以,如果要保证应用中就只有一个对象,就应该让NTMoviePlayer类的alloc方法只会进行一次内存空间的分配。这样,找到了问题所在,就去实现代码,重写alloc方法,这里提供了两种方法,一种是alloc,一种是allocWithZone方法,其实在alloc调用的底层也是allocWithZone方法,所以在此,我们需要重写allocWithZone方法:
id moviePlayer;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
if (moviePlayer == nil) {
// 调用super的allocWithZone方法来分配内存空间
moviePlayer = [super allocWithZone:zone];
}
return moviePlayer;
}
在这里我们初步使用懒加载来控制保证只有一个单例,但是这种仅仅适合在单一线程中使用的情况,要是涉及到了多线程的话,那么就会出现这样的情况,当一个线程走到了if判断时,判断为空,然后进入其中去创建对象,在还没有返回的时候,另外一条线程又到了if判断,判断仍然为空,于是又进入进行对象的创建,所以这样的话就保证不了只有一个单例对象。于是,我们对代码进行手动加锁。
id moviePlayer;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
// 在这里加一把锁(利用本类为锁)进行多线程问题的解决
@synchronized(self){
if (moviePlayer == nil) {
// 调用super的allocWithZone方法来分配内存空间
moviePlayer = [super allocWithZone:zone];
}
}
return moviePlayer;
}
这样的话,就可以解决上述问题,但是,每一次进行alloc的时候都会加锁和判断锁的存在,这一点是可以进行优化的(在java中也有对于这种情况的处理),于是在加锁之前再次进行判断,修改代码如下:
id moviePlayer;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
// 在这里判断,为了优化资源,防止多次加锁和判断锁
if (moviePlayer == nil) {
// 在这里加一把锁(利用本类为锁)进行多线程问题的解决
@synchronized(self){
if (moviePlayer == nil) {
// 调用super的allocWithZone方法来分配内存空间
moviePlayer = [super allocWithZone:zone];
}
}
}
return moviePlayer;
}
到此,在allo