美文网首页iOS锦囊
一个优雅的单例案例

一个优雅的单例案例

作者: 片片碎 | 来源:发表于2020-11-24 15:48 被阅读0次

    参考资料:

    一、一个优雅的单例案例

    1.1、简介
    • 使用官方推荐的dispatch_once创建
    • 避免循环引用,使用[super allocWithZone:NULL]创建
    • 保持单列完整性,遵循了NSCopying,NSMutableCopying,重写了
    • 标记不能被继承__attribute__ ((objc_subclassing_restricted))
    • 考虑了创建对象的几种方式,以及调用顺序
    1.2、FengOnceClass.h
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    // 标记不能被继承
    __attribute__ ((objc_subclassing_restricted))
    
    @interface FengOnceClass : NSObject<NSCopying,NSMutableCopying>
    
    + (instancetype)sharedInstance;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    1.3、FengOnceClass.m
    #import "FengOnceClass.h"
    @implementation FengOnceClass
    
    + (instancetype)sharedInstance {
        static FengOnceClass *sharedInstance = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedInstance = [[super allocWithZone:NULL] init];//避免循环引用,故用super
                      /*
                      * 也可以此处用self,alloc中调用super
                      * 但是不推荐,有代码冗余,推荐此处直接使用super
                      */
                     //sharedInstance = [[self allocWithZone:NULL] init]; 
      });
        return sharedInstance;
    }
    
    - (instancetype)init {
        if (self = [super init]) {
          //初始化,需要有初始化的就写,没有可以省略这个方法
        }
        return self;
    }
    #pragma mark --重写 保证为真正的单列
    + (id) allocWithZone:(struct _NSZone *)zone {
      /*
        * 需要考虑开发着的调用顺序,故不能直接return self;
        * 因为开发者可能先调用alloc再调用sharedInstance
      */
       //return self;
       return [self sharedInstance];
    }
    
    /* 
      * 有的文章说需要重写new
      * 但验证后,是不需要重写的
      * new调用的就是alloc和init,只要重写allocWithZone就好了
    */
    //+ (instancetype)new {
    //    return [self sharedInstance];
    //}
    
    
    /*
      * copy和mutableCopy其实不重写也可以,但是会崩溃unrecognized selector sent to instance XXXX"
      * 因为需要支持copy和mutableCopy必须支持NSCopying,NSMutableCopying协议
      * 但是为了代码的严谨性还是重写一下比较好
      * 可以直接return self,是因为iOS的记住,如果是实例为空对象,就不会调到实例方法
    */
    /*
      * copy和mutableCopy其实不重写也可以,但是会崩溃unrecognized selector sent to instance XXXX"
      * 因为需要支持copy和mutableCopy必须支持NSCopying,NSMutableCopying协议
      * 但是为了代码的严谨性还是重写一下比较好
      * 其实可以直接return self,因为iOS的特性,如果是实例为空对象,就不会调到实例方法
      * 但是为了代码的严谨性还是是用sharedInstance
    */
    - (id)copyWithZone:(nullable NSZone *)zone {
        Class selfClass = [self class];
        return [selfClass sharedInstance];
    }
    - (id)mutableCopyWithZone:(nullable NSZone *)zone {
        Class selfClass = [self class];
        return [selfClass sharedInstance];
    }
    @end
    

    二、为什么要标记成不可继承

    • 现把FengOnceClass标记为不能被继承的标示屏蔽,然后定义一个FengOncePlusClass,什么都不做,不定义属性不定义方法,只是继承FengOnceClass,代码如下:
    //.h文件
    #import "FengOnceClass.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface FengOncePlusClass : FengOnceClass
    
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    //.m文件
    #import "FengOncePlusClass.h"
    
    @implementation FengOncePlusClass
    
    @end
    
    • 使用如下:
    FengOnceClass *test1 = [FengOnceClass sharedInstance];
    test1.name = @"Feng";
    
    FengOncePlusClass *test2 = [FengOncePlusClass sharedInstance];
    test2.name = @"FengPlus";
    NSLog(@"test1 %p test1.name %@ test2 %p test2.name %@",test1,test1.name,test2,test2.name);
    
    • 打印结果如下:
    2020-11-24 16:47:03.574846+0800 BlueToothDemo[51487:3514206] test1 0x600002b70030 test1.name FengPlus test2 0x600002b70030 test2.name FengPlus
    
    • 分析:
      地址都是一样的,然后name都变成了"FengPlus",这显然不是我们想要的单例,不能保持单列的完成性了。所以还是要开启不能被继承的标示。但是如果业务有需求,需要实现可继承单例,又不影响单例,是否能实现了。答案是可以的,继续往下看。

    3、可继承单列实现

    • 在单例上做如下改动

      • 屏蔽不能被继承的标示
      • 使用runTime的关联对象进行创建
    • FengOnceClass.h,代码如下:

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    @interface FengOnceClass : NSObject<NSCopying,NSMutableCopying>
    
    + (instancetype)sharedInstance;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    • FengOnceClass.m,代码如下:
    #import "FengOnceClass.h"
    @implementation FengOnceClass
    //使用runTime的关联对象进行创建
    +(instancetype)sharedInstance {
        Class selfClass = [self class];
        // 从类中获取对象
        id instance = objc_getAssociatedObject(selfClass, @"sharedInstance");
        if (!instance) {
            // 不存在,创建对象
            instance = [[super allocWithZone:NULL] init];
            // 给类绑定对象
            objc_setAssociatedObject(selfClass, @"sharedInstance", instance, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        return instance;
    }
    
    - (instancetype)init {
        if (self = [super init]) {
          //初始化,需要有初始化的就写,没有可以省略这个方法
        }
        return self;
    }
    + (id) allocWithZone:(struct _NSZone *)zone {
       return [self sharedInstance];
    }
    - (id)copyWithZone:(nullable NSZone *)zone {
        Class selfClass = [self class];
        return [selfClass sharedInstance];
    }
    - (id)mutableCopyWithZone:(nullable NSZone *)zone {
        Class selfClass = [self class];
        return [selfClass sharedInstance];
    }
    @end
    
    • 使用如下:
    FengOnceClass *test1 = [FengOnceClass sharedInstance];
    test1.name = @"Feng";
    
    FengOncePlusClass *test2 = [FengOncePlusClass sharedInstance];
    test2.name = @"FengPlus";
    NSLog(@"test1 %p test1.name %@ test2 %p test2.name %@",test1,test1.name,test2,test2.name);
    
    • 打印结果和分析如下:


      可继承单例

    相关文章

      网友评论

        本文标题:一个优雅的单例案例

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