OC 单例对象的创建及释放

1、单例的定义

  • 单例是一种设计模式,它能保证一个类的对象,无论何时创建,也无论在什么地方创建,也无论创建多少次,创建的都是同一个对象。

2、单例的作用

  • 可以保证在程序运行期间,一个类中只有一个实例,该实例易于外界访问,从而可以方便的控制实例的个数,并且节约系统资源。

3、单例的使用场合

  • 在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次),一般用于工具类。例如:登陆控制器,网络数据请求,音乐播放器等一个工程需要使用多次的控制器或方法。

4、优点

  • 单例模式可以保证系统中一个类只有一个实例,且该实例易于外界访问,从而可以方便的控制实例的个数,节约系统资源。
  • 单例对象一旦创建,对象指针是保存在静态区的。单例对象在堆中分配的内存空间,会在应用程序终止后才会被释放。

5、缺点

  • 单例类无法继承,因此很难进行类的扩展。
  • 单例不适用于变化的对象,如果同一类型的对象总是要在不同的场景下发生变化,单例就会引起数据的错误,不能保存彼此的状态。

6、注意:

  • 我们在使用单例类之前,一定要考虑好单例类是否适合和类以后的扩展性,避免盲目滥用单例。

7、创建单例对象的步骤

1、在类内部定义一个static修饰的全局变量。
2、提供一个类方法 ,方法名:+share类名 或者 +default类名,方便外界访问。
3、重写系统的 +allocWithZone:方法,保证一个单例对象分配一次内存空间,实现一个类只有一个实例。
4、重写-copyWithZone方法和-mutableCopyWithZone方法,遵守NSCopying协议、NSMutableCopying协议,可以通过copy、mutableCopy方式创建对象。

#import "Tools.h"
@implementation Tools

//ARC下创建单例对象的步骤
//创建私有的静态对象,防止外部访问
static Tools *_instance;
static dispatch_once_t onceToken;

//重写 allocWithZone: 方法,保证单例对象只能分配一次内存空间
+(instancetype)allocWithZone:(struct _NSZone *)zone {
    //为了防止多线程访问对象,造成多次分配内存空间,所以要加上线程锁(互斥锁)
//    @synchronized (self) {
//        if (_instance == nil) {
//            _instance = [super allocWithZone:zone];
//        }
//        return _instance;
//    }
    //使用GCD创建一次性对象也能实现单例
    dispatch_once(&onceToken, ^{
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    });
    return _instance;
}
//定义类方法,易于外界访问
+ (instancetype)shareTools {
    return [[self alloc]init];
}

// 为了严谨,也要重写copyWithZone 和 mutableCopyWithZone
-(id)copyWithZone:(NSZone *)zone {
    return _instance;
}

-(id)mutableCopyWithZone:(NSZone *)zone {
    return _instance;
}
//调用
 Tools *tools = [Tools shareTools];
    Tools *tools2 = [[Tools alloc]init];
    NSLog(@"tools = %@",tools);
    NSLog(@"tools2 = %@",tools2);

输出显示
在这里插入图片描述

可以看出两种初始化方法返回的指针地址相同,创建出来的是同一个对象。因为单例对象的初始化方法 alloc 方法底层调用的就是 +allocWithZone:方法。

问题:因为单例对象使用 static 修饰,被存储在静态区域。该对象只能在程序终止时才会被释放。如果我要提前释放单例对象该怎么做呢?

  • 想法1:直接将单例对象赋值为 nil,是否可行呢?答案是不行的。
  • 先看代码演示
 Tools *tools = [Tools shareTools];
    tools.name = @"step1 name";
    NSLog(@"step1---%@",tools);
    NSLog(@"step1 defaultValue = %@, name = %@", tools.defaultValue, tools.name);

    tools = nil;
    NSLog(@"step2---%@",tools);
    NSLog(@"step2 defaultValue = %@, name = %@", tools.defaultValue, tools.name);

    tools = [Tools shareTools];
    NSLog(@"step3---%@",tools);
    NSLog(@"step3 defaultValue = %@, name = %@", tools.defaultValue, tools.name);

打印的数据
在这里插入图片描述

按照正常的一个认知,将实例对象instance置为nil后,重新实例化后,应该是一个新的对象。但是丛云性能结果可以看出,将单例对象设置为 nil,再次创建对象,还是指向的同一块内存空间,说明并没有创建新的对象。表明用这中方式来释放对象是不可取的。

那么是什么原因造成的呢?进入到dispatch_once的源码实现中去探究下

dispatch_once源码
在这里插入图片描述
注意dispatch_once源码中的红线部分,dispatch_once_t *predicate; dispatch_once_t又是什么呢?继续看源码

dispatch_once_t源码
在这里插入图片描述

  • 通过源码我们可以清晰的知道,dispatch_once_t是一个long类型的变量,初始化必须为0;
  • 至此,我们可以清晰的了解到,dispatch_once的实现逻辑;
  • dispatch_once主要是根据long类型的值决定怎么去执行代码(所以dispatch_once_t需要声明为static,保证全局只有1个)
  • 当值为0时,执行dispatch_once的block代码
  • 当值为-1时,跳过dispatch_once的block代码
  • 当值为其他值时,线程被阻塞,等待其值改变;
  • 当dispach_once的block执行完成后,将long类型的值设置为-1.其他线程不在阻塞,跳过block。下周再调用shareInstance的时候,block已经为-1。直接跳过block。

查看代码验证源码

+(instancetype)allocWithZone:(struct _NSZone *)zone {
    //为了防止多线程访问对象,造成多次分配内存空间,所以要加上线程锁(互斥锁)
//    @synchronized (self) {
//        if (_instance == nil) {
//            _instance = [super allocWithZone: zone];
//        }
//        return _instance;
//    }

    //使用 GCD 创建一次对象也可以

    NSLog(@"1---%ld",onceToken);    //输出:0(onceToken)
    dispatch_once(&onceToken, ^{
        if (_instance == nil) {
            _instance = [super allocWithZone: zone];
            _instance.defaultValue = @"init defaultValue";
            NSLog(@"2---%ld",onceToken);  //输出:256(一个很大的数,此时线程阻塞状态)
        }
    });
    NSLog(@"3---%ld",onceToken);  //输出:-1 跳过dispatch_once的block代码
    return _instance;
}

onceToken 的运行结果
在这里插入图片描述
了解了dispatch_once的实现原理后,将实例instance=ni后,再次实例化后,得到的还是同样的结果,那么应该如何修改呢?

  • 想法二:关键就在与onceToken的值,添加一个方法,将onceToken置为0,同时将全局静态单例对象设置为 nil。这样才能保证正确释放单例对象。

单例的完整实现

+(instancetype)allocWithZone:(struct _NSZone *)zone {
    //使用 GCD 创建一次对象也可以
    NSLog(@"1---%ld",onceToken);    //输出:0(onceToken)
    dispatch_once(&onceToken, ^{
        if (_instance == nil) {
            _instance = [super allocWithZone: zone];
            _instance.defaultValue = @"init defaultValue";
            NSLog(@"2---%ld",onceToken);  //输出:256(一个很大的数,此时线程阻塞状态)
        }
    });
    NSLog(@"3---%ld",onceToken);  //输出:-1 跳过dispatch_once的block代码
    return _instance;
}

//手动创建一个方法,设置onceToken为 0
-(void)clear {
    onceToken = 0;
    //将全局静态单例对象设置为 nil
    _instance = nil;
}

调用单例对象

Tools *tools = [Tools shareTools];
    tools.name = @"step1 name";
    NSLog(@"step1---%@", tools);
    NSLog(@"step1 defaultValue = %@, name = %@", tools.defaultValue, tools.name);

    [tools clear];
  //  tools = nil;  //后面没有使用 tools 对象的话,在大括号执行完毕时, tools 也会被释放
    
    NSLog(@"step2---%@",tools);
        
    tools = [Tools shareTools];
    NSLog(@"step4---%@",tools);
    NSLog(@"step4 defaultValue = %@, name = %@", tools.defaultValue, tools.name);

打印结果:
在这里插入图片描述
这样操作正确释放了单例对象,成功创建了新的单例对象。

总结:一般定义了单例,就是需要整个类中只创建一个单例对象,可以给到全局使用,直到程序终止该对象才被释放。单例不能继承,也不能对类进行扩展。因此要正确使用单例对象。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值