美文网首页
iOS开发之进阶篇(5)—— 单例

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

作者: 看影成痴 | 来源:发表于2020-06-17 15:18 被阅读0次
    singleton.png

    目录

    1. 最终推荐写法
    2. 何为单例?
    3. 对象的创建
    4. 单例写法的讨论过程

    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

    相关文章

      网友评论

          本文标题:iOS开发之进阶篇(5)—— 单例

          本文链接:https://www.haomeiwen.com/subject/gfsjxktx.html