iOS 中单例的使用详解

        iOS中有很多的设计模式,当然任何一门语言都有很多的设计模式,单例是其中一种,单例,字面上理解来说就是单独的实例,首先它是单独唯一的,其次它是一个实例。我们知道在iOS开发中使用的Objective—C语言是面向对象语言,我们说的实例通常就是指我们创建的对象。而用来创建单例(唯一实例)的类就是单例类,这一点不难理解。

        单例:

无论一个应用程序请求多少次,单例类都始终返回的是同一个实例对象。一个典型的类在用户需要的情况下会创建很多个对象,而一个单例类在一个应用程序当中只会创建唯一的一个实例。一个单例对象提供一个资源点供全局访问。单例通常在单点控制值得使用的时候使用,比如一个类要提供一些通用的服务和资源。

        单例类:

        我们通常通过一个工厂方法从单例类中获取一个全局的实例变量。当第一次被请求的时候,这个类会懒加载它的唯一实例(单例),以此确保此后没有其它实例被创建。一个单例类同时也阻止用户copy、retain或者release这个实例。如果我们需要,我们可以创建我们自己的单例类(系统有很多单例类,下面补充说明)。比如我们需要一个类为其它应用程序中的对象提供声音的时候,我们可以创建一个单例。

       补充:

       有几个Cocoa框架的类是单例类,包括NSFileManager、NSWorkSpace,并且在UIKit当中,UIApplication,UIAccelerometer都是单例类。按照惯例,工厂方法返回单一实例的名称格式,形式是shareClassType(share + 类型名)。就拿Cocoa框架举例来说,工厂方法都称之为“sharedFileManager、sharedColorPanel和sharedWorkspace”。

       图示:

       左图表示普通类创建对象,当外界需要的时候创建一次对象,但是每次创建的对象都不一样。右图表示单例类创建的对象,无论外界请求多少,返回的对象都是唯一的,这样在实际开发当中,我们就可以通过单例来给外界提供共有的资源和服务来使用,比如多个页面需要同一个数据,就可以在多个页面访问唯一的单例。

       

 

       准备:

       在学习单例之前,除了OC基本的语法之外,你需要这些知识储备:

       一、必备知识

       1、创建一个对象的基本形式。

       2、内存管理的基本知识。

       3、工厂方法的了解。

      二、相关知识:

       1、对象的拷贝

       2、内存管理高级。

       3、多线程知识。

       如果你在这些方面有一些欠缺,可能不会直接导致你理解不了单例设计模式,但是会增加难度,所以我还是希望我们有一定的学习基础再去涉及设计模式的知识,当然大家完全可以通过搜索或者查阅资料做一些先决的知识储备。


        单例的基本知识我们已经介绍完了,现在我们开始去学习如何创建一个单例。我们先来创建一个最简单的单例,也就是“伪单例”,是我们常用的方式,然后再一步一步健全它。

        第一步:我们创建一个空工程(EmptyApplication),并且在工程中搜索,gar,启用MRC(方便我们研究和学习一个完整的单例)。

             

       

        第二步:我们创建一个单例类(WDSingletonManager,当然名字自定义,你可以起名为“A”,只要你开心,别忘了继承自NSObject),并且分别在.h、.m中书写以下代码。

        接口部分(.h中)代码:

        

[objc]  view plain  copy
  1. #import <Foundation/Foundation.h>  
  2.   
  3. @interface WDSingletonManager : NSObject  
  4.   
  5. //去掉前缀,share + 类名  
  6. + (instancetype)shareSingletonManager;  
  7.   
  8. //当然你的类名如果是 XX + DataHandle(XX,代表开发者自定义的前缀,一般是你称呼的首字母组合或者团队的缩写),那么你的方法名按照苹果推荐的命名规范应该如下书写(只是建议,对程序本身无影响):  
  9. //+ (instancetype)shareDataHandle;  
  10.   
  11.   
  12. @end  

        实现部分(.m中)代码:

         

[objc]  view plain  copy
  1. @implementation WDSingletonManager  
  2.   
  3. //实现创建单例的工厂方法  
  4. + (instancetype)shareSingletonManager{  
  5.   
  6.     //声明一个静态指针变量用于保持指针指向的对象的唯一,static这一行代码在整个工程中只执行一次。  
  7.     static WDSingletonManager *singletonManager = nil;  
  8.     //懒创建方式,如果没有才创建,有的话直接返回,if的分支语句只有外界第一次调用的时候才执行。  
  9.     if (nil == singletonManager) {  
  10.         singletonManager = [[self alloc]init];  
  11.     }  
  12.     return singletonManager;  
  13. }  
  14.   
  15.   
  16. @end  

          好了,至此,一个伪单例类就创建完成了,这样我们每次调用的时候,看一下对象是否唯一呢?一起来验证一下。

         


        验证发现,结果是我们想要的,也就是说我们无论创建多少个对象,实际上都是同一个实例(因为始终在堆区始终是同一块内存空间),当然,在实际开发中,你也可以给这个单例加一个属性或者方法,达到每个页面或者每个页面的每个地方都可以访问或者修改的目的。这就是我们基本的一个单例,当然这也是不完整的。不完整在哪里,我们一起来思考一下。通常创建一个对象都是可以使用动静态方法结合来创建的,比如id obj = [[NSObject alloc]init];//或者 id _obj = [NSObject new];,那么我们在使用单例的时候没有谁规定一定使用我们shareClassType类似的方法去创建,所以为了保证对象的唯一性,我们需要在使用alloc + init的方法的时候也需要保证创建的对象唯一性。那怎么办到呢?其实我们每次在调用alloc方法的时候,系统都会去调用一个allocWithZone的方法,所以我们需要重写此方法。那么单例是唯一的,我们在程序中多个页面都需要使用,那么其中的一个页面或者一个地方万一release掉呢?retain呢?这时候我们的单例都无法保证它的特性。所以,我们要把内存管理的问题综合考虑进去,我们需要完善一下。

第三步:完善步骤(在我们的.m中书写)。

        注:在静态方法(类方法)中,self代表的的是类,在动态方法中(对象方法,也就是俗称的“减号方法”),self代表的是当前的对象,因为单例类的唯一性,所以我们在调用静态方法的时候,返回self和返回我们自己的静态变量实质是一样的。

        

[objc]  view plain  copy
  1. #import "WDSingletonManager.h"  
  2. @implementation WDSingletonManager  
  3. //声明一个静态指针变量用于保持指针指向的对象的唯一,static这一行代码在整个工程中只执行一次。  
  4. static WDSingletonManager *singletonManager = nil;  
  5.   
  6. //实现创建单例的工厂方法  
  7. + (instancetype)shareSingletonManager{  
  8.       
  9.     //懒创建方式,如果没有才创建,有的话直接返回,if的分支语句只有外界第一次调用的时候才执行。  
  10.     if (nil == singletonManager) {  
  11.         singletonManager = [[self alloc]init];  
  12.     }  
  13.     return singletonManager;  
  14. }  
  15.   
  16. + (instancetype)allocWithZone:(struct _NSZone *)zone{  
  17.       
  18.     if (!singletonManager) {  
  19.         //记住这里不是self,而是super,因为self调用alloc或者allocWithZone都会导致递归,用super可以有效的避免  
  20.         singletonManager = [super allocWithZone:zone];  
  21.     }  
  22.     return singletonManager;  
  23. }  
  24.   
  25. //重写init方法,保证初始化返回的对象唯一。  
  26. - (instancetype)init{  
  27.       
  28.     if (!self) {  
  29.         self = [super init];  
  30.     }  
  31.     return self;  
  32. }  
  33.   
  34. //直接返回自己,不让内部做任何操作,确保对象唯一  
  35. - (instancetype)retain{  
  36.   
  37.     return self;  
  38. }  
  39. //直接返回自己,不让内部做任何操作,确保对象唯一  
  40. - (id)copy{  
  41.   
  42.     return self;  
  43. }  
  44. //直接返回自己,不让内部做任何操作,确保对象唯一,不过需要先遵守NSCopying协议  
  45. - (id)copyWithZone:(NSZone *)zone{  
  46.       
  47.     return self;  
  48. }  
  49. //不让释放,重写方法,不做任何操作即可。(one way代表此操作无法“回滚”,即不能撤销)  
  50. - (oneway void)release{  
  51.       
  52. }  
  53. //区别于release,autorelease是有返回值的,我们只需要返回自己(self)或者返回我们的单例(singletonManager)就可以  
  54. - (instancetype)autorelease{  
  55.       
  56.     return self;  
  57.       
  58. }  
  59. //以下方法可不写,当然包括dealloc方法也不需要重写  
  60.   
  61. //不让外界访问我们的引用计数(模仿系统的类簇比如NSString的retainCount),给出一个不可能的值(无实际意义,装逼用)  
  62. - (NSUInteger)retainCount{  
  63.   
  64.     return NSIntegerMax;//或者-1,也可以。  
  65. }  
  66.   
  67. @end  

       补充:很多人告诉大家,关于整个单例类的设计中,init的相关方法可以不写,因为alloc已经分配了空间,这是错误的结论。我希望大家对此有一个认知。alloc,是静态的空间分配,init是动态的初始化过程。一般情况下他们返回的地址是一样的,但是也会有例外。所以我们无法保证。下面这段代码(大家拷贝到自己的工程去验证)就告诉大家不要轻易的相信alloc和init返回的对象地址是一致的,当然这只是个例,不过我还是希望大家注意。

       
[objc]  view plain  copy
  1. NSMutableArray *array = [NSMutableArray alloc];  
  2. NSLog(@"%p",array);  
  3. array = [array init];  
  4. NSLog(@"%p",array);  

       较完善的单例创建就已经OK了,这是我们目前在MRC情况下考虑比较全面的情况,也是依照苹果官方的建议书写的单例。但是随着我们学习知识的全面性,依然有一些问题暴露出来了,比如多线程,那么,多线程会出现什么问题呢?当两个线程同时访问一个单例,这时候如果单例为nil,那么他们同时去创建单例,又两个线程同时创建单例对象,显然又不符合我们的要求。那么我们需要怎么做呢?我们需要进行多线程情况下的保护操作,只需要给下面这三个方法加锁就可以了。    


第四步:多线程问题

     

[objc]  view plain  copy
  1. + (instancetype)shareSingletonManager{  
  2.       
  3.     //确保每次只有一个线程可以访问  
  4.     @synchronized(self){  
  5.           
  6.         if (nil == singletonManager) {  
  7.             singletonManager = [[WDSingletonManager alloc]init];  
  8.         }  
  9.           
  10.     }  
  11.       
  12.     return singletonManager;  
  13. }  
  14.   
  15. + (instancetype)allocWithZone:(struct _NSZone *)zone{  
  16.       
  17.     @synchronized(self){  
  18.           
  19.         if (nil == singletonManager) {  
  20.             singletonManager = [super allocWithZone:zone];  
  21.         }  
  22.           
  23.     }  
  24.     return singletonManager;  
  25. }  
  26. - (instancetype)init{  
  27.       
  28.     @synchronized(self){  
  29.           
  30.         if (!self) {  
  31.         self = [super init];  
  32.         }  
  33.     }  
  34.     return self;  
  35. }  

        当然,这还不是唯一的,因为我们知道多线程也可以创建单例,也省去了我们考虑多线程在内的因素。就是用下面的方法去创建单例(替换掉第一个加号方法就可以),可以有效的避免,至于涉及到的多线程知识,那就是dispatch_once了,我们知道dispatch_once是在整个程序的生命周期中只运行一次,所以有效的避免了各种问题,包括多线程访问的问题。       

[objc]  view plain  copy
  1. + (instancetype)shareSingletonManager{  
  2.   
  3.     static dispatch_once_t onceToken;  
  4.     dispatch_once(&onceToken, ^{  
  5.         singletonManager = [[self alloc]init];  
  6.     });  
  7.     return singletonManager;  
  8. }  

         好了,我知道赘述到这里,相信大家已经对单例有了一个详细的了解了,希望可以帮到大家。当然,实际开发中我们可能只需要一个伪单例,或者是在ARC的情况下去处理一些问题,有时候不会碰到这么多的问题。所以我们只是做一个详细的解释,供大家参考。不要一味的追寻完整性,使代码冗余,高效的代码才是我们追求的。当然,单例是一种很好的设计模式,做解析数据的公共数据源,界面传值等等都是很好的方案。不过也不要乱用,因为一旦创建了单例,只有程序被kill掉单例才会消失,所以对于内存精确控制的学生,一定要不要滥用单例。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值