单例模式是一个类在系统中只有一个实例对象。通过全局的一个入口点对这个实例对象进行访问。
在iOS开发中,单例模式是非常有用的一种设计模式。
OS SDK中也有许多类使用了单例模式,例如,UIApplication:当程序启动的时候,会调用UIApplicationMain方法,在该方法中,会实例化一个UIApplication对象,之后在程序中的任意地方调用sharedApplication方法都将返回一个与当前应用程序相关的UIApplication实例(UIApplicationMain方法中创建的UIApplication单例)。
单例模式的作用
什么时候使用单例模式?
在整个应用程序中,共享一份资源。(这份资源只需要创建/初始化一次)
怎么创建一个单例类?
在OC中,单例的实现方式一般分为两种,ARC/MRC
(如果不想知道详细实现过程,可以直接跳到文章尾部下载代码,利用宏来快速简单的创建单例,兼容ARC/MRC)
现在以创建一个MySingletonClass的单例模式为例。首先我们创建一个MySingletonClass类
为了保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问.
通常我们需要重写两个方法以及提供一个快速创建的方法供外界使用
按照iOS SDK的习惯我们应该自定义的方法(share+类名)
+ (instancetype)sharedMySingletonClass;
比如我们要拿到 UIApplication的适合我们会这样做
[UIApplicationsharedApplication]
另外我们应该重写
+ (id)allocWithZone:(struct_NSZone *)zone; //(alloc底层实际上会调用这个方法)
- (id)copyWithZone:(NSZone *)zone; // 非必要,防止外界使用copy创建
下面直接上代码
//用来保存唯一的单例对象 static 作用是保证_instance仅作用于当前文件,防止外界拿到修改
static id _instance;
// 提供快速创建方法
+ (instancetype)sharedMySingletonClass
{
if (nil == _instance) { //防止多次加锁
@synchronized(self){ //加锁防止多个线程同时进来操作
if (nil == _instance) { //防止多次创建
_instance = [[self alloc] init]; //调用alloc方法
}
}
}
return _instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
if (nil == _instance) { //防止多次加锁
@synchronized(self){ //加锁防止多个线程同时进来操作
if (nil == _instance) { //防止多次创建
_instance = [super allocWithZone:zone]; //调用父类方法
}
}
}
return _instance;
}
// 因为如果通过copy来创建对象,必须是先有一个对象,所以可以直接返回_instance
// 另外如果通过copy来创建对象,必须实现NSCopying协议,所以如果你敲下面代码没有提示的话,先去遵守协议
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
在上面的代码中(关键字 @synchronized 是为了保证我们的单例的线程级别的安全,可以适用于多线程模式下。)
static变量_instance用于存储一个单例的指针,并且保证实例仅作用于当前文件
staticid _instace 这个id也可以改成本类,写id类型是为了方便之后把代码写成宏
这样不管外界创建几次,我们提供的始终是同一个对象,内存地址也都是一样的。
上面的代码是一种创建单例的方法,也是最容易想到最基础的方式
下面再说一种CGD的方法创建
CGD中提供了一个dispatch_once: 函数,这个函数的作用是保证代码在程序运行过程中仅执行一次,是不是跟我们创建单例的需求很相似。
“有些变量只需要初始化一次(如从文件中读取配置参数,读取设备型号等等),可以使用dispatch_once来进行读取优化,保证只调用API一次,以后就只要直接访问变量即可。”
//用来保存唯一的单例对象
static id _instance;
+ (instancetype)sharedMySingletonClassb;
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
这样也能达到相同的效果,不过代码看起来要简洁一些
到这里我们ARC下的单例模式创建就讲完了,但是在MRC(手动内存管理)下,我们仅仅重写上面那两个方法是不够的
比如在外界,别人对你的单例对象release了,对象被销毁了我们就拿不到了,并且对象是无法再被创建的。
所以我们还需要重写一下几个方法
- (oneway void)release { } //release的时候什么都不执行
- (id)retain { return self; } // retain 的时候就返回self
- (NSUInteger)retainCount { return 1;} //调用这个方法的时候始终保持count为1
- (id)autorelease { return self;}
到这里我们创建的单例应该是非常安全的了。但是大家有没有发现,我们创建两个了两个单例基本上写的内容都一样,
差别只是有几处类名不同而已。这时候懒的程序员会想办法既然每次写的代码都一样,
那有没有什么办法能一劳永逸让我们不再去写这些重复的代码,我们首先想到的是继承,不过实践之后失败了
因为对象只会被创建一次。之后在想到把代码定义成宏,宏里面只传入类名
一开始我们会定义两个宏来满足ARC/MRC两种情况,后来慢慢发现其实利用条件编译,只需要一个宏就行了。
// .h文件
#define HFSingletonH(name) + (instancetype)shared##name;
// .m文件
#define HFSingletonM(name) \
static id _instance; \
\
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
return _instance; \
} \
\
+ (instancetype)shared##name \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[self alloc] init]; \
}); \
return _instance; \
} \
\
- (id)copyWithZone:(NSZone *)zone \
{ \
return _instance; \
}
"\"是让系统知道后面也是宏的一部分 “##”后面代表的就是前面传入的name参数(类名)
这样写完以后我们在创建单例只需要两行代码了
在.h里面 HFSingletonH(MySingletonClassC)
在.m里面 HFSingletonM(MySingletonClassC)
MRC 下的宏
// .h文件
#define HFSingletonH(name) + (instancetype)shared##name;
// .m文件
#define HFSingletonM(name) \
static id _instance; \
\
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
return _instance; \
} \
\
+ (instancetype)shared##name \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[self alloc] init]; \
}); \
return _instance; \
} \
\
- (id)copyWithZone:(NSZone *)zone \
{ \
return _instance; \
} \
\
- (oneway void)release { } \
- (id)retain { return self; } \
- (NSUInteger)retainCount { return 1;} \
- (id)autorelease { return self;}
下面是完整版的宏,兼容ARC/MRC,带着这个文件走天下就行了.
// .h文件
#define HFSingletonH(name) + (instancetype)shared##name;
// .m文件
#if __has_feature(objc_arc)
#define HFSingletonM(name) \
static id _instace; \
\
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [super allocWithZone:zone]; \
}); \
return _instace; \
} \
\
+ (instancetype)shared##name \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [[self alloc] init]; \
}); \
return _instace; \
} \
\
- (id)copyWithZone:(NSZone *)zone \
{ \
return _instace; \
}
#else
#define HFSingletonM(name) \
static id _instace; \
\
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [super allocWithZone:zone]; \
}); \
return _instace; \
} \
\
+ (instancetype)shared##name \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [[self alloc] init]; \
}); \
return _instace; \
} \
\
- (id)copyWithZone:(NSZone *)zone \
{ \
return _instace; \
} \
\
- (oneway void)release { } \
- (id)retain { return self; } \
- (NSUInteger)retainCount { return 1;} \
- (id)autorelease { return self;}
#endif
源码下载:https://github.com/hongfenglt/singleton
这篇文章断断续续写了两三天,发现写博客确实对自己是一种考验。而且对自己提升也很有帮助。
希望自己能坚持下去,尽力保持每周一到两篇.