Singleton单例

单例类总是返回自己的同一个实例,它提供了对类的对象所提供资源的全局访问点。
单例模式的意图:
使得类的一个对象成为系统中的唯一实例。

单例版本1:

NS_ASSUME_NONNULL_BEGIN
@interface TestManager : NSObject
@property (nonatomic,copy) NSString * name;
+ (instancetype)shareInstance;
@end
NS_ASSUME_NONNULL_END

@implementation TestManager
static TestManager * manager = nil;
+ (instancetype)shareInstance{
    if (manager== nil) {
        manager = [[TestManager alloc] init];
    }
    return manager;
}
@end

这不是一个“严格”意义上的单例,要想在实际中使用,需要面对以下两个主要的障碍:

  • 发起调用的对象不能以其他分配方式实例化单例对象。否则,就有可以创建单例类的多个实例
  • 对单例对象实例化的限制应该与引用计数内存模型共存

以下测试代码对TestManager生成了多个不同的实例

    TestManager * test1 = [TestManager shareInstance];
    test1.name = @"1";
    TestManager * test2 = [[TestManager  alloc] init];
    test2.name = @"rewq";
    NSLog(@"%@:%@",test1.name,test2.name); // 1:rewq

此模式下,调用test1的copy方法会crash ,-[TestManager copyWithZone:]: unrecognized selector sent to instance 0x6000003b05f0 TestManager类未实现NSCopying协议

版本2

@implementation TestManager
static TestManager * manager = nil;
+ (instancetype)shareInstance{
    static TestManager * manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[TestManager alloc] init];
    });
    return manager;
}
@end

此版本依然是一个非“严格“意义上的单例,但这个是线程安全的。

下面我们来实现一个严格意上的单例:
版本3:

NS_ASSUME_NONNULL_BEGIN

@interface TestManager : NSObject<NSCopying,NSMutableCopying>
@property (nonatomic,copy) NSString * name;
+(instancetype)shareInstance;
@end

NS_ASSUME_NONNULL_END
@implementation TestManager
+(instancetype)shareInstance{
    static TestManager * manager = nil;
    if (manager== nil) {
        manager = [[super allocWithZone:NULL] init];
    }
    return manager;
}

+(instancetype)allocWithZone:(struct _NSZone *)zone{
    return [self shareInstance];
}
//保证不返回实例的副本,通过返回self,返回同一个实例
-(id)copyWithZone:(NSZone *)zone{
    return self;
}
//保证不返回实例的副本,通过返回self,返回同一个实例
-(id)mutableCopyWithZone:(NSZone *)zone{
    return self;
}
@end

和前两个版本一样,首先检查类的唯一实例是否已创建,如果没有,就创建一个新的实例并将其返回。但是,这里没有使用alloc这样的方法,而是调用[[super allocWithZone:NULL] init]来生成新的实例。为什么是super而不是self?因为已经在self中重载了基本的对象分配方法,所以需要“借用”其父类的功能来帮助处理底层内存分配的杂务。
Ps:这里没有考虑到多线程问题,如果在多线程中使用的话需要考虑到线程安全
版本4(线程安全)

@implementation TestManager
+(instancetype)shareInstance{
    static TestManager * manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //以下代码有crash的机率
        //manager = [[TestManager alloc] init];
         manager = [[ super allocWithZone:NULL] init];
    });
    return manager;
}

+(instancetype)allocWithZone:(struct _NSZone *)zone{
    return [self shareInstance];
}
//保证不返回实例的副本,通过返回self,返回同一个实例
-(id)copyWithZone:(NSZone *)zone{
    return self;
}
//保证不返回实例的副本,通过返回self,返回同一个实例
-(id)mutableCopyWithZone:(NSZone *)zone{
    return self;
}
@end

版本5(线程安全):

+(instancetype)shareInstance{
    return [[[self class] alloc]init];
}
+(instancetype)allocWithZone:(struct _NSZone *)zone{
    static TestManager * manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //不能这样添加,否则会crash
        // manager = [[TestManager alloc] init];
         manager = [[ super allocWithZone:zone] init];
    });
    return manager;
}
//保证不返回实例的副本,通过返回self,返回同一个实例
-(id)copyWithZone:(NSZone *)zone{
    return self;
}
//保证不返回实例的副本,通过返回self,返回同一个实例
-(id)mutableCopyWithZone:(NSZone *)zone{
    return self;
}
@end

一劳永逸,单例模式的优化

如果想要一劳永逸,我们将面临两个问题

  1. 如何写一份单例代码在ARC和MRC环境下都适用?
  2. 如何使一份单例代码可以多个类共同使用

为了解决这两个问题,我们可以在PCH文件使用代参数的宏和条件编译
利用条件编译来判断是ARC还是MRC环境

#if __has_feature(objc_arc)
//如果是ARC,那么就执行这里的代码1
#else
//如果不是ARC,那么就执行代理的代码2
#endif

将单例方法写成宏定义:

#define singleH(name) +(instancetype)shareInstance;

#if __has_feature(objc_arc)

#define singleM(name) static id _instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+ (instancetype)shareInstance\
{\
return [[self alloc]init];\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
\
- (id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}


#else
#define singleM static id _instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+ (instancetype)shareInstance\
{\
return [[self alloc]init];\
}\
- (id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
\
- (id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
- (oneway void)release\
{\
}\
\
- (instancetype)retain\
{\
return _instance;\
}\
\
- (NSUInteger)retainCount\
{\
return MAXFLOAT;\
}
#endif

在.h文件中调用singleH(类名)
在.m文件中调用singleM(类名)


   转载规则


《Singleton单例》 志鹏 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
TaggedPoint TaggedPoint
Tagged Pointer 详细的内容可以看这里 深入理解Tagged Pointer Tagged Pointer 是一个能够提升性能、节省内存的有趣的技术。 Tagged Pointer 专门用来存储小的对象,例如 NSNumber
2018-11-08 志鹏
下一篇 
UIView显示层初级动画 UIView显示层初级动画
显示层初级动画属性预览extension UIView { open var frame: CGRect open var bounds: CGRect open var center: CGPoint
2018-09-24 志鹏
  目录