当前位置: 首页 > news >正文

【iOS】单例模式

【iOS】单例模式

什么是单例模式?

定义

单例模式,简单地说就是一个类只对应一个对象,每次使用这个类时,都只能获取到那一个对象。它的详细定义如下:

如果一个类始终只能创建一个实例,则这个类被称为单例类。 有一个全局的接口来访问这个实例。当第一次载入的时候,它通常使用延时加载的方法创建单一实例。 在程序中,一个单例类在程序中只能初始化一次,为了保证在使用中始终都是存在的,所以单例是在存储器的全局区域,在编译时分配内存,只要程序还在运行就会一直占用内存,在APP结束后由系统释放这部分内存 系统方法中,有未知的自动释放池,如果一个对象进行自动释放的话,又可能进入未知的自动释放池,出现内存问题。这就是单例模式不能自动释放的原因

特点

1.单例是一个类,创造出来的对象叫单例对象

2.单例对象使用类方法创建

3.单例一旦被创建,程序结束才会释放,也就是说,单例只初始化一次

4.单例不用程序员管理内存,内存随程序关闭而释放

为什么使用单例模式?

有时候我们需要一个全局的对象,而且要保证全局有且只有一份即可,这时候就需要用到单例设计模式,需要注意:在多线程的环境下做好线程保护。 一般在程序中,经常调用的类,如工具类、公共跳转类等都会采用单例模式

常见的单例模式

在OC语言中,有一些原生的单例类

UIApplication应用程序实例类
NSNotificationCenter消息中心类
NSFileManager文件管理类
NSUserDefaults应用程序设置
NSURLCache请求缓存类
NSHTTPCookieStorage应用程序cookies池

如何使用单例模式?

static关键字

在了解如何使用单例模式之前,我们先来了解一个与之相关的属性修饰关键字——static

static关键字用于修饰局部变量、全局变量和函数。修饰局部变量时,表示该局部变量存储在静态区,修饰全局变量时,表示限制该全局变量只能在当前文件中访问,修饰函数时,则表示限制函数只能在当前源文件中使用。

静态内存存储区在整个程序运行期间都存在

我们需要用static关键字来修饰单例变量,将变量限制在当前类的文件,否则在其他类中可以使用extern来拿到这个单例并修改

创建单例

我们来试着创建一个单例

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface singleton : NSObject
+ (id)instance;
@endNS_ASSUME_NONNULL_END#import "singleton.h"@implementation singleton
static id instance = nil;+ (id)instance {if (!instance) {instance = [[super allocWithZone:NULL] init];}return instance;
}@end#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "singleton.h"int main(int argc, char * argv[]) {NSString * appDelegateClassName;@autoreleasepool {NSLog(@"singleton:%d",[singleton instance] == [singleton instance]);NSLog(@"%d",[singleton instance] == [[singleton alloc] init]);}return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

运行后得到以下结果:

我们发现,当使用自定义的类方法创建对象时,的确实现了我们想要的效果,创建出了单例。但是,当使用alloc init创建对象时,得到的并不是单例,与使用类方法创建的对象也并不相同。

这个时候我们就需要重写alloc init方法,来保证无论使用什么方法创建,这个类始终只有一个对象。这时我们只需重写allocWithZone:方法就可以了。

我们平时在初始化对象时使用的方法[[Class alloc] init],其实做了两件事,alloc方法为对象分配内存空间,init是对对象的初始化(包括设置成员变量初值等工作)。而给对象分配内存空间,除了alloc方法,还有一个方法就是allocWithZone:。使用alloc方法时,其实也是调用了allocWithZone方法的,所以只需要在allocWithZone里重写,就可以覆盖所有会生成新的实例的方法

这里的allocWithZone:方法,它的参数是被忽略的,正确的做法是传nil或者NULL。

此外,为了防止copy方法生成新的实例,还要重写copyWithZone和mutableCopyWithZone。

​#import "singleton.h"@implementation singleton
static id instance = nil;+ (id)instance {if (!instance) {instance = [[super allocWithZone:NULL] init];}return instance;
}+ (instancetype)allocWithZone:(struct _NSZone *)zone{return instance;
}
-(id)copyWithZone:(NSZone *)zone{return instance;
}-(id)mutableCopyWithZone:(NSZone *)zone{return instance;
}@end

懒汉模式

创建单例有两种模式,分别是懒汉模式和饿汉模式。这两种模式的区别就在于在什么时候去创建对象,懒汉模式就是在第一次用到类的时候才会实例化,饿汉模式就是在类加载的时候就进行实例化。下面先介绍一下懒汉模式

懒汉模式一般用于访问量较小的时候,用时间换空间。通俗地说,懒汉模式就像一个大懒虫,这个懒虫现在有很多穿过的脏衣服,但是他却不打算洗这些衣服,等到实在没衣服穿了,他才会去洗这些衣服。

在使用懒汉模式的时候,要注意保证线程的安全性。

多线程和单线程

线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,也就是说不同的线程可以执行同样的函数。

多线程是指程序中包含多个执行流,也就是在一个程序中可以同时运行多个不同的线程来执行不同的任务

加锁

在单线程中,我们上面所用到的创建单例的方法其实就是懒汉模式,可以直接使用上面的方法来创建懒汉模式,而在多线程中,为了确保线程安全,我们可以用@synchronized来加锁,@synchronized可以防止不同的线程同时执行同一段代码,因此加锁后每次访问该代码就都只能一个行程进行访问。

#import "FirstMethod.h"@implementation FirstMethod
static id instance = nil;
+ (instancetype)instance {@synchronized (self) {if (!instance) {instance = [[super allocWithZone:NULL] init];}}return instance;
}
@end

这一段代码可以在确保线程安全的情况下实现单例,但是我们细看会发现调用insrance方法后,如果instance已经存在,可以直接将instance作为返回值传递出去,没有必要进入锁。于是我们加锁之前先做一次判断

#import "FirstMethod.h"@implementation FirstMethod
static id instance = nil;
+ (instancetype)instance {if (!instance) {@synchronized (self) {if (!instance) {instance = [[super allocWithZone:NULL] init];}}}return instance;
}
@end

注意:这里内层的if不能省略,因为如果省略了内层的if,只是对对象的创建和初始化加了锁,而不是对创建单例过程加锁,也就是说有可能会有两个线程同时进入外层的if,然后先后一个一个执行alloc init,这样就可能导致创建出两个不一样的对象。

GCD的dispatch_once

GCD:全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”,是苹果公司为多核的并行运算提出的解决方案,会自动利用更多的CPU内核(比如双核、四核),自动管理线程的生命周期(创建线程、调度任务、销毁线程),程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。

另一种确保线程安全的方法就是使用GCD的dispatch_once来创建单例

#import "singleton.h"@implementation singleton
static id instance = nil;+ (id)instance {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{instance = [[super alloc] init];});return instance;
}+ (instancetype)allocWithZone:(struct _NSZone *)zone{return instance;
}
@end

下面解释一下dispatch_once是怎样确保线程安全并创建单例的。

我们看到上述代码中出现了dispatch_once_t变量和dispatch_once函数

dispatch_once_t变量

这里的intptr_t长度是所在平台的位数,用来存放地址。

dispatch_once函数

dispatch_once无论单线程还是多线程,都只执行一次,在安全的前提下也保证了性能,同时实现了单例的创建。

当onceToken为0时,线程执行dispatch_once的block中的代码

当onceToken为-1时,线程跳过dispatch_once的block中的代码

当onceToken为其他值时,线程被阻塞,等待onceToken值改变

饿汉模式

饿汉模式就是像一个饿汉,在类加载时就完成这个单例的创建和初始化。

以下是饿汉模式的代码:

#import "singleton.h"@implementation singleton
static id instance = nil;+ (void)load {[super load];instance = [[self allocWithZone:NULL] init];
}+ (id)instance {instance = [[self allocWithZone:NULL] init];return instance;
}
@end

load方法:当类加载到运行环境中时调用且仅调用一次,同时一个类只会加载一次(程序启动时,所有类加载一次,不管有没有在当前视图中用到)

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 基于python+django+vue的图书管理系统
  • 传输层协议 —— TCP协议(上篇)
  • 学习Java(一)类和对象
  • 安卓开发,如何实现apk的代码混淆、日志混淆?
  • 音视频入门基础:AAC专题(10)——FFmpeg源码中计算AAC裸流每个packet的pts、dts、pts_time、dts_time的实现
  • 【d46】【Java】【力扣】234.回文链表
  • 详解QT元对象系统用法
  • RK3568部署DOCKER启动服务器失败解决办法
  • 实用小工具——多标签页插件Office Tab介绍
  • C++ 解析 RDP 协议
  • 分布式Redis(14)哈希槽
  • 数据可视化pyecharts——数据分析(柱状图、折线图、饼图)
  • 【算法——双指针】
  • 每日一题——第九十七题
  • 【掘金量化使用技巧】用日线合成长周期k线
  • Date型的使用
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • Fastjson的基本使用方法大全
  • Git同步原始仓库到Fork仓库中
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • JAVA 学习IO流
  • JavaScript 基础知识 - 入门篇(一)
  • JavaScript服务器推送技术之 WebSocket
  • java架构面试锦集:开源框架+并发+数据结构+大企必备面试题
  • JS学习笔记——闭包
  • Laravel Mix运行时关于es2015报错解决方案
  • laravel 用artisan创建自己的模板
  • Median of Two Sorted Arrays
  • Python 基础起步 (十) 什么叫函数?
  • Python_网络编程
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • Rancher如何对接Ceph-RBD块存储
  • React as a UI Runtime(五、列表)
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 力扣(LeetCode)965
  • 前嗅ForeSpider采集配置界面介绍
  • 如何解决微信端直接跳WAP端
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 用quicker-worker.js轻松跑一个大数据遍历
  • 掌握面试——弹出框的实现(一道题中包含布局/js设计模式)
  • Spring Batch JSON 支持
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • ​1:1公有云能力整体输出,腾讯云“七剑”下云端
  • ‌前端列表展示1000条大量数据时,后端通常需要进行一定的处理。‌
  • #nginx配置案例
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • (1综述)从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练
  • (八)Docker网络跨主机通讯vxlan和vlan
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
  • (接上一篇)前端弄一个变量实现点击次数在前端页面实时更新
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题