岁岁年,碎碎念

重谈单例模式

2022.05.06     354

前言

一提到 iOS 单例模式,一般都会想到如下常用方式。

+ (instancetype)sharedInstance {
    static TestClass *_sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

大家按约定使用 sharedInstance 获取单例,似乎没有什么问题,大家也都是这么做的。

其实,单例模式其实不只是仅仅一个 sharedInstance 方法就够了

完整写法

避免使用方使用 alloc、new 和 copy、mutableCopy,有两种处理办法

写法一

+ (instancetype)sharedInstance {
    static TestClass *_sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    return [self sharedInstance];
}

- (id)copyWithZone:(struct _NSZone *)zone
{
    return [TestClass sharedInstance];
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
    return [TestClass sharedInstance];
}

写法二

+ (instancetype)sharedInstance {
    static TestClass *_sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

# .h 中禁用方法调用
+ (instancetype)new  __attribute__((unavailable("Use +sharedInstance instead")));
- (instancetype)init __attribute__((unavailable("Use +sharedInstance instead")));
- (instancetype) copy __attribute__((unavailable("Use +sharedInstance instead")));
- (instancetype) mutableCopy __attribute__((unavailable("Use +sharedInstance instead")));
# 或者
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)copy NS_UNAVAILABLE;
- (instancetype)mutableCopy NS_UNAVAILABLE;

重谈单例

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

为保证进程内有且仅有一个实例可以供外部访问,需要满足下面条件:

  1. 私有化构造函数
  2. 私有的静态变量存储唯一实例
  3. 公开静态方法获取唯一实例

实现单例需要考虑的问题有:

  1. 创建单例是否延迟加载
  2. 创建单例是否线程安全
  3. 获取单例是否加锁,加锁性能如何保证

饿汉式

类加载的时候,静态实例 instance 就已创建并初始化

public class Singleton {
    // 静态字段引用唯一实例:
    private static final Singleton INSTANCE = new Singleton();

    // 通过静态方法返回实例:
    public static Singleton getInstance() {
        return INSTANCE;
    }

    // private构造方法保证外部无法实例化:
    private Singleton() {
    }
}

懒汉式

调用方第一次调用getInstance()时才初始化全局唯一实例

public class Singleton {
    private static final Singleton INSTANCE = null;

    public static synchronized Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }

    private Singleton() {
    }
}

双重检查

由于懒汉式加锁会严重影响并发性能

public class Singleton {
    private static final Singleton INSTANCE = null;

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
            if (INSTANCE == null) {
                INSTANCE = new Singleton();
            }
        }
        }
        return INSTANCE;
    }

    private Singleton() {
    }
}

需要注意: 在低版本 Java 中,注意指令重排,需要给 INSTANCE 加 volatile,禁止指令重排,高版本无需关心。

由于 Java 的内存模型,双重检查在这里不成立。要真正实现延迟加载,只能通过 Java 的 ClassLoader 机制完成。

静态内部类

Java 加载外部类的时候,不会创建内部类的实例,只有在外部类使用到内部类的时候才会创建内部类实例。

public class Singleton {
    public static Singleton getInstance() {
        return SingletonInner.instance;
    }

    private static class SingletonInner {
      private static final Singleton instance = new Singleton();
    }

    private Singleton() {
    }
}

枚举

Java 保证枚举类的每个枚举都是单例,所以编写一个只有一个枚举的类,可以实现单例

public enum Test {
	INSTANCE;

	private String name = "test";

	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}
}