iOS开发教程之单例使用问题详析

  

  
  

单例(singleton),是可可的核心模式之一。在iOS上,单例十分常见,比如:UIApplication, NSFileManager等等。虽然它们用起来十分方便,但实际上它们有许多问题需要注意,所以在你下次自动补全dispatch_once代码片段的时候,想一下这样会导致什么后果。

     


  

  

在《设计模式》一书中给出了单例的定义:

  
  

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

     

单例模式提供了一个访问点,供客户类为共享资源生成唯一实例,并通过它来对共享资源进行访问,这一模式提供了灵活性。
  

  

在objective - c中,可以使用以下代码创建一个单例:
  

        + (instancetype) sharedInstance   {   静态dispatch_once_t>   @ implementation数学{   了NSUInteger _a;   了NSUInteger _b;   }      -(了NSUInteger) computeSum   {   返回_a + _b;   }      

这段代码想要计算_a和_B相加的和,并返回。但事实上这段代码存在着不少问题:

  
      <李> computeSum方法中并没有把_a和_b作为参数。相比查找接口并了解哪个变量控制方法的输出,查来找实现了解显得更隐蔽,而隐蔽代表着容易发生错误。   <李>当准备修改_a和_b的值来让它们调用computeSum方法的时候,程序员必须清楚修改它们的值不会影响其他包含着两个值的代码的正确性,而在多线程的情况下作出这样的判断显得尤其困难。   
  

对比下面这段代码:
  

        +(了NSUInteger) computeSumOf:(了NSUInteger) +:(了NSUInteger) b   {   返回一个+ b;   }      

这段代码中,a和b的从属显得十分清晰,不再需要去改变实例的状态来调用这个方法,而且不用担心调用这个方法的副作用。
  

  

那这个例子和单例又有什么关系呢?事实上,单例就是披着羊皮的全局状态。一个单例可以在任何地方被使用,而且不用清晰地声明从属。程序中的任何模块都可以简单的调用(MySingleton sharedInstance),然后拿到这个单例的访问点,这意味着任何和单例交互时产生的副作用都会有可能影响程序中随机的一段代码,如:
  

        @ interface MySingleton: NSObject      + (instancetype) sharedInstance;      ——(了NSUInteger) badMutableState;   - (void) setBadMutableState:(了NSUInteger) badMutableState;      @end      @ implementation ConsumerA      - (void) someMethod   {   如果([[MySingleton sharedInstance] badMutableState]) {//做某事…   }   }      @end      @ implementation ConsumerB      - (void) someOtherMethod   {   [[MySingleton sharedInstance] setBadMutableState: 0];   }      

在上面的代码中,ConsumerA和ComsumerB是程序中两个完全独立的模块,但是ComsumerB中的方法会影响到ComsumerA中的行为,因为这个状态的改变通过单例传递了过去。
  

  

在这段代码,正是因为单例的全局性和状态性,导致了ComsumerA和ComsumerB这两个看起来似乎毫无关系的模块之间隐含的耦合。

  


  

  

另一个单例的主要问题是它们的生命周期。
  

  

举个例子,假设一个应用中需要实现能够让用户看到他们的好友列表的功能,每一个好友有自己的头像,同时我们还希望这个应用能够下载并缓存这些好友的头像。这时候通过之前学习单例的知识,我们很可能会写出以下的代码:
  

        @ interface MyAppCache: NSObject      + (instancetype) sharedCMyAppCache;      - (void) cacheProfileImage: (NSData *) imageData forUserId: userID (NSString *);   ——(NSData *) cachedProfileImageForUserId:(NSString *)标识;      @end      

这段代码看起来完全没有问题,运行起来也很好,所以应用继续开发,直到有一天,我们决定帮应用加入“登”出的功能。突然我们发现,用户数据储存在全局单例中。当用户登出的时候,我们想要把这些数据清除掉,当新用户登入的时候,再为他创建一个新的MyAppCache。
  

  

但是问题出在了单例这里,因为单例的定义就是:“创建一次,永久存活”的实例。事实上有很多方法解决上面的问题,我们也许可以在用户登出的时候销毁这个单例:

iOS开发教程之单例使用问题详析