[OC] OC中的单例

1,125 阅读4分钟

1: 单例的定义

在程序运行的整个过程中,根据类创建的所有的对象以及 copy 的对象,其内存地址都是一样的,也就是说,只要是单例,那么系统就只会创建一次, 只会给其分配一次存储空间;

2: 单例的作用

根据单例模式的定义,我们知道一般两种情况下使用单例:

  • 系统中某种对象只能存在一个,多了就会出问题

  • 系统中某种对象实例只需要一个就够用了,多了占内存

对于第一种情况,我们必须使用单例,对于第二种情况,我们虽然可以不用单例,但是单例是更优的选择

3: 单例的使用场景

单例在整个项目过程中,共享一份资源 系统API提供的的单例有

 [NSUserDefaults standardUserDefaults];
 [NSNotificationCenter defaultCenter];
 [UIApplication sharedApplication];
 [NSFileManager defaultManager]

4: 单例的创建

  • 写法1. 不考虑多线程的单例创建方式
+ (instancetype)shareInstance {
    static SingleInstance *_instance;
    if(!single) {        
        single = [[SingleInstance alloc] init];    
    }
     return _instance;
 }

严格意义上来说,我们还需要将alloc方法封住,因为严格的单例是不允许再创建其他实例的,而alloc方法可以在外部任意生成实例。但是考虑到alloc属于NSObject,iOS中无法将alloc变成私有方法,最多只能覆盖alloc让其返回空,不过这样做也可能会让使用接口的人误解,造成其他问题。所以我们一般情况下对alloc不做特殊处理。系统的单例也未对alloc做任何处理

对于一个实例,我们一般并不能保证他一定会在单线程模式下使用,所以我们得适配多线程情况。在多线程情况下,上面的单例创建方式可能会出现问题。如果两个线程同时调用shareInstance,可能会创建出2个single来。所以对于多线程情况下,我们可以使用@synchronized来加锁。

  • 写法2: 加互斥锁,解决多线程访问安全问题
+(id)sharedInstance { 
    // 创建静态对象 防止外部访问 
    static SingleInstance *_instance; 
    @synchronized (self) { 
        // 为了防止多线程同时访问对象,造成多次分配内存空间,所以要加上线程锁。
        // 相比于GCD,@synchronized比较耗性能 
        if (instance == nil) { 
            instance = [[super allocWithZone:NULL] init];  
        } 
    return _instance; 
}

这样的话,当多个线程同时调用shareInstance时,由于@synchronized已经加锁,所以只能有一个线程进入创建_instance。这样就解决了多线程下调用单例的问题.

使用@synchronized虽然解决了多线程的问题,但是并不完美。因为只有在instance未创建时,我们加锁才是有必要的。如果instance已经创建.这时候锁不仅没有好处,而且还会影响到程序执行的性能(多个线程执行@synchronized中的代码时,只有一个线程执行,其他线程需要等待. 而且@synchronized在底层增删改查消耗了大量性能.

  • 写法3: GCD dispatch_onec, 本身是线程安全的,保证整个程序中只会执行一次
    + (instancetype)sharedInstance {
        static ClassName *_instance;
        static dispatch_one_t oneToken;
        dispatch_once(&onetoken,^{
           _instance = [self alloc]init];
        });
        return _instance;
    }
    
    + (instancetype)allocWithZone:(NSZone *) zone{
      static dispatch_t onetoken;
      dispatch_once(&oncetoken ^{
         _instance = [super allocwithzone:zone];
      })
      retun _instance
    }

static dispatch_once_t onceToken的原理:

dispatch_once_t 
It must be initialized to zero.
 * Note: static and global variables default to zero.

多线程解决当前创建对象的时候,保证只有一条线程执行创建代码,是根据onceToken的值来判断的,默认它的值为0,也可以通过在不同的位置,打印它的值,来验证它什么时候执行 NSLog(@"已经执行了%ld",onceToken);

  • onceToken= 0时,线程执行dispatch_once的block中代码;
  • onceToken= -1时,线程跳过dispatch_once的block中代码不执行;
  • onceToken为其他值时,被线程被阻塞,等待onceToken值改变; 当线程第一次调用sharedInstanceonceToken的值为0,执行block中的代码, 当执行时,系统内部将onceToken的值修改为140734629902336,当执行完成后,将onceToken修改为-1;以下就分两种情况了:

第一种情况: 某一线程要执行block中的代码时,首先需要判断onceToken的值,再去执行block中的代码。一旦有线程首次正在执行,那么这里onceToken的值变为140734731430192,也就表明当前线程正在执行,其他线程被阻塞;

当当前线程执行完block之后, onceToken变为-1,那么, 当有其他线程执行时,获取到的onceToken值为-1,其他线程直接跳过block, 保证只会创建一次。

第二种情况: 一旦执行过一次,下次再调用sharedInstance时,block已经为-1。直接跳过block

4: Reference:

www.jianshu.com/p/03a560a7e…