线程安全的单例

      每个iOS或Mac OS应用都至少会有UIApplication.
      什么是单例呢?Wikipedia是如此定义的:
    在软件工程中,单例是一种用于实现单例的数学概念,即将类的实例化限制成仅一个对象的设计模式。
    或者我的理解是:
     单例是一种特殊类,该类只能实例化一个对象。
    尽管这是单例的实际定义,但在Foundation框架中不一定是这样。比如NSFileMangerNSNotificationCenter,分别通过它们的类方法defaultManagerdefaultCenter获取。尽管不是严格意义的单例,这些类方法返回一个可以在应用的所有代码中访问到的类的共享实例。

本文写出了两种线程安全的单例,一种利用GCD中得dispatch_once,另一种利用@synchronized(self){} 互斥锁;

此外封堵alloc,copy方法,致使不能通过这两种方法创建实例。重写-allocWithZone与-copyWithZone来分别实现alloc,copy方法的封堵;

 1 #import "YTSingleton.h"
 2 static YTSingleton *s = nil;
 3 @implementation YTSingleton
 4 //线程不安全的单例方法实现
 5 + (instancetype)sharedSingletonNotSafe
 6 {
 7     if (s == nil) {
 8         s = [[self alloc]init];
 9     }
10     return s;
11 }
12 //线程安全的单例方法
13 + (instancetype)sharedSingleton
14 {
15     static dispatch_once_t onceToken;
16     dispatch_once(&onceToken, ^{
17         s = [[self alloc]init];
18     });
19     return s;
20 }
21 
22 //线程安全的单例方法
23 + (instancetype)sharedSingleton2
24 {
25     @synchronized(self){
26         if (s == nil) {
27             s = [[self alloc]init];
28         }
29     }
30     return s;
31 }
32 
33 //当alloc当前类的对象时自动调用的方法
34 + (instancetype)allocWithZone:(struct _NSZone *)zone
35 {
36     if (s == nil) {
37         s = [super allocWithZone:zone];
38         return s;
39     }
40     return nil;
41 }
42 
43 - (id)copyWithZone:(NSZone *)zone
44 {
45     return self;
46 }
47 @end
YTSingleton.m

 1.  dispatch_once

void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);
    该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用)。它还接收一个希望在应用的生命周期内仅被调度一次的代码块,对于本例就用于shared实例的实例化。
dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题(下面会提到该问题)。
    Apple的GCD Documentation证实了这一点:
如果被多个线程调用,该函数会同步等等直至代码块完成。
    该方法有很多优势: 
           1 线程安全
           2 很好满足静态分析器要求
           3 和自动引用计数(ARC)兼容 
           4 仅需要少量代码
2.  @synchronized(self){} 

        1、synchronized关键字的作用域有二种:

            1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要          一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的 synchronized方法是          不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;

            2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作            用。

         2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是:                                  synchronized(this){},它的作用域是当前对象;(显然本文中用到的时这种用法)

3.  allocWithZone

     首先我们应该知道 [[Class alloc] init],其实是做了两件事。 alloc 给对象分配内存空间,init是对对象的初始化,包括设置成员变量初值这些工作。而给对象分配空间,除了alloc方法之外,还有另一个方法: allocWithZone.在NSObject 这个类的官方文档里面,allocWithZone方法介绍说,该方法的参数是被忽略的,正确的做法是传nil或者NULL参数给它。而这个方法之所以存在,是历史遗留原因。Do not override allocWithZone: to include any initialization code. Instead, class-specific versions of init… methods.This method exists for historical reasons; memory zones are no longer used by Objective-C.

      文档里面提到,memory zone已经被弃用了,只是历史原因才保留这个接口。详细是什么历史原因我没找到,不过后面介绍的内容会稍微涉及到。而实践证明,使用alloc方法初始化一个类的实例的时候,默认是调用了 allocWithZone 的方法。于是覆盖allocWithZone方法的原因已经很明显了:为了保持单例类实例的唯一性,需要覆盖所有会生成新的实例的方法,如果有人初始化这个单例类的时候不走[[Class alloc] init] ,而是直接 allocWithZone, 那么这个单例就不再是单例了,所以必须把这个方法也堵上。allocWithZone的答案到此算是解决了,但是,问题是无止境的。

4.  copyWithZone

      -copy,此方法存在的意义只是为了方便,里面会直接把-copyWithZone的方法返回。但是NSObject并没有实现-copyWithZone,需要子对象去实现NSCopying协议(即实现-copyWithZone方法)。

  最后NSZone这玩艺儿已经被Apple抛弃,所以NSObject的copy方法中只是传入NULL。

 

posted @ 2015-07-16 13:54  chrisyu-chn  阅读(289)  评论(0编辑  收藏  举报