单例模式
单例模式,是一种常用的软件设计模式。
概念
- 通过单例可以保证系统中一个类只有一个对象实例;
- 单例的核心结构中只包含一个被称为单例的特殊类。
使用场合
在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次)。
例如:登陆控制器,网络数据请求,音乐播放器等一个工程需要使用多次的控制器或方法。
优缺点
优点:
- 单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
- 如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
- 单例模式因为类控制了实例化过程,所以类可以更加灵活修改实例化过程。
缺点:
- 单例对象一旦建立,对象指针是保存在静态区的,单例对象在堆中分配的内存空间,会在应用程序终止后才会被释放。
- 单例类无法继承,因此很难进行类的扩展。
- 单例不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
注意
- 我们在使用单例类之前,一定要考虑好单例类是否适合和类以后的扩展性,避免盲目滥用单例
- 例模式不可以使用继承,因为使用继承,同时也会继承静态变量,当子类和父类同时创建的时候只会创建一个先创建的实例对象。
在OC中的使用
- NSNotificationCenter 中的 defaultCenter 负责全局的消息分发;
- NSFileManager 的 defaultManager 统一负责物理文件的管理;
- NSUserDefaults 的 standardUserDefaults 统一管理用户的配置文件;
单例在ARC中的实现
iOS [Objective-C] 单例模式 在ARC中的实现
ARC实现单例步骤
- 在类的内部提供一个
static
修饰的全局变量 - 提供一个类方法,方便外界访问
- 重写
+allocWithZone
方法(注意线程安全),保证永远都只为单例对象分配一次内存空间 - 严谨起见,重写
-copyWithZone
方法和-MutableCopyWithZone
方法
注意: <NSCopying>的协议,并实现相应的方法,防止别人误调copy方法而崩溃
ARC 单例实现源码
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface CustomManager : NSObject
+ (instancetype)new NS_UNAVAILABLE; // 禁止使用
//命名规范 share+类名 | default+类名 | 类名
+ (instancetype)shareCustomManager;
@end
NS_ASSUME_NONNULL_END
@interface CustomManager ()<NSCopying>
@end
@implementation CustomManager
// 提供一个static修饰的全局变量,强引用着已经实例化的单例对象实例(防止外部访问)
static CustomManager *_manager;
// 类方法,返回一个单例对象
+(instancetype)shareCustomManager
{
// return _manager;
// 注意:这里建议使用self,而不是直接使用类名(考虑继承)
return [[self alloc]init];
}
// new -> alloc -> allocWithZone 最终必须调用的方法
// 保证永远只分配一次存储空间
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
// 使用GCD中的一次性代码
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_manager = [super allocWithZone:zone];
});
return _manager;
}
// 让代码更加的严谨 ,也要重写copyWithZone 和 mutableCopyWithZone
- (id)copyWithZone:(nullable NSZone *)zone
{
return [[self class] allocWithZone:zone];
return _manager;
}
- (id)mutableCopyWithZone:(nullable NSZone *)zone
{
return _manager;
}
@end
单利创建的几种方式
1、传统方法
+ (instancetype)traditionSingleton{
static CustomManager *singleton = nil;
if (singleton == nil){
singleton = [[self alloc] init];
}
return singleton;
}
2、GCD方式
+ (instancetype)gcdSingleton{
static CustomManager *singleton = nil;
//给单例加了一个线程锁
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[self alloc] init];
});
return singleton;
}
3、加锁方式
为了防止多线程同时访问对象,造成多次分配内存空间,所以要加上线程锁。
+ (instancetype)singleton{
static CustomManager *singleton = nil;
// 使用加锁的方式,保证只分配一次存储空间
if (singleton == nil) { //防止频繁加锁
@synchronized(self) {
if(singleton == nil) { //防止创建多次
singleton = [[self alloc] init];
}
}
}
return singleton;
}
单例在MRC中的实现
MRC单例实现步骤
- 在类的内部提供一个
static
修饰的全局变量 - 提供一个类方法,方便外界访问
- 重写
+allocWithZone
方法(注意线程安全),保证永远都只为单例对象分配一次内存空间 - 严谨起见,重写
-copyWithZone
方法和-MutableCopyWithZone
方法 - 重写
release
方法 - 重写
retain
方法 - 建议在
retainCount
方法中返回一个最大值
配置MRC环境
- 注意ARC不是垃圾回收机制,是编译器特性
- 配置MRC环境:build setting -> 搜索automatic ref -> 修改为
N0
MRC中单例代码实现
配置好MRC环境之后,在ARC代码基础上重写下面的三个方法即可
- (oneway void)release
{
}
- (instancetype)retain
{
return _instance;
}
// 惯用法,有经验的程序员通过打印retainCount这个值可以猜到这是一个单例
- (NSUInteger)retainCount
{
return MAXFLOAT;
}
- (instancetype)autorelease{
}
在MRC环境下,如果用户retain了一次,那么直接返回instance变量,不对引用计数器+1;
如果用户release了一次,那么什么都不做,因为单例模式在整个程序运行过程中都拥有且只有一份,程序退出之后被释放,所以不需要对引用计数器操作.
扩展
Q:new -> alloc -> allocWithZone 最终必须调用的方法
A:通过打印方式查看调用方法;
+[CustomManager new]
+[CustomManager alloc]
+[CustomManager allocWithZone:]
说明
避免使用new创建对象,从安全和设计角度来说我们应该对初始化所有属性,提高程序的健壮性和复用性。
不论是何种情况,在类中至少包含一个构造函数是一种很好的编程实践,如果类中有属性,好的实践往往是初始化这些属性。
——以上摘自《The Object-Oriented Thought Process》 by Matt Weisfeld
Q:写一份单例代码在ARC和MRC环境下都适用?
A:可以使用条件编译来判断当前项目环境是ARC还是MRC
Q:条件编译的代码呢,么么哒?
A:条件编译
宏判断是否ARC环境
在PCH文件使用代参数的宏和条件编译,利用条件编译来判断是ARC还是MRC环境。
#if __has_feature(objc_arc)
// ARC
#else
// MRC
#endif
Q:使用带参数的宏完成通用版单例模式代码
A:
- 注意条件编译的代码不能包含在宏定义里面
- 宏定义的代码只需要写一次就好,之后直接拖到项目中用就OK
copy 和 mutableCopy 区别
- mutableCopy 创建一个新的可变对象,并初始化为原对象的值,新对象的引用计数为 1;
- copy 返回一个不可变对象。分两种情况:
(1)若原对象是不可变对象,那么返回原对象,并将其引用计数加1;
(2)若原对象是可变对象,那么创建一个新的不可变对象,并初始化为原对象的值,新对象的引用计数为 1。
扩展宏
这时我们就可以一劳永逸,任何项目中,当我们要使用单例类的时候只要在项目中导入PCH文件然后
在.h文件中调用singleH(类名)
在.m文件中调用singleM(类名)
创建类时直接调用share类名方法即可。
PCH文件Single.h
#define singleH(name) +(instancetype)share##name;
#if __has_feature(objc_arc)
#define singleM(name) static id _instance;\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+(instancetype)share##name\
{\
return [[self alloc]init];\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
\
-(id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}
#else
#define singleM static id _instance;\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+(instancetype)shareTools\
{\
return [[self alloc]init];\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
-(id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
-(oneway void)release\
{\
}\
\
-(instancetype)retain\
{\
return _instance;\
}\
\
-(NSUInteger)retainCount\
{\
return MAXFLOAT;\
}
#endif
更新记录
版本 | 时间 | 说明 |
---|---|---|
1.3 | 2019.03.21 18:12 | 第三版本 |
1.2 | 2019.01.30 14:29 | 补充 |
1.1 | 2018.12.25 11:18 | 第二版本 |
1.0 | 2016.12.20 09:52 | 第一版本 |