严谨的单例实现方案

单例

单例可以保证程序运行过程中,一个类只有一个实例对象,并且该实例在整个程序运行过程中都会存在。由于该实例只有一个实例从而节约系统资源。但由于该实例一直在内存中,所以会占用内存资源,建议在开发中不要过多的使用单例,一般的使用场景为:当整个应用程序共享一份资源时,我们可以使用单例。例如:在做背景音乐,要求在不同的控制器里面都要播放音乐。或者在发送网络请求时,我们通常会搞一个工具类(AFN作者就搞了一个单例)等等。

方案一:非GCD方案

首先我们先创建一个类,名为SingleDog
我们创建一个对象时,首先调用SingleDog的alloc方法为对象分配内存空间,然后再调用init方法进行初始化,而alloc方法底层又调用+(instancetype)allocWithZone:(struct _NSZone *)zone方法 。所以我们要重写现+(instancetype)allocWithZone:(struct _NSZone *)zone方法,这样更加好那么一点;于是现在的代码是这样的(先不考虑线程安全)

#import "SingleDog.h"

@implementation SingleDog
//static修饰全局变量,该变量只能在本文件中访问,并且在整个程序运行过程中都会存在
//设置一个全局变量,用于保存SingleDog的实例
static SingleDog * _SingleDogInstance;

//在此方法里面要保证只分配一次内存空间
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
    if (_SingleDogInstance == nil) {
        //如果实例对象_SingleDogInstance为空,我们就调用父类的allocWithZone:方法,因为我们不知道怎么给它分配内存,所以让父类去处理
        _SingleDogInstance = [super allocWithZone:zone];
    }

    return _SingleDogInstance;
}

@end

为了便于使用者在外界直接创建该类的实例,我们通常提供一个类方法,给外界提供一个接口便于快速创建。在单例设计模式中我们提供的类方法通常以share,stand,default开头,在本例中我们提供的类方法将以share开头,就叫+(instancetype)sharedSingleDog;

于是在SingleDog.h文件中目前的代码是这样的

#import <Foundation/Foundation.h>

@interface SingleDog : NSObject
//提供一个类方法便于外界快速创建对象
+(instancetype)sharedSingleDog;

@end

在SingleDog.m文件中是这样的

#import "SingleDog.h"

@implementation SingleDog

//static修饰全局变量,该变量只能在本文件中访问,并且在整个程序运行过程中都会存在
//设置一个全局变量,用于保存SingleDog的实例
static SingleDog * _SingleDogInstance;

//在此方法里面要保证只分配一次内存空间
+(instancetype)allocWithZone:(struct _NSZone *)zone
{

    if (_SingleDogInstance == nil) {
        //如果实例对象_SingleDogInstance为空,我们就调用父类的allocWithZone:方法,因为我们不知道怎么给它分配内存,所以让父类去处理
        _SingleDogInstance = [super allocWithZone:zone];
    }

    return _SingleDogInstance;
}
//提供一个类方法便于外界快速创建对象
+(instancetype)sharedSingleDog
{
    if (_SingleDogInstance == nil) {
    //要调用一下init方法,因为别人要直接用这个对象
        _SingleDogInstance = [[self alloc]init];
    }

    return _SingleDogInstance;
}
@end

由于并不是每个人在创建一个又一个对象的时候都用alloc,init方法已及sharedSingleDog等等,当有些人创建对象时,如果调用对象的copy方法来创建一个新对象时,这时为了保证创建出来的对象是同一个,我们必须重写- (id)copyWithZone:(NSZone *)zone所以在SingleDog.m文件中,还要加上一下代码

//该方法为对象方法,所以已经存在一个对象了,我们直接返回即可
- (id)copyWithZone:(NSZone *)zone
{
    return _SingleDogInstance;
}

在以上的代码中我们忽略了多线程问题,当有很多线程同时执行以下代码时

//在此方法里面要保证只分配一次内存空间
+(instancetype)allocWithZone:(struct _NSZone *)zone
{

    if (_SingleDogInstance == nil) {
        //如果实例对象_SingleDogInstance为空,我们就调用父类的allocWithZone:方法,因为我们不知道怎么给它分配内存,所以让父类去处理
        _SingleDogInstance = [super allocWithZone:zone];
    }

    return _SingleDogInstance;
}

当有一条线程进去,判断_SingleDogInstance为空时,此时还没有执行_SingleDogInstance = [super allocWithZone:zone];又有一条线程判断_SingleDogInstance为空,于是又进了进去,这时_SingleDogInstance = [super allocWithZone:zone];就会执行了两遍。为了避免这种情况的发生我们通常加一把锁,让一条线程先执行完毕,另一条线程才能进去。
于是比较完整的SingleDog如下
在SingleDog.h文件中为:

#import <Foundation/Foundation.h>

@interface SingleDog : NSObject
//提供一个类方法便于外界快速创建对象
+(instancetype)sharedSingleDog;

@end

在SingleDog.m文件中为:


#import "SingleDog.h"

@implementation SingleDog

//static修饰全局变量,该变量只能在本文件中访问,并且在整个程序运行过程中都会存在
//设置一个全局变量,用于保存SingleDog的实例
static SingleDog * _SingleDogInstance;

//在此方法里面要保证只分配一次内存空间
+(instancetype)allocWithZone:(struct _NSZone *)zone
{

    @synchronized(self){if (_SingleDogInstance == nil) {
        //如果实例对象_SingleDogInstance为空,我们就调用父类的allocWithZone:方法,因为我们不知道怎么给它分配内存,所以让父类去处理
        _SingleDogInstance = [super allocWithZone:zone];
    }
    }
    return _SingleDogInstance;
}
//提供一个类方法便于外界快速创建对象
+(instancetype)sharedSingleDog
{
    @synchronized(self){if (_SingleDogInstance == nil) {
        //要调用一下init方法,因为别人要直接用这个对象
        _SingleDogInstance = [[self alloc]init];
    }
    }
    return _SingleDogInstance;
}
//该方法为对象方法,所以已经存在一个对象了,我们直接返回即可
- (id)copyWithZone:(NSZone *)zone
{
    return _SingleDogInstance;
}
@end

方案二:GCD方案

当你读完以上非GCD方案后,我们依然顺着上面流程演进,这时你的大脑已经清晰我们应该重写一下方法:
(1)+(instancetype)allocWithZone:(struct _NSZone *)zone
(2)+(instancetype)sharedSingleDog
(3)- (id)copyWithZone:(NSZone *)zone

在SingleDog.h文件不变:

#import <Foundation/Foundation.h>
@interface SingleDog : NSObject
//提供一个类方法便于外界快速创建对象
+(instancetype)sharedSingleDog;
@end

在SingleDog.m文件中为:

#import "SingleDog.h"

@implementation SingleDog

//static修饰全局变量,该变量只能在本文件中访问,并且在整个程序运行过程中都会存在
//设置一个全局变量,用于保存SingleDog的实例
static SingleDog * _SingleDogInstance;

//在此方法里面要保证只分配一次内存空间
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //里面的代码只会执行一次
        //onceToken默认等于0,如果是0就会执行此block里的代码,如果不是0,就不执行
        //如果实例对象_SingleDogInstance为空,我们就调用父类的allocWithZone:方法,因为我们不知道怎么给它分配内存,所以让父类去处理
        _SingleDogInstance = [super allocWithZone:zone];
    });
    return _SingleDogInstance;
}
//提供一个类方法便于外界快速创建对象
+(instancetype)sharedSingleDog
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //要调用一下init方法,因为别人要直接用这个对象
        _SingleDogInstance = [[self alloc]init];

    });

    return _SingleDogInstance;
}
//该方法为对象方法,所以已经存在一个对象了,我们直接返回即可
- (id)copyWithZone:(NSZone *)zone
{
    return _SingleDogInstance;
}
@end

swift版
swift版也可以用GCD实现单例,但总是有更简单的表现方式

 /// 在swift中 let是线程安全的
    private static let instance = SingleDog()

    class var sharedSingleDog: SingleDog {
        return instance
    }
      private  override init() {

    }

我们重写了父类的init方法,避免外界用一对圆括号(),创建对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值