iOS开发之进阶篇(5)—— 单例

singleton.png

1. 最终推荐写法

1.1 OC

SingleObject.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface SingleObject : NSObject

+ (instancetype)sharedInstance;

@end

NS_ASSUME_NONNULL_END

SingleObject.m

#import "SingleObject.h"

static SingleObject *_singleInstance = nil;

@implementation SingleObject

+ (instancetype)sharedInstance
{
    return [[self alloc] init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _singleInstance = [super allocWithZone:zone];
    });
    return _singleInstance;
}

- (instancetype)init
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _singleInstance = [super init];
        if (_singleInstance) {
            // 在这里初始化self的属性和方法
        }
    });
    return _singleInstance;
}

/*
#pragma mark - 如果遵循了 NSCopying / NSMutableCopying 协议

- (id)copyWithZone:(nullable NSZone *)zone
{
    return _singleInstance;
}

- (id)mutableCopyWithZone:(nullable NSZone *)zone
{
    return _singleInstance;
}
 */

@end

1.2 Swift

摘自苹果文档.

class Singleton {
    static let sharedInstance = Singleton()
}

如果需要做一些初始化操作, 使用下面:

class Singleton {
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code
        return instance
    }()
}

2. 何为单例?

2.1 单例概念

苹果文档
A singleton class returns the same instance no matter how many times an application requests it. A typical class permits callers to create as many instances of the class as they want, whereas with a singleton class, there can be only one instance of the class per process. A singleton object provides a global point of access to the resources of its class.

翻译过来就是:

  1. 在App运行期间, 单例类有且仅有一个实例对象;
  2. 这个单例对象是可以全局访问的.

2.2 几个官方单例

  1. NSFileManager
  2. NSWorkspace
  3. UIApplication
  4. UIAccelerometer (Deprecated)

按照惯例, 返回单例类实例对象的方法是一种工厂方法, 约定方法名称这样命名:sharedClassType. 比如sharedFileManager, sharedWorkspace, sharedApplication等等.

官方样例: 无
😂😂😂

2.3 单例原理

苹果文档
The class lazily creates its sole instance the first time it is requested and thereafter ensures that no other instance can be created.
五星翻译: 在单例类第一次创建的时候稍微做些处理, 使这个类无法再创建其他实例对象.

简单来说, 就是防止一个单例类被多次创建, 或者说这个单例类的创建方法仅执行一次.
结论是:
让单例类的创建实例方法只执行一次.
让单例类的创建实例方法只执行一次.
让单例类的创建实例方法只执行一次.

接下来我们从对象的创建入手. 请看下小节.

3. 对象的创建

object_creation.png

对象的创建分两步: 分配内存和初始化.
常规操作如下:

TheClass *newObject = [[TheClass alloc] init];

分配内存
为对象分配内存有两种方法: allocallocWithZone:
其实, 使用alloc最终还是会调用allocWithZone:方法.
allocWithZone:这个方法苹果不建议我们直接使用, 但是这个方法在OC中没有被遗弃, 它的存在是历史遗留问题:

This method exists for historical reasons; memory zones are no longer used by Objective-C.

所以, 我们分配内存时应该使用alloc而不是allocWithZone:方法.

但是, 不排除个别人使用allocWithZone:去分配内存的情况. 所以, 后面我们讨论单例的写法时, 还是要考虑这种情况. 这里先埋下伏笔.

初始化
初始化处在创建对象阶段, 该阶段通过将对象的实例变量设置为合理的初始值, 还可以分配和准备对象所需的其他全局资源, 才使得该对象可用.
按照约定, 初始化方法始终以init开头. 该方法返回一个动态类型的对象(id), 或者, 如果初始化失败, 则返回nil.
如果某个类实现了初始化方法, 则第一步应调用其父类的初始化程序. 比如:

- (instancetype)init
{
    self = [super init];
    if (self) {
        // 初始化self的属性和方法
    }
    return self;
}

此要求可确保从根对象开始在继承链中对对象进行一系列初始化.

initialization.png

工厂方法
工厂方法是一种类方法, 其将分配内存alloc和初始化init结合在一起, 并返回一个自动释放的类实例.
例如:

+ (instancetype)stringWithString:(NSString *)string;
+ (NSNumber *)numberWithInt:(int)value;

我们单例类的获取实例方法, 就是使用工厂方法返回的, 形式如sharedClassType.

new
我们常常看到一个对象的创建方法如下:

NSString *string = [NSString new];

在苹果文档中:

This method is a combination of alloc and init.

其实, new也是工厂方法, new = alloc + init.

4. 单例写法的讨论过程

创建一个SingleObject类, 继承于NSObject. 给这个SingleObject写一个工厂方法用于返回类的实例对象.
SingleObject.h

@interface SingleObject : NSObject

+ (instancetype)sharedInstance;

@end

SingleObject.m

+ (instancetype)sharedInstance {
    
    return [[self alloc] init];
}

OK, 雏形有了, 现在它能返回一个实例. 但仅是这样肯定是不行的, 因为每次调用sharedObject都会重新创建一个实例.
之前说过, 单例的原理是让单例类的创建实例方法只执行一次. 而创建方法分两步: alloc和init. 所以, 创建单例, 我们需做到以下两点(tag=10001):

  • alloc只调用一次
  • init只调用一次

可是alloc和init方法都是外部可以访问的, 我们不能控制别人使用的次数. 比如外部可以调用多次:

SingleObject *obj1 = [[SingleObject alloc] init];
SingleObject *obj2 = [[SingleObject alloc] init];
...

我们在对象的创建小节里讨论过, 如果某个类实现了初始化方法, 则第一步应调用其父类的初始化程序. 也就是说, 虽然该类的init无法控制, 但是通过重写init方法, 它的父类init是可控的. 所以, tag=10001那两点应该改写表述成如下两点(tag=10086):

  • [super alloc]只调用一次
  • [super init]只调用一次

问题又来了, 我们之前不是说过分配内存有两种方法吗? 假如创建的时候不是用alloc而是用allocWithZone:方法, 那我们又得干瞪眼了. 好在alloc最终都是调用allocWithZone:方法的, 所以tag=10086这两点最终应修改成:

  • [super allocWithZone:]只调用一次
  • [super init]只调用一次

这两点需求算是成熟可以告一段落了, 接下来讨论怎样让一段代码只执行一次呢?
苹果给我们提供了dispatch_once这个方法:

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 不管调用多少次dispatch_once, 这里的代码只会执行一次
});

关于dispatch_once这里不打算详解, 我们只需要知道它的作用以及它是线程安全的.
我们就用这个方法去实现那两点需求:

#import "SingleObject.h"

static SingleObject *_singleInstance = nil;

@implementation SingleObject

+ (instancetype)sharedInstance
{
    return [[self alloc] init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _singleInstance = [super allocWithZone:zone];
    });
    return _singleInstance;
}

- (instancetype)init
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _singleInstance = [super init];
        if (_singleInstance) {
            // 在这里初始化self的属性和方法
        }
    });
    return _singleInstance;
}

@end

这里allocWithZone:和init方法都需要返回同个对象, 所以使用static进行全局定义.

到这里已经接近尾声了, 我们可以验证一下:

    SingleObject *obj1 = [[SingleObject alloc] init];
    SingleObject *obj2 = [[SingleObject allocWithZone:NULL] init];
    SingleObject *obj3 = [SingleObject new];
    SingleObject *obj4 = [SingleObject sharedInstance];
    SingleObject *obj5 = [SingleObject sharedInstance];
    
    NSLog(@"obj1:%@", obj1);
    NSLog(@"obj2:%@", obj2);
    NSLog(@"obj3:%@", obj3);
    NSLog(@"obj4:%@", obj4);
    NSLog(@"obj5:%@", obj5);

log:

2020-06-17 14:42:12.810826+0800 KKSingletonDemo[2168:109605] obj1:<SingleObject: 0x600000740580>
2020-06-17 14:42:12.811040+0800 KKSingletonDemo[2168:109605] obj2:<SingleObject: 0x600000740580>
2020-06-17 14:42:12.811162+0800 KKSingletonDemo[2168:109605] obj3:<SingleObject: 0x600000740580>
2020-06-17 14:42:12.811325+0800 KKSingletonDemo[2168:109605] obj4:<SingleObject: 0x600000740580>
2020-06-17 14:42:12.811430+0800 KKSingletonDemo[2168:109605] obj5:<SingleObject: 0x600000740580>

最后还得注意一点, 如果单例遵循了协议, 那么创建单例的方法可能不走init而是走copy或者mutableCopy, 这种情况下我们还得添加如下处理:

#pragma mark - 如果遵循了 NSCopying / NSMutableCopying 协议

- (id)copyWithZone:(nullable NSZone *)zone
{
    return _singleInstance;
}

- (id)mutableCopyWithZone:(nullable NSZone *)zone
{
    return _singleInstance;
}

最后, 我们的成品代码见篇头.

参考

https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/ObjectCreation.html#//apple_ref/doc/uid/TP40008195-CH39-SW1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值