美文网首页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);
  • 打印结果和分析如下:


    可继承单例

相关文章

  • 单例模式只有饿汉式和懒汉式吗?这几种单例模式你见过吗

    设计模式之单例模式-单例模式的几种实现方式及小案例 本文出处:凯哥Java(wx:kaigejava) 单例模式有...

  • 你说你熟悉java设计模式,那单例模式的这几种创建方式你都知道吗

    单例模式使用案例 数据库的连接池; Spring中的Bean默认也是单例的; 单例模式的特性 将构造函数私有化 在...

  • 研磨设计模式之单例模式(内部类)

    前言 在Java的单例模式里面,很多人都知道懒汉式要比饿汉式更优雅,这里我想告诉你的是,我这里,有一种更优雅的单例...

  • iOS单例_Singleton

    单例的案例 单例是什么: 保证某个类创建出来的对象永远只有1个; 节省内存开销 保证数据或者资源的一致性 单例...

  • Swift严格的单例写法

    相比OC,Swift有很优雅的实现单例的写法。 实现 单例类Tools 客户端调用: 说明 当尝试使用 这种方法去...

  • 2020-07-02 - C#单例

    C#单例模式 使用懒加载模式创建, 写法比较优雅.

  • 一个优雅的单例案例

    参考资料: 《iOS单例的精心设计历程》[https://www.jianshu.com/p/e18d1518db...

  • 单例模式

    一、定义与特点 单例模式的特点 单例类只有一个实例对象 该单例对象必须由单例类自行创建 单例类对外提供一个访问该单...

  • 单例模式

    特点 单例类只有1个实例对象 该单例对象必须由单例类自行创建 单例类对外提供一个访问该单例的全局访问点 结构 单例...

  • Java 单例模式

    线程安全的单例模式的几种实现方法分享线程安全的单例模式实现有几种思路,个人认为第2种方案最优雅 饿汉式 借助内部类...

网友评论

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

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