iOS多线程技术—单例模式(ARC)
一、简单说明:
设计模式:多年软件开发,总结出来的一套经验、方法和工具
java中有23种设计模式,在ios中最常用的是单例模式和代理模式。
二、单例模式说明
(1)单例模式的作用 :可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问,从而方便地控制了实例个数,并节约系统资源。
(2)单例模式的使用场合:在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次),应该让这个类创建出来的对象永远只有一个。
(3)单例模式在ARC\MRC环境下的写法有所不同,需要编写2套不同的代码
可以用宏判断是否为ARC环境
#if __has_feature(objc_arc) // ARC #else // MRC #endif
(4)在ARC中,单例模式的实现思路
在.m中保留一个全局的static的实例 static id _instance;
1)重写allocWithZone:方法,在这里创建唯一的实例(注意线程安全)
+ (id)allocWithZone:(struct _NSZone *)zone { @synchronized(self) { if (!_instance) { _instance = [super allocWithZone:zone]; } } return _instance; }
2)提供1个类方法让外界访问唯一的实例
+ (instancetype)sharedSoundTool { @synchronized(self) { if (!_instance) { _instance = [[self alloc] init]; } } return _instance; }
3)实现copyWithZone:方法
+ (id)copyWithZone:(struct _NSZone *)zone { return _instance; }
(5)非ARC中(MRC),单例模式的实现(比ARC多了几个步骤)
实现内存管理方法
- (id)retain { return self; }
- (NSUInteger)retainCount { return 1; }
- (oneway void)release {}
- (id)autorelease { return self; }
三、单例模式(ARC)
1.说明
重写allocWithzone:方法,控制内存分配。因为alloc内部会调用该方法,每次调用allocWithzone:方法,系统都会创建一块新的内存空间。
alloc方法中:永远只分配一次内存
init方法中:保证所有的MP3数据都只加载一次。
2.代码示例
创建一个音频工具类,继承子NSObject类。
在该类中实现以下代码,观察:
1 // 2 // YYAudioTool.m 3 // 06-单例模式1 4 // 5 // Created by apple on 14-6-25. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import "YYAudioTool.h" 10 @interface YYAudioTool () 11 //用来保存mp3文件 12 @property(nonatomic,strong)NSMutableDictionary *muscis; 13 @end 14 @implementation YYAudioTool 15 //构造方法 16 -(id)init 17 { 18 if (self=[super init]) { 19 //加载所需的音乐资源 20 //.... 21 // self.muscis=[NSMutableDictionary dictionary]; 22 // self.muscis[@"1.mp3"]=1mp3数据; 23 // self.muscis[@"2.mp3"]=2mp3数据; 24 } 25 return self; 26 } 27 28 //两个方法的调用 29 +(id)alloc 30 { 31 NSLog(@"alloc----"); 32 return [super alloc]; 33 } 34 35 //控制内存分配,每次调用allocWithzone:方法,系统都会创建一块新的内存空间 36 +(id)allocWithZone:(struct _NSZone *)zone 37 { 38 NSLog(@"allocWithZone---"); 39 return [super allocWithZone:zone]; 40 } 41 42 43 44 @end
在主控制器中,创建工具类对象:
打印结果:
说明:在alloc内部会调用更底层的方法allocWithZone方法分配内存空间,上面的代码创建了四个不同的对象。
3.单例模式:设计思路
(1)永远只分配一块内存来创建对象
(2)提供一个类方法,返回内部唯一的一个变量
(3)最好保证init方法也只初始化一次
代码示例:
创建一个音频工具类,继承子NSObject类。
在该类中按照设计思路实现以下代码:
YYAudioTool.m文件
1 // 2 // YYAudioTool.m 3 // 06-单例模式1 4 // 5 // Created by apple on 14-6-25. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import "YYAudioTool.h" 10 @interface YYAudioTool () 11 //用来保存mp3文件 12 @property(nonatomic,strong)NSMutableDictionary *muscis; 13 @end 14 15 @implementation YYAudioTool 16 //定义一份变量(整个程序运行过程中,只有一份) 17 static id _instace; 18 //单例模式:设计 19 //(1)永远只分配一块内存来创建对象 20 //(2)提供一个类方法,返回内部唯一的一个变量 21 //(3)最好保证init方法也只初始化一次 22 23 //构造方法 24 -(id)init 25 { 26 // __block id obj=nil; 27 static id obj=nil; 28 static dispatch_once_t onceToken; 29 dispatch_once(&onceToken, ^{ 30 if ((obj=[super init]) != nil) { 31 //加载所需的音乐资源 32 //.... 33 // self.muscis=[NSMutableDictionary dictionary]; 34 // self.muscis[@"1.mp3"]=1mp3数据; 35 // self.muscis[@"2.mp3"]=2mp3数据; 36 } 37 }); 38 self=obj; 39 40 return self; 41 } 42 43 44 //重写该方法,控制内存的分配,永远只分配一次存储空间 45 +(id)allocWithZone:(struct _NSZone *)zone 46 { 47 48 //里面的代码只会执行一次 49 static dispatch_once_t onceToken; 50 dispatch_once(&onceToken, ^{ 51 _instace=[super allocWithZone:zone]; 52 }); 53 return _instace; 54 } 55 56 //类方法 57 +(id)sharedAudioTool 58 { 59 //里面的代码永远都只执行一次 60 static dispatch_once_t onceToken; 61 dispatch_once(&onceToken, ^{ 62 _instace=[[self alloc]init]; 63 }); 64 return _instace; 65 } 66 67 +(id)copyWithZone:(struct _NSZone *)zone 68 { 69 return _instace; 70 } 71 @end
YYAudioTool.h文件
1 #import <Foundation/Foundation.h> 2 3 @interface YYAudioTool : NSObject 4 //提供一个类方法,返回内部唯一的一个变量 5 +(id)sharedAudioTool; 6 @end
主控制器中创建对象:
1 // 2 // YYViewController.m 3 // 06-单例模式1 4 // 5 // Created by apple on 14-6-25. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import "YYViewController.h" 10 #import "YYAudioTool.h" 11 12 @interface YYViewController () 13 14 @end 15 16 @implementation YYViewController 17 18 - (void)viewDidLoad 19 { 20 [super viewDidLoad]; 21 // YYAudioTool *tool1=[[YYAudioTool alloc]init]; 22 // YYAudioTool *tool2=[[YYAudioTool alloc]init]; 23 YYAudioTool *tool1=[YYAudioTool sharedAudioTool]; 24 YYAudioTool *tool2=[YYAudioTool sharedAudioTool]; 25 YYAudioTool *tool3=[[YYAudioTool alloc]init]; 26 YYAudioTool *tool4=[[YYAudioTool alloc]init]; 27 NSLog(@"%p--%p--%p--%p",tool1,tool2,tool3,tool4); 28 } 29 30 @end
观察打印结果:
说明:整个程序中只创建一个对象实例。
4.static补充:
注意:static id instace=nil;和static id instace;instace=nil;的区别
四、非ARC模式下的单例模式
1.说明:把一个项目修改为非ARC的
2.MAC下单例模式代码示例:
新建一个工具类,让该类继承自NSObject。
YYAudioTool.m文件
1 // 2 // YYAudioTool.m 3 // 06-单例模式1 4 // 5 // Created by apple on 14-6-25. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import "YYAudioTool.h" 10 @interface YYAudioTool () 11 //用来保存mp3文件 12 @property(nonatomic,strong)NSMutableDictionary *muscis; 13 @end 14 15 @implementation YYAudioTool 16 //定义一份变量(整个程序运行过程中,只有一份) 17 static id _instace; 18 //单例模式:设计 19 //(1)永远只分配一块内存来创建对象 20 //(2)提供一个类方法,返回内部唯一的一个变量 21 //(3)最好保证init方法也只初始化一次 22 23 //构造方法 24 -(id)init 25 { 26 // __block id obj=nil; 27 static id obj=nil; 28 static dispatch_once_t onceToken; 29 dispatch_once(&onceToken, ^{ 30 if ((obj=[super init]) != nil) { 31 32 } 33 }); 34 self=obj; 35 36 return self; 37 } 38 39 40 //重写该方法,控制内存的分配,永远只分配一次存储空间 41 +(id)allocWithZone:(struct _NSZone *)zone 42 { 43 44 //里面的代码只会执行一次 45 static dispatch_once_t onceToken; 46 dispatch_once(&onceToken, ^{ 47 _instace=[super allocWithZone:zone]; 48 }); 49 return _instace; 50 } 51 52 //类方法 53 +(id)sharedAudioTool 54 { 55 //里面的代码永远都只执行一次 56 static dispatch_once_t onceToken; 57 dispatch_once(&onceToken, ^{ 58 _instace=[[self alloc]init]; 59 }); 60 return _instace; 61 } 62 63 //重写release方法 64 //oneway :分布式对象 65 -(oneway void)release 66 { 67 } 68 69 //不管调用哪个方法,返回的都是唯一的实例,所以这里self和instace是一样的 70 -(id)autorelease 71 { 72 return self; 73 } 74 75 -(id)retain 76 { 77 return self; 78 } 79 80 -(NSUInteger)retainCount 81 { 82 return 1; 83 } 84 85 +(id)copyWithZone:(struct _NSZone *)zone 86 { 87 return _instace; 88 } 89 @end
YYAudioTool.h文件
1 // 2 // YYAudioTool.h 3 // 06-单例模式1 4 // 5 // Created by apple on 14-6-25. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import <Foundation/Foundation.h> 10 11 @interface YYAudioTool : NSObject 12 //提供一个类方法,返回内部唯一的一个变量 13 +(id)sharedAudioTool; 14 @end
主控制器中创建对象:
1 // 2 // YYViewController.m 3 // 06-单例模式1 4 // 5 // Created by apple on 14-6-25. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import "YYViewController.h" 10 #import "YYAudioTool.h" 11 12 @interface YYViewController () 13 14 @end 15 16 @implementation YYViewController 17 18 - (void)viewDidLoad 19 { 20 [super viewDidLoad]; 21 YYAudioTool *tool1=[YYAudioTool sharedAudioTool]; 22 YYAudioTool *tool2=[YYAudioTool sharedAudioTool]; 23 YYAudioTool *tool3=[YYAudioTool sharedAudioTool]; 24 YYAudioTool *tool4=[[YYAudioTool alloc]init]; 25 26 NSLog(@"%p--%p--%p--%p",tool1,tool2,tool3,tool4); 27 28 //对象创建只会,release对象会销毁,无法再创建新的对象(因为单例),所以还需要重写release方法 29 [tool1 release]; 30 [tool2 release]; 31 [tool3 release]; 32 [tool4 release]; 33 } 34 35 @end
打印结果:
说明:整个程序过程中,只创建一个对象实例。
五、把单例代码定义为一个带参数的宏
1.新的困扰
弊端:如果又创建一个新的类,是否又要把文件代码拷贝一份,所以这里可以考虑把固定的代码写成宏。
由于项目中代码经常有移植的需要,要求项目中又有ARC的,又有非ARC的,应该怎么应用单例模式?
不管项目是ARC的还是非ARC的,这个宏都有用。可以先判断编译器的环境,判断当前环境是否是ARC的。
条件编译的使用:
2.使用条件编译,并把单例模式的代码定义为宏。
新建一个.h头文件
把代码定义为宏,头文件中的代码如下:
1 // ## : 连接字符串和参数 2 #define singleton_h(name) + (instancetype)shared##name; 3 4 #if __has_feature(obj_arc) //如果是ARC 5 #define singleton_m(name) \ 6 static id _instance; \ 7 + (id)allocWithZone:(struct _NSZone *)zone \ 8 { \ 9 static dispatch_once_t onceToken; \ 10 dispatch_once(&onceToken, ^{ \ 11 _instance = [super allocWithZone:zone]; \ 12 }); \ 13 return _instance; \ 14 } \ 15 \ 16 + (instancetype)shared##name \ 17 { \ 18 static dispatch_once_t onceToken; \ 19 dispatch_once(&onceToken, ^{ \ 20 _instance = [[self alloc] init]; \ 21 }); \ 22 return _instance; \ 23 } \ 24 \ 25 + (id)copyWithZone:(struct _NSZone *)zone \ 26 { \ 27 return _instance; \ 28 } 29 30 #else //非ARC 31 #define singleton_m(name) \ 32 static id _instance; \ 33 + (id)allocWithZone:(struct _NSZone *)zone \ 34 { \ 35 static dispatch_once_t onceToken; \ 36 dispatch_once(&onceToken, ^{ \ 37 _instance = [super allocWithZone:zone]; \ 38 }); \ 39 return _instance; \ 40 } \ 41 \ 42 + (instancetype)shared##name \ 43 { \ 44 static dispatch_once_t onceToken; \ 45 dispatch_once(&onceToken, ^{ \ 46 _instance = [[self alloc] init]; \ 47 }); \ 48 return _instance; \ 49 } \ 50 \ 51 - (oneway void)release \ 52 { \ 53 \ 54 } \ 55 \ 56 - (id)autorelease \ 57 { \ 58 return _instance; \ 59 } \ 60 \ 61 - (id)retain \ 62 { \ 63 return _instance; \ 64 } \ 65 \ 66 - (NSUInteger)retainCount \ 67 { \ 68 return 1; \ 69 } \ 70 \ 71 + (id)copyWithZone:(struct _NSZone *)zone \ 72 { \ 73 return _instance; \ 74 } 75 #endif
在程序中的应用:
控制器问价代码如下:
1 #import "YYViewController.h" 2 #import "YYAudioTool.h" 3 4 @interface YYViewController () 5 6 @end 7 8 @implementation YYViewController 9 10 - (void)viewDidLoad 11 { 12 [super viewDidLoad]; 13 YYAudioTool *tool1=[YYAudioTool sharedAudioTool]; 14 15 //对象创建后,需要考虑 16 // (1)alloc init 17 // (2)release 18 // (3)copy copy内部会调用另外一个方法copywithzone 19 // (4)autorelease 20 // 21 YYAudioTool *tool2=[YYAudioTool sharedAudioTool]; 22 YYAudioTool *tool3=[YYAudioTool sharedAudioTool]; 23 NSLog(@"%p---%p---%p",tool1,tool2,tool3); 24 [tool2 release]; 25 [tool3 release]; 26 [tool1 release]; 27 28 } 29 30 @end
工具类的头文件代码如下:
1 #import "YYViewController.h" 2 #import "YYAudioTool.h" 3 4 @interface YYViewController () 5 6 @end 7 8 @implementation YYViewController 9 10 - (void)viewDidLoad 11 { 12 [super viewDidLoad]; 13 YYAudioTool *tool1=[YYAudioTool sharedAudioTool]; 14 15 //对象创建后,需要考虑 16 // (1)alloc init 17 // (2)release 18 // (3)copy copy内部会调用另外一个方法copywithzone 19 // (4)autorelease 20 // 21 YYAudioTool *tool2=[YYAudioTool sharedAudioTool]; 22 YYAudioTool *tool3=[YYAudioTool sharedAudioTool]; 23 NSLog(@"%p---%p---%p",tool1,tool2,tool3); 24 [tool2 release]; 25 [tool3 release]; 26 [tool1 release]; 27 28 } 29 30 @end
工具类的实现部分代码如下:
1 // 2 // YYAudioTool.h 3 // 06-单例模式1 4 // 5 // Created by apple on 14-6-25. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import <Foundation/Foundation.h> 10 #import "Singleton.h" 11 12 @interface YYAudioTool : NSObject 13 ////提供一个类方法,返回内部唯一的一个变量 14 //+(id)sharedAudioTool; 15 singleton_h(AudioTool) 16 @end
补充说明:如果把代码下载dispatch_once里面,那么它内部默认会进行加锁。
六、补充
问题:ARC和非ARC单例模式的区别?
由于非ARC是进行手动内存管理,所以需要注意下面一个方法,在项目中通常使用宏。
- (id)retain { return self; }
- (NSUInteger)retainCount { return 1; }
- (oneway void)release {}
- (id)autorelease { return self; }