在讲到GCD的其他用法的时候,其中有一个用法是让代码在程序运行期间只执行一次。这种功能可以帮助我们实现iOS开发中的一种设计模式:单例模式。
单例模式
一个类在整个程序运行过程中只有一个实例。
单例模式的作用
可以保证在程序运行过程中,一个类只有一个实例,而且该实例易于供外界访问。从而方便第控制了实例个数,并节约系统资源。
单例模式使用场合
在整个应用程序中,共享一份资源(这份资源只需要创建初始化一次)
单例模式在ARC和非ARC环境下得写法有所不同,需要编写两套不同的代码。如果将单例写成宏的形式,可以使用__has_feature(objc_arc)宏判断是否为ARC环境。
#if __has_feature(objc_arc)
// 是ARC
#else
// 非ARC
#endif
ARC中的单例实现
1. 在.m文件中保留一个全局的static的实例
static id _instance
2. 重写allocWithZone:方法(alloc方法内部会自动执行该方法,在该方法中分配唯一内存),在这里创建唯一的实例(注意线程安全)
+ (id)allocWithZone:(struct _NSZone *)zone
{
// 里面的代码永远只执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
// 返回对象
return _instance;
}
或者:
+ (id)allocWithZone:(struct _NSZone *)zone
{
// 里面的代码永远只执行1次
@synchronized(self) {// 加锁,防止别的线程同时访问
if (!_instance) {
_instance = [super allocWithZone:zone];
}
}
// 返回对象
return _instance;
}
3. 提供一个类方法让外界获得唯一的实例
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
})
return _instance;
}
或者:
+ (instancetype)sharedInstance
{
@synchronized(self) {
if (!_instance) {
_instance = [[self alloc] init];
}
}
return _instance;
}
4. 再实现copy方法(copy方法内部会自动执行copyWithZone:方法,因为实例就一个只要返回自身即可)
+ (id)copy
{
return _instance;
}
非ARC中的单例实现
在非ARC中(MRC),单例模式的实现除了实现上面的代码之外,还要比ARC多出几个步骤。
在非ARC环境下,需要我们自己管理内存,需要实现以下内存管理方法:
- (id)retain {
return self;// 或return _instance
}
- (NSUInteger)retainCount {
return 1;
}
- (oneway void)release {
}
- (id)autorelease {
return self;// 或return _instance
}
下面我们就用一个例子来实现一下载ARC和非ARC下的单例
场景:现在在项目中有10个控制器页面需要播放同一段音乐。要是每个控制器器里面都创建一个新的对象来播放该音乐,就显得太繁琐,内存占用也大。那么这个时候就可以使用单例模式来解决这个问题。
创建一个HXMusicTool类(整个应用程序中只有一个实例对象),该类的作用是播放一段音乐。( ARC)2. 在HXMusicTool.m文件中实现和重写以下方法
#import "HXMusicTool.h"
@implementation HXMusicTool
// 声明一个全局的实例变量
static id _instance;
+ (instancetype)shareMusicTool {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
/**
* 重写allocWithZone:方法(alloc方法中会自动调用该方法)来分配唯一的内存空间
*/
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
// block中的代码只会执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
// 返回分配内存后的对象
return _instance;
}
/**
* 重写copy方法,保证copy出来的对象也是实例对象
*/
+ (id)copy
{
return _instance;
}
@end
3. 在HXMusicTool.h文件中提供shareMusicTool外部接口,供外界访问实例对象
#import <Foundation/Foundation.h>
@interface HXMusicTool : NSObject
// 外部接口,供外界访问实例变量
+ (instancetype)shareMusicTool;
@end
4. 在ViewController中使用HXMusicTool类
#import "ViewController.h"
#import "HXMusicTool.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// tool1,tool2通过alloc创建
HXMusicTool *tool1 = [[HXMusicTool alloc] init];
HXMusicTool *tool2 = [[HXMusicTool alloc] init];
// tool3,tool4通过提供的外部接口创建
HXMusicTool *tool3 = [HXMusicTool shareMusicTool];
HXMusicTool *tool4 = [HXMusicTool shareMusicTool];
HXMusicTool *tool5 = [HXMusicTool copy];
// 打印四个对象的内存地址
NSLog(@"\n1 = %p\n2 = %p\n3 = %p\n4 = %p\n5 = %p", tool1, tool2, tool3, tool4, tool5);
}
@end
打印结果:
相同的内存地址。说明四个对象是一个实例。这就做到了在整个应用程序中,不管使用什么方法来创建对象都是同一个实例对象。
在非ARC环境下实现上面的需求,只需要在HXMusicTool.m文件中再实现以下方法即可
- (id)retain {
return self;// 或return _instance
}
- (NSUInteger)retainCount {
return 1;
}
- (oneway void)release {
}
- (id)autorelease {
return self;// 或return _instance
}
这就保证了在程序运行期间,实例对象一旦创建就会一直存在,且唯一。
现在项目的开发大部分都在ARC和非ARC混合环境下进行的,那么单例模式如何同时适用ARC和非ARC环境。
下面就提供一种解决方案:在实现单例模式的代码写成宏,再使用宏判断当前是否是ARC环境,并在不同环境下实现不同的代码。
以下是同时适用ARC和非ARC的单例模式(宏)
// ## : 连接字符串和参数
#define singleton_h(name) + (instancetype)shared##name;
#if __has_feature(objc_arc) // ARC
#define singleton_m(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:(struct _NSZone *)zone \
{ \
return _instance; \
}
#else // 非ARC
#define singleton_m(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; \
} \
\
- (oneway void)release \
{ \
\
} \
\
- (id)autorelease \
{ \
return _instance; \
} \
\
- (id)retain \
{ \
return _instance; \
} \
\
- (NSUInteger)retainCount \
{ \
return 1; \
} \
\
+ (id)copyWithZone:(struct _NSZone *)zone \
{ \
return _instance; \
}
#endif
注意:后面添加\是说明下一行也是宏的一部分
这就是iOS开发单例设计模式的实现,其实IOS开发中的设计模式还有很多,其他的设计模式以后会慢慢讲到。