单例的理解

什么是单例?

通过单例方法获取到的实例,在系统中是唯一的。 为什么说通过单例方法获取到的实例是唯一的呢?这个问题接下在分析。

什么时候适合使用单例?

系统中某个对象只需要存在一个实例(多了就浪费内存)

单例的创建

创建单例,有两种方式(我知道的就两种),一种是通过@synchronized来创建,一种是通过GCD来创建。这里不考虑单线程下创建单例。

1、@synchronized方式创建
 static UserInfo *manager;
+ (instancetype)sharedManager {
  if(!manager) {
    @synchronized (self) {
        if (!self) {
            manager = [[UserInfo alloc] init];
        }
    }
  }
    return manager;
}

2、GCD方式创建

+ (instancetype)shareManager{
    static dispatch_once_t onceToken;
    static UserInfo *manager;
    dispatch_once(&onceToken, ^{
        if (manager == nil) {
            manager = [[UserInfo alloc] init];
        }
    });
    return manager;
}

两种方式创建分析:

通过@synchronized方式创建的单例,加锁的目的是为了防止多线程同时去创建单例实例

通过GCD方式创建的单例,我们申明的静态局部变量static dispatch_once_t onceToken;的初始值为0(tip:dispatch_once_t是一个长整形),当获取实例的时候dispatch_once会判断onceToken的值,当值为0的时候,才会走dispatch_once的block方法,创建完成之后会将onceToken的值赋为非0。下一次获取实例的时候dispatch_once会判断onceToken的值,当值不为0的时候就不在走block方法,也就不会再创建实例。通过一下源码大致分析dispatch_once的流程:

// dispatch_once_t 的定义
typedef long dispatch_once_t;
// dispatch_once 的定义
void dispatch_once(dispatch_once_t *val, void (^block)(void)){
    struct Block_basic *bb = (void *)block;
    dispatch_once_f(val, block, (void *)bb->Block_invoke);
}
// 核心点dispatch_once_f函数
void dispatch_once_f(dispatch_once_t *val, void *ctxt, void (*func)(void *)){
    
    volatile long *vval = val;
    if (dispatch_atomic_cmpxchg(val, 0l, 1l)) {
        func(ctxt);
        dispatch_atomic_barrier();
        *val = ~0l;
    } else {
        do {
            _dispatch_hardware_pause();
        } while (*vval != ~0l);
        dispatch_atomic_barrier();
    }
}

dispatch_atomic_cmpxchg(val, 0l, 1l)函数判断val的值是否等于0,等于则将val值赋为1,返回true,否则返回false

在多线程环境中,如果某一个线程Thread1首次进入dispatch_once_f*val0,这个时候直接将其设为1,然后执行func(ctxt);方法(这个就是我们block里边的代码),然后调用dispatch_atomic_barrier(这函数干啥暂时没有搞懂,网搜了一下说是一个编译器操作,意思为前后指令的顺序不能颠倒.这里是防止编译器优化将原本的指令语义破坏),最后将*val的值修改为~0l(非零)。

如果其它的线程也进入dispatch_once_f,那么这个时候if的判断返回false,就进入else分支,于是执行了do~while空循环,一直等待。_dispatch_hardware_pause(这个函数具体干啥暂时未知,网上说有助于提高性能和节省CPU耗电,延迟空等),直到首个线程已经将func(ctxt);执行完毕且将*val修改为~0l,调用dispatch_atomic_barrier后退出。所以多线程的时候block也是无法同时执行的,这就保证了在dispatch_once_fblock的执行的唯一性,生成的单例也是唯一的。

如何创建唯一的单例实例

上面两种方式创建的单例,其实在系统中并不是唯一的,并不唯一的意思是 当前类可以通过其他方式创建实例。 并不是说 通过shareManager方法获取的实例不是唯一的。注意区分喔(tip:系统实现的单例其实也是可以通过alloc、new等方式去重新获取实例的)。
还是可以通过alloc 、new、copy、mutableCopy等方法获取到当前类的实例。

那么如何避免通过alloc 、new、copy、mutableCopy获取到实例呢?也有两种实现方式。

1、通过 NS_UNAVAILABLE实现

我们把能创建实例的方法全部加入 NS_UNAVAILABLE

+ (instancetype)alloc NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (id)copy NS_UNAVAILABLE;
- (id)mutableCopy NS_UNAVAILABLE;

这样定义了之后,在外部通过alloc 、new、copy、mutableCopy获取实例系统会报错找不到方法(tip:假如当前类没有实现NSCopying, NSMutableCopying协议时,可以不用限制copy、mutableCopy)。

2、通过重写方法的方式实现
// 重写+ (instancetype)allocWithZone:(struct _NSZone *)zone; 方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [UserInfo shareManager];
}

// 注意:重写+ (instancetype)allocWithZone:(struct _NSZone *)zone; 方法后,需要调用父类allocWithZone方法来分配空间
// GCD创建方式需要改写为
+ (instancetype)shareManager{
    static dispatch_once_t onceToken;
    static UserInfo *manager;
    dispatch_once(&onceToken, ^{
        if (manager == nil) {
            manager = [[super allocWithZone:NULL] init];
        }
    });
    return manager;
}

// 如果实现了NSCopying, NSMutableCopying协议,还需要重写copy、mutableCopy方法
- (id)copyWithZone:(NSZone *)zone {
    return [UserInfo shareManager];
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    return [UserInfo shareManager];
}

注意:通过 NS_UNAVAILABLE方式实现的,也不一定是无法获取当前类其它实例,还是可以通过runtime获取:

UserInfo *allocInfo = [UserInfo performSelector:@selector(alloc)];

通过上述的方式,还是可以获取。

单例使用注意

初始化要尽量简单,不要太复杂;
单例尽量考虑使用场景,不要随意实现单例,单例一旦初始化就会一直占着资源不能释放,造成资源浪费。

你可能感兴趣的:(单例的理解)