iOS 单例类详解 (二)

2017-01-14 10:06:21来源:http://www.jianshu.com/p/7951bf58f33a作者:Rock鑫人点击

第七城市
单例一般作为:工具类
单例命名:一般情况下如果一个类是单例,那么就要提供一个类方法用于快速创建单例对象,而且这个类方法的名称是有一定的规则:share + 类名称 / default + 类名称
单例的应用十分普遍,单例模式使一个类只有一个实例,易于供外界访问,方便控制实例个数,节约系统资源.
应用程序中用到的单例 : 背景音乐,音效管理 等。

OC中的常见单例:
如:UIApplication,
NSNotificationCenter,
NSUserDefaults,
NSFIleManager。


一、ARC(自动引用计数模式)中实现单例的步骤: 创建单例的步骤:

1.定义一个全局的 静态变量 _instance,用来记录“第一次”被实例化出来的对象
2.重写allocWithZone方法,此方法是为对象分配内存空间 必须会被调用的一个方法!因此,在此方法里面使用“dispatch_once”,能够保证在多线程中,_instance也只能被“分配”一次空间. 并调用super allocWithZone。
3.定义一个sharedXXX “类” 方法,用来获取单例. 在此方法中也调用 dispatch_once, 实例化_instance 保证使用类方法调用的对象,只会被初始化一次!也方便其他使用单例的对象调用此单例.
注释:如果不考虑copy& MRC,以上三个步骤即可!(这里不知道你会不会问那什么情况下要考虑copy呢?)
----可选---------------------------------------
*4.如果要支持copy,则需要:
(1)先遵守NSCopying协议,重写copyWithZone
(2)在copyWithZone方法中,直接返回_instance即可。


tips:
一般的写法(懒汉式, 饿汉式, 加锁):
if(!_instance)_instance=[[RockShareTool alloc]init];
return_instance;
懒汉式是线程不安全的.因此实际中不这么写. 还有饿汉式,加锁等.
但是OC中有其自己的写法.需要结合其对象生命周期的一些方法来写单例.


为什么要使用dispatch_one? :

防止多线程同时进来,就相当与Java单例中的加锁机制,保证只被实例化一次. 但这里使用的不是@synchronized(){}, 是类似互斥锁的东西, 但比他的性能高.


ARC中实现单例的代码如下:
@implementation RockShareTool    
//第1步: 存储唯一实例
static RockShareTool *_instance;
//第2步: 分配内存空间时都会调用这个方法. 保证分配内存alloc时都相同.
+(id)allocWithZone:(struct _NSZone *)zone {
//调用dispatch_once保证在多线程中也只被实例化一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
//第3步: 保证init初始化时都相同
+(instancetype)sharedTool {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[RockShareTool alloc] init];
});
return _instance;
}
//第4步: 保证copy时都相同
-(id)copyWithZone:(NSZone *)zone{
return _instance;
}
@end

测试代码如下(打印单例对象的地址都相同):
-(void)viewDidLoad{
//实例化一个类的几种方法. 单例就是要保证实例化出来的类是同一个类
//1.alloc init方法. 一般不这么来调用单例.
RockShareTool *t1 = [[RockShareTool alloc] init];
RockShareTool *t2 = [[RockShareTool alloc] init];
//2.类方法
RockShareTool *t3 = [RockShareTool sharedTool];
//3.copy
RockShareTool *t4 = [t3 copy];
NSLog(@"%@ %@ %@ %@", t1, t2, t3, t4);
}

二、MRC中运用单例:


因为单例对象是用 static 标记过的, 因此存放在 静态区.
所以在MRC中不需要由程序员去管理,因此要去覆盖一些内存管理的方法.
实现部分与ARC一致,只需要覆盖一些MRC中内存管理的方法:


(id)retain. 单例中不需要增加引用计数器.returnself.
(id)autorelease. 只有堆中的对象才需要.单例中不需要.returnself.
(NSUInteger)retainCount. (可写可不写,防止引起误解).单例中不需要修改引用计数,返回最大的无符号整数即可.return UINT_MAX;
(oneway void)release.不需要release.直接覆盖,什么也不做.


操作如下:
#import "RockShareTool.h"     
@implementation RockShareTool
static RockShareTool *_instance;
+ (id)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
+ (instancetype)sharedTool {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[RockShareTool alloc] init];
});
return _instance;
}
- (id)copyWithZone:(NSZone *)zone {
return _instance;
}
//可以看出实现部分与ARC一致,下面是MRC中需要覆盖的方法
#pragma mark - MRC中需要覆盖的方法
//不需要计数器+1
- (id)retain {
return self;
}
//不需要. 堆区的对象才需要
- (id)autorelease {
return self;
}
//不需要
- (oneway void)release {
}
//不需要计数器个数. 直接返回最大无符号整数
- (NSUInteger)retainCount {
return UINT_MAX; //参照常量区字符串的retainCount
}
@end

三、ARC与MRC的整合
单例模式的作用可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问,从而方便地控制了实例个数,并节约系统资源。单例模式在ARC/MRC环境下的写法有所不同,需要编写2套不同的代码,整合是为了方便单例既能在ARC中使用,又能在MRC中使用。而不必去修改单例中的方法。具体做法是使用宏定义:(判断是否是ARC环境,是的话就省略内存管理的方法)


#if !__has_feature(objc_arc)

MRC中内存管理的方法放在这个地方


endif


代码如下:


//====ARC/MRC整合=====================
#pragma mark - MRC中需要覆盖的方法, ARC与MRC的整合
#if !__has_feature(objc_arc)
- (id)retain {
return self;
}
- (id)autorelease {
return self;
}
- (oneway void)release {
}
- (NSUInteger)retainCount {
return UINT_MAX;
}
#endif

//====iOS 实现单例写法========
static id _instance;
// alloc方法内部会调用这个方法
+ (id)allocWithZone:(struct _NSZone *)zone
{
if (_instance == nil) { // 防止频繁加锁
@synchronized(self) {
if (_instance == nil) { // 防止创建多次
_instance = [super allocWithZone:zone];
}
}
}
return _instance;
}
+ (instancetype)sharedTool
{
if (_instance == nil) { // 防止频繁加锁
@synchronized(self) {
if (_instance == nil) { // 防止创建多次
_instance = [[self alloc] init];
}
}
}
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
// 如果是非ARC环境需要加上以下代码
// 重写release对象不会被释放掉
- (oneway void)release { }
// retain直接返回对象
- (id)retain { return self; }
// 读取对象ratainCount返回1
- (NSUInteger)retainCount { return 1;}
// 执行自动释放操作返回self
- (id)autorelease { return self;}

iOS 单例模式的实现再次剖析:

首先我们要明白下面三个问题:


1.什么是单例模式

单例模式(Singleton):单例模式确保对于一个给定的类只有一个实例存在,这个实例有一个全局唯一的访问点。


2.单例模式的优点

节省内存开销:Singleton 会阻止其他对象实例化其自己的 Singleton 对象的副本,从而确保所有对象都访问唯一实例。如果有一些数据,整个程序都用得上,使用同一份资源即可。(保证大家访问的数据是相同的,一致的)
例如:[NSUserDefaults standardUserDefaults],
[UIApplication sharedApplication],
[UIScreen mainScreen],
[NSFileManager defaultManager]等,所有的这些方法都返回一个单例对象,苹果公司大量使用了此模式。


3.如何实现单例模式

一般情况下,项目中的工具类使用单例模式比较合适,工具类一般是整个项目都用的上,但是只用一个,所以没必要创建多个。
 


单例的实现应该分为两种情况:非ARC(MRC)和 ARC

3.1 MRC下单例模式的实现:
MRC情况下我们的单例模式实现如下:
需要把我们的项目配置成MRC
新建一个网络工具类
1> 在不使用单例模式的情况下打印三个实例对象,他们指向的地址是不一样的。示例代码如下:


RockworkTool *tool1 = [[DHNetworkTool alloc]init];
RockworkTool *tool2 = [[DHNetworkTool alloc]init];
RocktworkTool *tool3 = [[DHNetworkTool alloc]init];
NSLog(@"/ntool1 = %p/ntool2 = %p/ntool3 = %p",tool1,tool2,tool3);

2> 我们希望通过RockworkTool创建的对象是同一个,也就是只分配一块内存空间,那我们应该去重写alloc方法,因为alloc方法负责分配内存空间的。



+ (instancetype)alloc { }
+ (instancetype)allocWithZone:(struct _NSZone *)zone { }
现在发现有以上两个方法,那么我们应该重写哪个alloc方法呢?

我的建议是重写后者也就是+ (instancetype)allocWithZone:(struct _NSZone *)zone {}


为什么?

因为alloc内部会调用allocWithZone,也就是说allocWithZone方法更底层。也就是说我们实现alloc方法的话就只能拦截alloc方法,但是实现allocWithZone方法的话,任何内存分配的方法我们都能拦截。
Zone 的意思就是空间,当你调用这个方法的时候,系统会自动给你传递一块内存空间,Zone就是系统分配给开发者APP的内存空间。
注意:在我们实现allocWithZone能调用父类的方法吗?不能!如果调用了就相当于我们没写!!!我们的目的就是不要使用父类的,我们自己做自己的事情。



3> 我们需要创建一个全局变量为了不让别人访问,我们应该使用static修饰一下
//用static是为了不让别人访问这个变量
static RockworkTool *_RockworkTool = nil;
//alloc内部会调用allocWithZone,我们实现alloc方法的话就只能拦截alloc方法,
//但是实现allocWithZone方法的话,任何内存分配的方法我们都能拦截
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
//如果在这里调用父类的方法相当于没有重写allocWithZone方法
// return [super allocWithZone:zone];
//这样判断的话就能保证我们返回的都是_RockworkTool这个对象了
if (_RockworkTool == nil) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//保证线程安全,而且这个代码只会被执行一次
//在这里可以调用父类的方法了
_RockworkTool = [super allocWithZone:zone];
});
}
return _RockworkTool;
}


4> 我们初步实现后可以再次验证是否创建的对象是同一个 打印一下
打印结果可以验证是同一个对象,因为实例化对象的时候要调用allocWithZone方法,在该方法实例化对象的代码只会走一次,所以保证每次实例化的对象都是同一个。
5> 在MRC环境下我们这样写是不严谨的,因为还可能会调用release方法!如果调用了release方法那就意味着这个对象完了,下次在调用alloc方法的时候就没办法创建对象了,因为在allocWithZone方法中实例化对象的代码只走一次。
为了避免这种情况,我们还需要重写release方法,拦截对象被释放。重写release方法就是为了保证整个程序都有这个单例对象。
//重写release防止对象被释放,因为对象一旦被释放就再也不能生成了。


- (oneway void)release {  
}

6> 除了release方法外,我们还需要重写retain方法。为什么呢?因为我们是单例模式,这个对象只会被创建一次,那么我们就一直让他的引用计数为1,不要增加,不要让其增加。那么我们还需要再重写retainCount方法,返回1就好了。



    //使单例对象引用计数不增加
-(instancetype)retain {
return self;
}
//使单例对象引用计数一致为1
- (NSUInteger)retainCount {
return 1;
}

7> allocWithZone release retainCount三者不可缺一。
8> 单例模式已经基本实现了,最后一步就是我们应该仿照系统实现一个类方法返回我们的单例对象。


注意:如果直接返回对象的话,那么这个对象就会一直为空,所以需要在类方法里调用alloc init方法。但是每次都调用init方法的话,我们的对象每次都要被初始化,所以要重写init方法,保证这个单例对象只执行一次初始化。为什么不判断对象为空呢?因为调用init的时候要先执行alloc方法。
    + (instancetype)sharedNetworkTool {
//直接返回这个对象意味着它一直为空
// return _RockworkTool;
return [[self alloc] init];
}
//每次都调用init方法的话,我们的对象每次都要被初始化,
//所以要保证init只执行一次
-(instancetype)init {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_RockworkTool = [super init];
});
return _networkTool;
}

最后再次验证一下,打印结果如下:
    RockworkTool *tool1 = [[DHNetworkTool alloc]init];
RocktworkTool *tool2 = [[DHNetworkTool alloc]init];
RockworkTool *tool3 = [DHNetworkTool sharedNetworkTool];
NSLog(@"/ntool1 = %p/ntool2 = %p/ntool3 = %p",tool1,tool2,tool3);

3.2 ARC下单例模式的实现
ARC下单例模式的实现相对比较简单,下面就只是展示.m文件部分源码,不做赘述了。
static id _instance = nil;
+ (id)allocWithZone:(struct _NSZone *)zone {
if (_instance == nil) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ // 安全(这个代码只会被调用一次)
_instance = [super allocWithZone:zone];
});
}
return _instance;
}
- ( id )init {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super init];
});
return _instance;
}
+ (instancetype)sharedDataTool {
return [[self alloc] init];
}

最后一点,在公司项目开发过程中我们通常会把单例模式的实现抽取成一个宏,放到.PCH文件中,这样方便项目组中的每个人去使用,再次也做一下抽取和代码的展示吧,可以直接拿到工程中使用。



//实现单例设计模式
// .h文件的实现


 #define SingletonH(methodName) + (instancetype)shared##methodName;


 // .m文件的实现
#if __has_feature(objc_arc) // 是ARC
#define SingletonM(methodName) /
static id _instace = nil; /
+ (id)allocWithZone:(struct _NSZone *)zone /
{ /
if (_instace == nil) { /
static dispatch_once_t onceToken; /
dispatch_once(&onceToken, ^{ /
_instace = [super allocWithZone:zone]; /
}); /
} /
return _instace; /
} /
/
- (id)init /
{ /
static dispatch_once_t onceToken; /
dispatch_once(&onceToken, ^{ /
_instace = [super init]; /
}); /
return _instace; /
} /
/
+ (instancetype)shared##methodName /
{ /
return [[self alloc] init]; /
} /
+ (id)copyWithZone:(struct _NSZone *)zone /
{ /
return _instace; /
} /
/
+ (id)mutableCopyWithZone:(struct _NSZone *)zone /
{ /
return _instace; /
}
#else // 不是ARC
#define SingletonM(methodName) /
static id _instace = nil; /
+ (id)allocWithZone:(struct _NSZone *)zone /
{ /
if (_instace == nil) { /
static dispatch_once_t onceToken; /
dispatch_once(&onceToken, ^{ /
_instace = [super allocWithZone:zone]; /
}); /
} /
return _instace; /
} /
/
- (id)init /
{ /
static dispatch_once_t onceToken; /
dispatch_once(&onceToken, ^{ /
_instace = [super init]; /
}); /
return _instace; /
} /
/
+ (instancetype)shared##methodName /
{ /
return [[self alloc] init]; /
} /
/
- (oneway void)release /
{ /
/
} /
/
- (id)retain /
{ /
return self; /
} /
/
- (NSUInteger)retainCount /
{ /
return 1; /
} /
+ (id)copyWithZone:(struct _NSZone *)zone /
{ /
return _instace; /
} /
/
+ (id)mutableCopyWithZone:(struct _NSZone *)zone /
{ /
return _instace; /
}
#endif



第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台