iOS中的单例你用对了么?

Gary2192 8年前
   <p style="text-align:center"><img src="https://simg.open-open.com/show/8b05fbaaad22067cba8faa475d2dc60d.jpg"></p>    <p>单例模式怎么定义的,可能在不同的语言,不同的书中不完全一样,但是概况开来都应该是:一个类有且仅有一个实例,并且自行实例化向整个系统提供。</p>    <p>因此,首先你可能需要确定你是真的需要一个单例类,还是说仅仅是需要一个方便调用的实例化方法。如果你是真的需要一个单例类,那么你就应该确保这个单例类,有且仅有一个实例(不管怎么操作都只能获取到这个实例)。</p>    <p>最近看到一些github上的单例使用,别人的用法,有一些思考,然后写demo测试了下,就这个简单的单例也有一些坑呢,希望能给他人一些提醒。</p>    <h2>Objective-C中的单例</h2>    <p>我们通常在OC中实现一个单例方法都是这样:</p>    <pre>  <code class="language-objectivec">static HLTestObject *instance = nil;  + (instancetype)sharedInstance  {      static dispatch_once_t onceToken;      dispatch_once(&onceToken, ^{          instance = [[[self class] alloc] init];      });      return instance;  }</code></pre>    <p>可是这样就可以了么?我做了如下测试:</p>    <pre>  <code class="language-objectivec">HLTestObject *objct1 = [HLTestObject sharedInstance];  NSLog(@"%@",objct1);  HLTestObject *objc2 = [[HLTestObject alloc] init];  NSLog(@"%@",objc2);  HLTestObject *objc3 = [HLTestObject new];  NSLog(@"%@",objc3);</code></pre>    <p>看到这个测试,你想到打印结果了么?结果是这样的:</p>    <pre>  <code class="language-objectivec">2016-05-23 12:52:57.095 PractiseProject[3579:81998]                          2016-05-23 12:52:57.095 PractiseProject[3579:81998]                     2016-05-23 12:52:57.095 PractiseProject[3579:81998]                       </code></pre>    <p>很明显,通过三种方式创建出来的是不同的实例对象,这就违背了单例类有且仅有一个实例的定义。</p>    <p>为了防止别人不小心利用alloc/init方式创建示例,也为了防止别人故意为之,我们要保证不管用什么方式创建都只能是同一个实例对象,这就得重写另一个方法,实现如下:</p>    <pre>  <code class="language-objectivec">+ (instancetype)allocWithZone:(struct _NSZone *)zone  {      static dispatch_once_t onceToken;      dispatch_once(&onceToken, ^{          instance = [super allocWithZone:zone];      });      return instance;  }</code></pre>    <p>再次用上面的测试代码,结果是这样的:</p>    <pre>  <code class="language-objectivec">2016-05-23 12:57:37.396 PractiseProject[3618:83975]                          2016-05-23 12:57:37.396 PractiseProject[3618:83975]                     2016-05-23 12:57:37.396 PractiseProject[3618:83975]                       </code></pre>    <p>好像用不同的构造方法,获取的都是同一个对象,你以为这样就完了?还早着呢!</p>    <p>一般我们的类里肯定都会有一些属性,然后我就添加了两个property:</p>    <pre>  <code class="language-objectivec">@property (assign, nonatomic)   int  height;  @property (strong, nonatomic)   NSObject  *object;  @property (strong, nonatomic)   NSMutableArray  *arrayM;</code></pre>    <p>而一些对象类的初始化,或者基础类型的默认值设置都是在init方法里,就像这样:</p>    <pre>  <code class="language-objectivec">- (instancetype)init  {      self = [super init];      if (self) {          _height = 10;          _object = [[NSObject alloc] init];          _arrayM = [[NSMutableArray alloc] init];      }      return self;  }</code></pre>    <p>我重写了HLTestObject类的description方法:</p>    <pre>  <code class="language-objectivec">- (NSString *)description  {      NSString *result = @"";      result = [result stringByAppendingFormat:@"<%@: %p>",[self class], self];      result = [result stringByAppendingFormat:@" height = %d,",self.height];      result = [result stringByAppendingFormat:@" arrayM = %p,",self.arrayM];      result = [result stringByAppendingFormat:@" object = %p,",self.object];      return result;  }</code></pre>    <p>还是用上面的测试代码,测试结果是这样的:</p>    <pre>  <code class="language-objectivec">2016-05-23 13:14:43.684 PractiseProject[3781:92758]                         height = 20, arrayM = 0x7f8a5b422940, object = 0x7f8a5b4544e0,  2016-05-23 13:14:43.684 PractiseProject[3781:92758]                     height = 10, arrayM = 0x7f8a5b4552e0, object = 0x7f8a5b45a710, 2016-05-23 13:14:43.684 PractiseProject[3781:92758]               height = 10, arrayM = 0x7f8a5b459770, object = 0x7f8a5b4544e0,                       </code></pre>    <p>可以看到,尽管使用的是同一个示例,可是他们的property值却不一样。</p>    <p>因为尽管没有为示例重新分配内存空间,但是因为又执行了init方法,会导致property被重新初始化。</p>    <p>所以我们需要修改单例的实现。</p>    <p>第一种:</p>    <p>可以将property的初始化或者默认值设置放到dispatch_once 的block内部:</p>    <pre>  <code class="language-objectivec">static HLTestObject *instance = nil;  + (instancetype)sharedInstance  {      static dispatch_once_t onceToken;      dispatch_once(&onceToken, ^{          instance = [[[self class] alloc] init];          instance.height = 10;          instance.object = [[NSObject alloc] init];          instance.arrayM = [[NSMutableArray alloc] init];      });      return instance;  }  + (instancetype)allocWithZone:(struct _NSZone *)zone  {      static dispatch_once_t onceToken;      dispatch_once(&onceToken, ^{          instance = [super allocWithZone:zone];      });      return instance;  }  - (NSString *)description  {      NSString *result = @"";      result = [result stringByAppendingFormat:@"<%@: %p>",[self class], self];      result = [result stringByAppendingFormat:@" height = %d,",self.height];      result = [result stringByAppendingFormat:@" arrayM = %p,",self.arrayM];      result = [result stringByAppendingFormat:@" object = %p,",self.object];      return result;  }</code></pre>    <p>来看看测试结果:</p>    <pre>  <code class="language-objectivec">2016-05-23 13:29:14.856 PractiseProject[3909:99058]                         height = 20, arrayM = 0x7fa722716c10, object = 0x7fa7227140e0,  2016-05-23 13:29:14.856 PractiseProject[3909:99058]                     height = 20, arrayM = 0x7fa722716c10, object = 0x7fa7227140e0, 2016-05-23 13:29:14.856 PractiseProject[3909:99058]               height = 20, arrayM = 0x7fa722716c10, object = 0x7fa7227140e0,                       </code></pre>    <p>第二种:</p>    <pre>  <code class="language-objectivec">static HLTestObject *instance = nil;  + (instancetype)sharedInstance  {      return [[self alloc] init];  }  - (instancetype)init  {      static dispatch_once_t onceToken;      dispatch_once(&onceToken, ^{          instance = [super init];          instance.height = 10;          instance.object = [[NSObject alloc] init];          instance.arrayM = [[NSMutableArray alloc] init];      });      return instance;  }  + (instancetype)allocWithZone:(struct _NSZone *)zone  {      static dispatch_once_t onceToken;      dispatch_once(&onceToken, ^{          instance = [super allocWithZone:zone];      });      return instance;  }  - (NSString *)description  {      NSString *result = @"";      result = [result stringByAppendingFormat:@"<%@: %p>",[self class], self];      result = [result stringByAppendingFormat:@" height = %d,",self.height];      result = [result stringByAppendingFormat:@" arrayM = %p,",self.arrayM];      result = [result stringByAppendingFormat:@" object = %p,",self.object];      return result;  }</code></pre>    <p>测试结果:</p>    <pre>  <code class="language-objectivec">2016-05-23 13:31:44.824 PractiseProject[3939:100662]                         height = 20, arrayM = 0x7fa9da707ca0, object = 0x7fa9da70a940,  2016-05-23 13:31:44.825 PractiseProject[3939:100662]                     height = 20, arrayM = 0x7fa9da707ca0, object = 0x7fa9da70a940, 2016-05-23 13:31:44.825 PractiseProject[3939:100662]               height = 20, arrayM = 0x7fa9da707ca0, object = 0x7fa9da70a940,                       </code></pre>    <p>注意:</p>    <p>以上代码均是使用ARC的方式管理内存,如果你还在使用MRC(这也太不与时俱进了)。那你还需要重写 retain 和release方法,防止示例引用计数的改变。</p>    <p>Swift中的单例</p>    <p>利用Swift中的一些特性,Swift中的单例可以超级简单,like this:</p>    <pre>  <code class="language-objectivec">class HLTestObject: NSObject {      static let sharedInstance = HLTestObject();  }</code></pre>    <p>可是这样就完了么?同样写一段测试代码:</p>    <pre>  <code class="language-objectivec">let object1 = HLTestObject.sharedInstance;  print(object1);  let object2 = HLTestObject();  print(object2);</code></pre>    <p>打印结果却是这样的:</p>    <p>所以,我们必须禁用到构造方法:</p>    <pre>  <code class="language-objectivec">class HLTestObject: NSObject {      static let sharedInstance = HLTestObject();      private override init() {      }  }</code></pre>    <p>如果有实例属性需要初始化,就可以这样:</p>    <pre>  <code class="language-objectivec">class HLTestObject: NSObject {      var height = 10;      var arrayM: NSMutableArray      var object: NSObject      static let sharedInstance = HLTestObject();      private override init() {          object = NSObject()          arrayM = NSMutableArray()          super.init()      }  }</code></pre>    <p>当然,由于Swift的特性,在Swift中创建单例的方式也不止一种,需要注意的是要确保该类有且仅有一个实例就OK了。</p>    <p>Have Fun!</p>    <p> </p>    <p><a href="/misc/goto?guid=4959675290357701548">阅读原文</a></p>    <p> </p>