单例的宗旨:该类在程序运行期间有且仅有一个实例。
注:在使用单例的时候要考虑好自己的需求是否真的需要,防止过度使用。
一、单例的优缺点
1.单例的优点
单例可以保证系统中该类有且仅有一个实例,所以很便于外界访问,对于项目中的个别场景的传值,存储状态等等更加方便。
2.单例的缺点
单例实例一旦创建,对象指针是保存在静态区的,那么在堆区分配空间只有在应用程序终止后才会被释放。
且 单例不能被继承。
二、单例在ARC中的实现
1. 方式一
ARC中单例实现步骤
①在类的内部提供一个static修饰的全局变量
②提供一个类方法,方便外界访问
③重写+allocWithZone方法,保证永远都只为单例对象分配一次内存空间
④严谨起见,重写-copyWithZone方法和-MutableCopyWithZone方法
ARC中单例代码实现
#import "LYYSingles.h"
//严谨起见,遵循<NSCopying,NSMutableCopying>两个协议 是为了重写copyWithZone以及MutableCopyWithZone这两个对象方法
@interface LYYSingles ()<NSCopying, NSMutableCopying>
@end
@implementation LYYSingles
// 创建静态对象 防止外部访问
static LYYSingles *_instance;
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
// @synchronized (self) {
// // 为了防止多线程同时访问对象,造成多次分配内存空间,所以要加上线程锁
// if (_instance == nil) {
// _instance = [super allocWithZone:zone];
// }
// return _instance;
// }
// 也可以使用一次性代码
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (_instance == nil) {
_instance = [super allocWithZone:zone];
}
});
return _instance;
}
// 为了使实例易于外界访问 我们一般提供一个类方法
// 类方法命名规范 share类名|default类名|类名|sharedInstance
+(instancetype)shareTools
{
//return _instance;
// 最好用self 用Tools他的子类调用时会出现错误
return [[self alloc] init];
}
// 为了严谨,也要重写copyWithZone 和 mutableCopyWithZone
-(id)copyWithZone:(NSZone *)zone
{
return _instance;
}
-(id)mutableCopyWithZone:(NSZone *)zone
{
return _instance;
}
@end
2. 方式二
上面的方法是把其可能出现的初始化方法做了相应的处理来其保证安全性。
其实我们可以在不做处理的情况下,禁止外部调用岂不是更简单。
个人感觉该方法也很不错而且一些成熟的第三方中的单例中也有使用该方法的。
①直接在你创建的单例文件的.h文件中加入代码
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (id)copy NS_UNAVAILABLE; // 没有遵循协议可以不写
- (id)mutableCopy NS_UNAVAILABLE; // 没有遵循协议可以不写
②.m文件实现一个方法即可,上面方式的其他方法就不要写了
// 跟上面的方法实现有一点不同
+ (instancetype)sharedSingleton{
static Singleton *_sharedSingleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 要使用self来调用
_sharedSingleton = [[self alloc] init];
});
return _sharedSingleton;
}
三、单例在MRC中的实现
MRC单例实现步骤
①在类的内部提供一个static修饰的全局变量
②提供一个类方法,方便外界访问
③重写+allocWithZone方法,保证永远都只为单例对象分配一次内存空间
④严谨起见,重写-copyWithZone方法和-MutableCopyWithZone方法
⑤重写release方法
⑥重写retain方法
⑦建议在retainCount方法中返回一个最大值
⑧配置MRC环境
注意:ARC不是垃圾回收机制,是编译器特性
①配置MRC环境:build setting ->搜索automatic ref->修改为N0
MRC中单例代码实现
②在.h文件中声明单例方法
#import <Foundation/Foundation.h>
@interface WGStudent : NSObject
/**
* 声明一个类方法,表明自己是一个单例
*/
+ (instancetype)shareInstance;
@end
③在.m文件中重写方法
#import "LYYSingles.h"
@interface LYYSingles()
@end
@implementation LYYSingles
#pragma mark - ARC环境下的单例
// 定义全局变量,保证整个进程运行过程中都不会释放
static LYYSingles *_instance;
// 保证整个进程运行过程中,只会分配一个内存空间
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
@synchronized(self) {
if (nil == _instance) {
_instance = [super allocWithZone:zone];
}
return _instance;
}
}
+ (instancetype)shareInstance
{
@synchronized(self) {
if (nil == _instance) {
_instance = [[self alloc] init];
}
return _instance;
}
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
return _instance;
}
#pragma mark - MRC环境下的单例(还要加上上面的方法)
#if __has_feature(objc_arc)
// ARC :就执行上面重写的方法即可
#else
// MRC : 除了执行上面的方法,还需要重写下面的方法.
- (oneway void)release
{
// 什么都不用做,安静的看着其他方法装逼即可
}
- (instancetype)retain
{
return _instance;
}
- (NSUInteger)retainCount
{
return MAXFLOAT;
}
#endif
@end
四、判断ARC或者MRC的宏
- (void)currentEnvironment
{
#if __has_feature(objc_arc)
// ARC
NSLog(@"ARC环境");
#else
// MRC
NSLog(@"MRC环境");
#endif
}
五、项目中单例只创建一次写法
在实际开发中,我们为了提高工作效率,一般不会每次需要使用单例时,都老实巴交一步一步的编写单例,我习惯将他们抽取出来,定义成一个宏,到时候使用单例时,直接调用宏,我们只需要传入一个参数。
①直接将单例的实现(ARC和MRC)全部定义到PCH文件中,,设置PCH文件路径即可
#define SingleH(instance) +(instancetype)share##instance;
#if __has_feature(objc_arc)
//ARC
#define SingleM(instance) static id _instance;\
\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+(instancetype)share##instance\
{\
return [[self alloc]init];\
}\
\
-(id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
\
-(id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}
#else
//MRC
#define SingleM(instance) static id _instance;\
\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+(instancetype)share##instance\
{\
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
②调用的时候在.h文件中直接调用宏singleH
#import <Foundation/Foundation.h>
@interface LYYHTTPSManager : NSObject
singleH(LYYHTTPSManager)
@end
③在.m文件中直接调用宏singleM
#import "LYYHTTPSManager.h"
@implementation LYYHTTPSManager
singleM(LYYHTTPSManager)
@end
注意点 :
每一行都需要''不然下一行不能识别
不要在注释后面添加''.否则后面的全部都会变成注释
在实际开发中,我们可以定义的方法不一样,我们可以使用"##"两个井号让方法变成可变的参数,我们传入什么,它就是什么.
注意定义全局变量的时候,我们定义的类是不一样的,所以我们需要将它改为id类型.
另外:单例中千万不能使用继承,原因是所有继承来的单例的内存地址都是同一个,没有开辟新的内存,所以新创建的单例对象和已经创建的单例对象指向了同一个内存地址。
参考博客:https://www.jianshu.com/p/49612e1e32b9