1、单例定义

2、单例的作用

3、单例使用场合

4、优点

5、缺点

6、注意:

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

7、创建单例对象步骤

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

#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源码
在这里插入图片描述

查看代码验证源码

+(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);

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

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

原文地址:https://blog.csdn.net/same_life/article/details/127306803

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_6015.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注