iOS开发笔记之八十——单例的使用笔记

******阅读完此文,大概需要10分钟******

一、单例的创建

#import "MDInstanceManager.h"
 
@implementation MDInstanceManager
 
static MDInstanceManager *shareInstance = nil;
static dispatch_once_t onceToken;
 
+ (instancetype)shareInstance
{
    dispatch_once(&onceToken, ^{
        shareInstance = [[self alloc] init];
    });
    return shareInstance;
}
  
- (instancetype)init
{
    self = [super init];
    if (self) {}
    return self;
}
@end

单例的关键字static,被它修饰的对象,存储在静态存储区,会在当前内存中保持唯一性和持久性(对象会在程序的整个运行期间都存在);上面的单例写法是最为常见的做法,dispatch_once保证了线程安全性,但是如果要保证真正的唯一性,还不够。

二、“严格”的单例

+ (id)allocWithZone:(NSZone *)zone
{
    if(!shareInstance) {
        shareInstance = [super allocWithZone:zone];
    }
    return shareInstance;
}
 
- (id)copy
{
    return shareInstance;
}
 
- (id)mutableCopy
{
    return shareInstance;
}

创建对象的步骤分为两步:(1)申请内存(alloc);(2)初始化(init) 这两个步骤;

因为外部的操作是不可控的,为了保证严格的唯一性,因此在第一步这个阶段我们就要拦截它;当我们调用alloc方法时,OC内部会调用allocWithZone这个方法来申请内存,我们覆写这个方法,然后在这个方法中调用shareInstance方法返回单例对象,这样就可以达到我们的目的。

拷贝对象也是同样的原理,覆写copy方法,然后在这个方法中调用shareInstance方法返回单例对象。

这样以来:

MDInstanceManager *instanceManager = [MDInstanceManager shareInstance];
NSLog(@"-->%@",instanceManager);
NSLog(@"-->%@",[[MDInstanceManager alloc]init]);
NSLog(@"-->%@",[instanceManager copy]);
NSLog(@"-->%@",[instanceManager mutableCopy]);
2020-06-30 11:21:53.886952+0800 MDProject[4723:1906563] --><MDInstanceManager: 0x600002455070>
2020-06-30 11:21:53.887055+0800 MDProject[4723:1906563] --><MDInstanceManager: 0x600002455070>
2020-06-30 11:21:56.200656+0800 MDProject[4723:1906563] --><MDInstanceManager: 0x600002455070>
2020-06-30 11:21:56.840126+0800 MDProject[4723:1906563] --><MDInstanceManager: 0x600002455070>

以上四种操作的内存地址都是一样的;

当然你也可以采取更为粗暴的方法,如下:

- (instancetype)init __attribute__((unavailable("replace with 'sharedInstance'")));
+ (instancetype)alloc __attribute__((unavailable("replace with 'sharedInstance'")));
+ (instancetype)new __attribute__((unavailable("replace with 'sharedInstance'")));
- (instancetype)copy __attribute__((unavailable("replace with 'sharedInstance'")));
- (instancetype)mutableCopy __attribute__((unavailable("replace with 'sharedInstance'")));

三、单例优缺点

优点:

  • 提供了应用唯一的实例对象,规范化统一管理资源,即提供了对唯一实例的受控访问; 

  • 不用再频繁地创建和销毁对象,从而提高了系统的性能和节约系统资源;

  • 单例对象可以做到按需创建对象或加载资源,以节省不必要的内存;

  • 避免对共享资源的多重占用;

缺点:

  • 单例从创建后到彻底关闭程序前都会一直存在,如果过多的创建单例无疑浪费系统资源和影响系统效率;

  • 由于单例模式中没有抽象层接口,因此单例类很难再进行扩展;

  • 单例类的职责过重,在一定程度上违背了“单一职责原则”,长期的累积会导致难以维护;

  • 因此可以把单例模式作为最后的兜底方案考虑,不宜过多使用;

四、单例的销毁

单例是可以销毁的,如下:

- (void)destroy
{
    shareInstance = nil;
    onceToken = 0;
}

五、Weak单例


static MDInstanceManager *shareInstance = nil;
以上static修饰的默认用一个强指针来持有这个单例,如果当前业务场景退出后,对应的单例管理对象,也没有用了,当然,也可以手动释放这个单例对象,但是业务使用中,需要注意的是释放的时机;
static __weak MDWeakInstanceManager *weakInstance = nil;
但是,如果改用OC特有的weak修饰,让对应的VC去持有它,那么这个weak单例就会自动伴随对应的VC释放而释放,而不需要我们去手动在干预;对于复杂的业务场景,我们常常需要一个管理类来作为中间者,weak单例无疑是一种很好的选择。

为了规范化这个过程,在实际开发中,我定义了一个protocol才实现VC对weak单例的持有,让对应的持有者去实现它,如下:
@protocol MDWeakInstanceManagerDelegate <NSObject>
 
- (void)assignInstance:(MDWeakInstanceManager *)instance;
 
@end

持有者VC的实现代码如下:

@interface MDWeakInstanceViewController ()<MDWeakInstanceManagerDelegate>
 
@property (nonatomic, strong) MDWeakInstanceManager *weakInstanceManager;
  
@end
  
@implementation MDWeakInstanceViewController
  
- (void)viewDidLoad
{
    [super viewDidLoad];
    [MDWeakInstanceManager buildInstance:self];
}
  
#pragma mark -- MDWeakInstanceManagerDelegate
 
- (void)assignInstance:(MDWeakInstanceManager *)instance
{
    self.weakInstanceManager = instance;
}
  
@end

单例中实现如下:

static __weak MDWeakInstanceManager *weakInstance = nil;
  
+ (void)buildInstance:(id)delegate;
{
    MDWeakInstanceManager *strongInstance = weakInstance;
    @synchronized(self) {
        if (!strongInstance) {
            strongInstance = [[[self class] alloc] init];
            weakInstance = strongInstance;
        }
    }
    strongInstance.delegate = delegate;
    if (strongInstance.delegate && [strongInstance.delegate respondsToSelector:@selector(assignInstance:)]) {
        [strongInstance.delegate assignInstance:strongInstance];
    }
}
 
//访问时须用此方法
+ (MDWeakInstanceManager *)shareInstance
{
    return weakInstance;
}

这样我们在持有者VC的业务场景中,都可以使用 [NMLKPBMusicManager shareInstance],而一旦它的持有者VC释放了,[NMLKPBMusicManager shareInstance]也会自动变为nil,无需手动干涉;

 

参考文献

https://www.cnblogs.com/NerdFooProgrammer/p/4870260.html

https://www.yunbook.vip/post/1547606575302.html

http://www.cocoachina.com/articles/19857

 

热门文章

暂无图片
编程学习 ·

一篇文章带你搞懂 SpringBoot 的配置文件

文章目录一、SpringBoot 配置文件类型1. SpringBoot配置文件类型和作用2. application.yml配置文件3. SpringBoot配置信息的查询二、配置文件与配置类的属性映射方式1. 使用注解@Value映射2. 使用注解@ConfigurationProperties映射 一、SpringBoot 配置文件类型 1. SpringBoot配…
暂无图片
编程学习 ·

leetcode718. 最长重复子数组/动态规划,滑动窗口

文章目录题目:leetcode718. 最长重复子数组/动态规划基本思想1:动态规划基本思想2:滑动窗口基本思想3:暴力 题目:leetcode718. 最长重复子数组/动态规划 给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。 示例 1: 输入: A: [1,2,3,2,1] B: [3,2,1…
暂无图片
编程学习 ·

Java实训心得一

Java第一次实训 第一次实训任务很简单 一: 编写学生管理系统功能结构图利用xmind编写结构图 图示:二:用Navicat工具创建数据库及表格创建student数据库 2.创建t_colledge表并进行设计,插入数据创建t_status表并进行设计,插入数据创建t_student表并进行设计,插入数据创建t…
暂无图片
编程学习 ·

Scanner对象

Scanner对象 作为输入使用,主要有两种接收键盘输入字符的方法,next()方法和nextLine()方法,下面介绍一下 import java.util.Scanner;public class demo01 {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.println("请输…
暂无图片
编程学习 ·

Spring-@Order注解

一、@Order 注解@Order的作用是定义Spring容器加载Bean的顺序 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Documented public @interface Order {/*** 默认最低优先级*/int value() default Ordered.LOWEST_PR…
暂无图片
中恒嘉业 ·

Heap Sort 讲解

Heap Sort sorts a group of unordered elements using the Heap data structure. The sorting algorithm using a Min Heap is as follows: Heapify all elements into a Min HeapRecord and delete the top elementPut to top element into an array T that stores all so
暂无图片
cgfy ·

8. 源码分析之ConsumeQueue

源码分析之ConsumeQueue 消息发送时数据在ConsumeQueue的落地 ​ 连续发送5条消息&#xff0c;消息是不定长&#xff0c;首先所有信息先放入 Commitlog中&#xff0c;每一条消息放入Commitlog的时候都需要上锁&#xff0c;确保顺序的写入。 ​ 当Commitlog写成功了之后。数据…
暂无图片
coreui ·

Heap Sort 讲解

Heap Sort sorts a group of unordered elements using the Heap data structure. The sorting algorithm using a Min Heap is as follows: Heapify all elements into a Min HeapRecord and delete the top elementPut to top element into an array T that stores all so
暂无图片
coreui ·

[react] 你觉得react上手快不快?它有哪些限制?

[react] 你觉得react上手快不快&#xff1f;它有哪些限制&#xff1f; 相对vue来说不快。 限制 需要学习JSX需要工程化的配置需要对原生JavaScript有相当的掌握react只是一个UI层面的库&#xff0c;像vue内置了动画处理、keep-alive等功能&#xff0c;react则需要去找第三方库…
暂无图片
未来博客 ·

Heap Sort 讲解

Heap Sort sorts a group of unordered elements using the Heap data structure. The sorting algorithm using a Min Heap is as follows: Heapify all elements into a Min HeapRecord and delete the top elementPut to top element into an array T that stores all so
暂无图片
未来博客 ·

[react] 你觉得react上手快不快?它有哪些限制?

[react] 你觉得react上手快不快&#xff1f;它有哪些限制&#xff1f; 相对vue来说不快。 限制 需要学习JSX需要工程化的配置需要对原生JavaScript有相当的掌握react只是一个UI层面的库&#xff0c;像vue内置了动画处理、keep-alive等功能&#xff0c;react则需要去找第三方库…
暂无图片
建站日记 ·

[react] 你觉得react上手快不快?它有哪些限制?

[react] 你觉得react上手快不快&#xff1f;它有哪些限制&#xff1f; 相对vue来说不快。 限制 需要学习JSX需要工程化的配置需要对原生JavaScript有相当的掌握react只是一个UI层面的库&#xff0c;像vue内置了动画处理、keep-alive等功能&#xff0c;react则需要去找第三方库…
暂无图片
建站日记 ·

STL Practice —— 【map (1)】

Description 给出学生姓名和分数&#xff0c;要求你输入姓名查询分数。 Input 输入包含T组测试数据。 开头是一个正整数T (0<T<10)&#xff0c;为测试数据数量。 对于每组测试数据&#xff0c;第一行是一个正整数N (0<N<100000)。 接下来有N行&#xff0c;每行包…
暂无图片
mfbz ·

AOV网是否存在回路-拓扑排序-C++

拓扑排序是对测试AOV网是否存在回路的方法&#xff01; 拓扑排序的过程中&#xff0c;由于需要查找所有以某顶点为尾的弧&#xff0c;即找到该顶点的所有出边&#xff0c;故图要采用邻接表的存储方式。但拓扑排序较邻接表的存储方式有一点不同&#xff0c;由于要查找入度为0的点…
暂无图片
mfbz ·

[react] 你觉得react上手快不快?它有哪些限制?

[react] 你觉得react上手快不快&#xff1f;它有哪些限制&#xff1f; 相对vue来说不快。 限制 需要学习JSX需要工程化的配置需要对原生JavaScript有相当的掌握react只是一个UI层面的库&#xff0c;像vue内置了动画处理、keep-alive等功能&#xff0c;react则需要去找第三方库…
暂无图片
珊珊日记 ·

AOV网是否存在回路-拓扑排序-C++

拓扑排序是对测试AOV网是否存在回路的方法&#xff01; 拓扑排序的过程中&#xff0c;由于需要查找所有以某顶点为尾的弧&#xff0c;即找到该顶点的所有出边&#xff0c;故图要采用邻接表的存储方式。但拓扑排序较邻接表的存储方式有一点不同&#xff0c;由于要查找入度为0的点…
暂无图片
珊珊日记 ·

8. 源码分析之ConsumeQueue

源码分析之ConsumeQueue 消息发送时数据在ConsumeQueue的落地 ​ 连续发送5条消息&#xff0c;消息是不定长&#xff0c;首先所有信息先放入 Commitlog中&#xff0c;每一条消息放入Commitlog的时候都需要上锁&#xff0c;确保顺序的写入。 ​ 当Commitlog写成功了之后。数据…