iOS单例模式的实现
首先我们要明白下面三个问题:
- 什么是单例模式
- 单例模式的优点
- 如何实现单例模式
1.什么是单例模式
单例模式(Singleton):单例模式确保对于一个给定的类只有一个实例存在,这个实例有一个全局唯一的访问点。
2.单例模式的优点
- 节省内存开销:Singleton 会阻止其他对象实例化其自己的 Singleton 对象的副本,从而确保所有对象都访问唯一实例。
- 如果有一些数据,整个程序都用得上,使用同一份资源即可。(保证大家访问的数据是相同的,一致的)
例如:[NSUserDefaults standardUserDefaults],[UIApplication sharedApplication], [UIScreen mainScreen], [NSFileManager defaultManager]等,所有的这些方法都返回一个单例对象,苹果公司大量使用了此模式。
3.如何实现单例模式
一般情况下,项目中的工具类使用单例模式比较合适,工具类一般是整个项目都用的上,但是只用一个,所以没必要创建多个。
单例的实现应该分为两种情况:非ARC(MRC)和ARC
3.1MRC下单例模式的实现
MRC情况下我们的单例模式实现如下:
- 需要把我们的项目配置成MRC
- 新建一个网络工具类
1> 在不使用单例模式的情况下打印三个实例对象,他们指向的地址是不一样的。示例代码如下:
DHNetworkTool *tool1 = [[DHNetworkTool alloc]init];
DHNetworkTool *tool2 = [[DHNetworkTool alloc]init];
DHNetworkTool *tool3 = [[DHNetworkTool alloc]init];
NSLog(@" tool1 = %p tool2 = %p tool3 = %p",tool1,tool2,tool3);
2> 我们希望通过DHNetworkTool创建的对象是同一个,也就是只分配一块内存空间,那我们应该去重写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修饰一下
1 //用static是为了不让别人访问这个变量 2 static DHNetworkTool *_networkTool = nil; 3 4 //alloc内部会调用allocWithZone,我们实现alloc方法的话就只能拦截alloc方法,但是实现allocWithZone方法的话,任何内存分配的方法我们都能拦截 5 + (instancetype)allocWithZone:(struct _NSZone *)zone { 6 7 //如果在这里调用父类的方法相当于没有重写allocWithZone方法 8 // return [super allocWithZone:zone]; 9 10 //这样判断的话就能保证我们返回的都是_networkTool这个对象了 11 if (_networkTool == nil) { 12 13 static dispatch_once_t onceToken; 14 dispatch_once(&onceToken, ^{//保证线程安全,而且这个代码只会被执行一次 15 16 //在这里可以调用父类的方法了 17 _networkTool = [super allocWithZone:zone]; 18 }); 19 20 21 } 22 return _networkTool; 23 }
4> 我们初步实现后可以再次验证是否创建的对象是同一个
打印结果可以验证是同一个对象,因为实例化对象的时候要调用allocWithZone方法,在该方法实例化对象的代码只会走一次,所以保证每次实例化的对象都是同一个。
5> 在MRC环境下我们这样写是不严谨的,因为还可能会调用release方法!如果调用了release方法那就意味着这个对象完了,下次在调用alloc方法的时候就没办法创建对象了,因为在allocWithZone方法中实例化对象的代码只走一次。
为了避免这种情况,我们还需要重写release方法,拦截对象被释放。重写release方法就是为了保证整个程序都有这个单例对象。
//重写release防止对象被释放,因为对象一旦被释放就再也不能生成了。 - (oneway void)release { }
6> 除了release方法外,我们还需要重写retain方法。为什么呢?因为我们是单例模式,这个对象只会被创建一次,那么我们就一直让他的引用计数为1,不要增加,不要让其增加。那么我们还需要再重写retainCount方法,返回1就好了。
1 //使单例对象引用计数不增加 2 -(instancetype)retain { 3 4 return self; 5 } 6 7 //使单例对象引用计数一致为1 8 - (NSUInteger)retainCount { 9 10 return 1; 11 }
7> allocWithZone release retainCount三者不可缺一。
8> 单例模式已经基本实现了,最后一步就是我们应该仿照系统实现一个类方法返回我们的单例对象。
注意:如果直接返回对象的话,那么这个对象就会一直为空,所以需要在类方法里调用alloc ini方法。但是每次都调用init方法的话,我们的对象每次都要被初始化,所以要重写init方法,保证这个单例对象只执行一次初始化。为什么不判断对象为空呢?因为调用init的时候要先执行alloc方法。
1 + (instancetype)sharedNetworkTool { 2 3 //直接返回这个对象意味着它一直为空 4 // return _networkTool; 5 return [[self alloc] init]; 6 } 7 8 //每次都调用init方法的话,我们的对象每次都要被初始化,所以要保证init只执行一次 9 -(instancetype)init { 10 11 static dispatch_once_t onceToken; 12 dispatch_once(&onceToken, ^{ 13 _networkTool = [super init]; 14 }); 15 return _networkTool; 16 }
最后再次验证一下,打印结果如下:
DHNetworkTool *tool1 = [[DHNetworkTool alloc]init];
DHNetworkTool *tool2 = [[DHNetworkTool alloc]init];
DHNetworkTool *tool3 = [DHNetworkTool sharedNetworkTool];
NSLog(@" tool1 = %p tool2 = %p tool3 = %p",tool1,tool2,tool3);
3.2 ARC下单例模式的实现
ARC下单例模式的实现相对比较简单,下面就只是展示.m文件部分源码,不做赘述了。
1 static id _instance = nil; 2 3 + (id)allocWithZone:(struct _NSZone *)zone 4 { 5 if (_instance == nil) { 6 static dispatch_once_t onceToken; 7 dispatch_once(&onceToken, ^{ // 安全(这个代码只会被调用一次) 8 _instance = [super allocWithZone:zone]; 9 }); 10 } 11 return _instance; 12 } 13 14 - (id)init 15 { 16 static dispatch_once_t onceToken; 17 dispatch_once(&onceToken, ^{ 18 _instance = [super init]; 19 }); 20 return _instance; 21 } 22 23 + (instancetype)sharedDataTool 24 { 25 return [[self alloc] init]; 26 }
最后一点,在公司项目开发过程中我们通常会把单例模式的实现抽取成一个宏,放到.PCH文件中,这样方便项目组中的每个人去使用,再次也做一下抽取和代码的展示吧,可以直接拿到工程中使用。
1 //实现单例设计模式 2 3 // .h文件的实现 4 #define SingletonH(methodName) + (instancetype)shared##methodName; 5 6 // .m文件的实现 7 #if __has_feature(objc_arc) // 是ARC 8 #define SingletonM(methodName) 9 static id _instace = nil; 10 + (id)allocWithZone:(struct _NSZone *)zone 11 { 12 if (_instace == nil) { 13 static dispatch_once_t onceToken; 14 dispatch_once(&onceToken, ^{ 15 _instace = [super allocWithZone:zone]; 16 }); 17 } 18 return _instace; 19 } 20 21 - (id)init 22 { 23 static dispatch_once_t onceToken; 24 dispatch_once(&onceToken, ^{ 25 _instace = [super init]; 26 }); 27 return _instace; 28 } 29 30 + (instancetype)shared##methodName 31 { 32 return [[self alloc] init]; 33 } 34 + (id)copyWithZone:(struct _NSZone *)zone 35 { 36 return _instace; 37 } 38 39 + (id)mutableCopyWithZone:(struct _NSZone *)zone 40 { 41 return _instace; 42 } 43 44 #else // 不是ARC 45 46 #define SingletonM(methodName) 47 static id _instace = nil; 48 + (id)allocWithZone:(struct _NSZone *)zone 49 { 50 if (_instace == nil) { 51 static dispatch_once_t onceToken; 52 dispatch_once(&onceToken, ^{ 53 _instace = [super allocWithZone:zone]; 54 }); 55 } 56 return _instace; 57 } 58 59 - (id)init 60 { 61 static dispatch_once_t onceToken; 62 dispatch_once(&onceToken, ^{ 63 _instace = [super init]; 64 }); 65 return _instace; 66 } 67 68 + (instancetype)shared##methodName 69 { 70 return [[self alloc] init]; 71 } 72 73 - (oneway void)release 74 { 75 76 } 77 78 - (id)retain 79 { 80 return self; 81 } 82 83 - (NSUInteger)retainCount 84 { 85 return 1; 86 } 87 + (id)copyWithZone:(struct _NSZone *)zone 88 { 89 return _instace; 90 } 91 92 + (id)mutableCopyWithZone:(struct _NSZone *)zone 93 { 94 return _instace; 95 } 96 97 #endif
写了一个小小的Demo做简单的展示,欢迎大家一起学习交流哇。链接: http://pan.baidu.com/s/1bpHjYUF 密码: ti7y