【iOS】单例模式

2023-09-15 22:06:16


前言

在最初进行OC的学习时笔者了解过单例模式的基本使用,现撰写博客加深对单例模式的理解


一、单例模式简介

单例模式是一种常见的设计模式,其主要目的是确保一个类只有一个实例,并提供全局访问点。这样就大大节省了我们的内存,防止一个实例被重复创建从而占用内存空间。这种模式在需要一个共享资源或对象的情况下非常有用,但也有一些优点和缺点。

面向对象应用程序中的单例类(singleton class)总是返回自己的同一个实例。它提供了对象所提供的资源的全局访问点。与这类设计相关的设计模式称为单例模式。
大家在开发过程中也见过不少的单例,比如UIApplication、UIAccelerometer(重力加速)、NSUserDefaults、NSNotificationCenter,当然,这些是开发Cocoa Touch框架中的,在Cocoa框架中还有NSFileManager、NSBundle等。

二、单例模式优缺点

优点

  • 一个类只被实例化一次,提供了对唯一实例的受控访问
  • 节省系统资源
  • 允许可变数目的实例

缺点

  • 全局状态:单例模式可能导致全局状态的存在,这使得程序更难调试和理解。全局状态的改变可能会影响多个部分,增加了代码的复杂性。
  • 难以扩展:由于单例模式的实例是固定的,因此很难扩展以支持多个实例。如果需要多个实例,必须修改代码,将单例模式改为允许多个实例。
  • 隐藏依赖关系:使用单例模式的代码可能会隐藏对单例类的依赖关系,这使得代码更加耦合。这可能使单元测试和模块化难以实现。
  • 不适用于多线程环境:在多线程环境下,需要特殊的处理来确保单例模式的线程安全性。否则,可能会出现多个实例被创建的情况。

三、模式介绍

1.懒汉模式

懒汉模式(Lazy Initialization)是一种常见的单例设计模式,其主要特点是在需要时才会创建单例对象的实例,而不是在程序启动时就立即创建。懒汉模式通过延迟对象的初始化来节省资源和提高性能

举个简单的例子,你现在有衣服要洗,但是可以选择先不洗,等没衣服穿的时候再洗,这就是懒汉模式的逻辑——==需要用到实例时再去创建实例。


我们给出懒汉模式的代码:

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController<NSCopying, NSMutableCopying>
+ (instancetype)shareSingleton;

@end

ViewController.m

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

static id _instance;

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    if (_instance == nil) {//互斥锁防止被频繁加锁
        @synchronized (self) {
            if (_instance == nil) {//防止单例被多次创建
                _instance = [super allocWithZone:zone];
            }
        }
    }
    return _instance;
}

+ (instancetype)shareSingleton {
    if (_instance == nil) { //防止被频繁枷锁
        @synchronized (self) {
            if (_instance == nil) {
                _instance = [[self alloc] init];
            }
        }
    }
    return _instance;
}



- (void)viewDidLoad {
    [super viewDidLoad];
    _instance = [[NSString alloc] init];
    _instance = @"111";
    NSLog(@"%@", _instance);
}


- (nonnull id)copyWithZone:(nullable NSZone *)zone {
    return _instance;
}

- (nonnull id)mutableCopyWithZone:(nullable NSZone *)zone {
    return _instance;
}
//copyWithZone: 和 mutableCopyWithZone: 方法是用于复制对象的方法。在这里,它们都被重写为返回单例对象的引用 _instance,以确保复制操作也得到同一个单例对象的引用,而不会创建新的对象副本。

@end

使用懒汉模式时我们有几个问题值得我们讨论:

  1. 这样创建单例是否可以?
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    if (_instance == nil) {//互斥锁防止被频繁加锁
        @synchronized (self) {
                _instance = [super allocWithZone:zone];
        }
    }
    return _instance;
}

答案是不行的,因为我们只是锁住了对象的创建,如果两个线程同时进入if,那么就会产生两个对象

  1. 为什么要用static修饰单例?

如果我们不使用static,其他类中可以使用extern来拿到这个单例

extern id instance;
instance = nil;

如果在其他类中对单例进行如下操作,那么单例就会被重新创建,我们原本的单例对象中的内容就被销毁了。

具体用法可以参考这篇博客:【iOS】浅析static,const,extern关键字

  1. allocWithZone 与copyWithZone方法

通过代码可以看到我们的allocWithZone与shareSingleton方法是相同的,我们既可以通过shareSingleton创建单例,也可以通过allocWithZone创建单例。

copyWithZone: 和 mutableCopyWithZone: 方法是用于复制对象的方法。在这里,它们都被重写为返回单例对象的引用 _instance,以确保复制操作也得到同一个单例对象的引用,而不会创建新的对象副本。

2. 饿汉模式

在没有使用代码去创建对象之前,这个对象已经加载好了,并且分配了内存空间,当你去使用代码创建的时候,实际上只是将这个原本创建好的对象拿出来而已。

通俗的理解就是我们可以选择一回家就吃饭,也可以选择回家后躺一会再吃,躺一会再吃就是懒汉模式,直接吃就是饿汉模式

在我们的类加载过程中我们的单例就已经被创建了,我们无需在后面再去创建,直接使用即可

这里给出代码示例:

ehanViewController.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface ehanViewController : UIViewController<NSMutableCopying, NSCopying>

//获取单例
+ (instancetype)sharedSingleton;

@end

NS_ASSUME_NONNULL_END


ehanViewController.m

#import "ehanViewController.h"

@interface ehanViewController ()

@end

@implementation ehanViewController

static id _instance;

//当类加载到OC运行时环境中(内存),就会调用一次(一个类之后加载一次)
+ (void)load {
    _instance = [[self alloc] init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    if (_instance == nil) {  //防止创建多次
        _instance = [super allocWithZone:zone];
    }
    return _instance;
}

+(instancetype)sharedSingleton {
    return _instance;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    _instance = @"111";
    NSLog(@"%@", _instance);
}

- (nonnull id)copyWithZone:(nullable NSZone *)zone {
    return _instance;
}

- (nonnull id)mutableCopyWithZone:(nullable NSZone *)zone {
    return _instance;
}


@end

总结

懒汉模式:

  • 优点:
    延迟加载:懒汉模式只有在第一次访问单例实例时才会进行初始化,可以节省资源,提高性能,因为实例只有在需要时才会被创建。
    节省内存:如果单例对象很大或者初始化过程开销较大,懒汉模式可以避免在程序启动时就创建不必要的对象。
    线程安全性:可以通过加锁机制(如双重检查锁定)来实现线程安全。
    缺点:
    线程安全性开销:懒汉模式在实现线程安全时可能需要额外的同步机制,这会引入一些性能开销。
    复杂性增加:实现线程安全的懒汉模式可能需要编写复杂的代码,容易引入错误。
  • 缺点:
    线程安全性开销:懒汉模式在实现线程安全时可能需要额外的同步机制,这会引入一些性能开销。
    复杂性增加:实现线程安全的懒汉模式可能需要编写复杂的代码,容易引入错误。

饿汉模式:

  • 优点:
    简单:饿汉模式实现简单,不需要考虑线程安全问题,因为实例在类加载时就已经创建。
    线程安全性:由于实例在类加载时创建,不会存在多个实例的风险,因此线程安全。
  • 缺点:
    无法实现延迟加载:饿汉模式在程序启动时就创建实例,无法实现延迟加载,可能会浪费资源,特别是当实例很大或初始化开销较大时。
    可能引起性能问题:如果单例类的实例在程序启动时没有被使用,那么创建实例的开销可能是不必要的。
    不适用于某些情况:如果单例对象的创建依赖于某些外部因素,而这些因素在程序启动时无法确定,那么饿汉模式可能不适用。

总的来说,懒汉模式适用于需要延迟加载实例的情况,可以节省资源和提高性能,但需要考虑线程安全性。饿汉模式适用于需要简单实现和线程安全性的情况,但不支持延迟加载。选择哪种模式应根据具体需求和性能考虑来决定。

更多推荐

JavaMail发送和接收邮件API(详解)

JavaMail发送和接收邮件API(详解)一、JavaMail概述:JavaMail是由Sun定义的一套收发电子邮件的API,不同的厂商可以提供自己的实现类。但它并没有包含在JDK中,而是作为JavaEE的一部分。厂商所提供的JavaMail服务程序可以有选择地实现某些邮件协议,常见的邮件协议包括:SMTP:简单邮件

尝试访问启动磁盘设置时出错怎么办?

当出现“尝试访问启动磁盘设置时出错”这样的错误提示,而且启动转换控制面板打不开了时,是无法开启触摸板功能的。我们可以使用以下方法来解决问题。1.在Windows桌面左下角搜索框输入“计算机管理”后点击“打开”。2.点击“本地用户与组”,鼠标右键点击“用户”,选择“新用户”。3.设置用户名为Apple,然后设置密码。其他

GitHub Copilot Chat

9月21日,GitHub在官网宣布,所有个人开发者可以使用GitHubCopilotChat。用户通过文本问答方式就能生成、检查、分析各种代码。据悉,GitHubCopilotChat是基于OpenAI的GPT-4模型打造而成,整体使用方法与ChatGPT类似。例如,能帮我生成一个Python的吃豆小游戏代码,并加上代

什么是接口测试,我们如何实现接口测试?

1.什么是接口测试顾名思义,接口测试是对系统或组件之间的接口进行测试,主要是校验数据的交换,传递和控制管理过程,以及相互逻辑依赖关系。其中接口协议分为HTTP,WebService,Dubbo,Thrift,Socket等类型,测试类型又主要分为功能测试,性能测试,稳定性测试,安全性测试等。在分层测试的“金字塔”模型中

25 DRF详细学习篇章二|Parsers|Renderers|Serializers

文章目录Parsers解析器解析的过程设置全局解析器使用局部解析器常见的接口函数JSONParserFormParser和MultiPartParserFileUploadParserRenderers渲染器解析的过程设置全局渲染器使用局部渲染器常用接口函数JSONRendererTemplateHTMLRendere

RayViz 一款服务于SolidWorks用于光学和机械设计的有效工具

RayViz作为SolidWorks的一个扩展插件,不仅可以在SolidWorks工作环境中直接定义和保存模型光学属性,而且还可以在该CAD工作环境中直接进行光线追迹。通过RayViz,把TracePro和Solidworks链接了起来,一个模型可以在TracePro中用于光学计算和分析,也可以在Solidworks中

js前端条件语句优化

背景在实际开发中,由于应用需求可能存在多种情况场景,那处理时就需要列举所有对应的情况去处理,常见的处理可能会用到if…else去处理。但是如果条件判断太多,就会导致代码过于冗余难以维护,因此我们可以使用其他的方式去优化、较少代码冗余量。使用1、借用Array的方法1.1多个判断用includes或者indexOf举个例

2023华为杯研究生数学建模C题分析

完整的分析查看文末名片获取!问题一在每个评审阶段,作品通常都是随机分发的,每份作品需要多位评委独立评审。为了增加不同评审专家所给成绩之间的可比性,不同专家评审的作品集合之间应有一些交集。但有的交集大了,则必然有交集小了,则可比性变弱。请针对3000支参赛队和125位评审专家,每份作品由5位专家评审的情况,建立数学模型确

【Linux】系统级文件操作&&文件系统的概念

我们在C语言都学过文件操作,例如fopen,fclose之类的函数接口,在C++中也有文件流的IO接口,那不仅仅是C/C++,python、java、go、hph等等这些语言也都有自己的文件操作的IO接口。那有没有一种统一的视角来看待这些文件操作呢?它们的底层原理到底是什么?下面我们就来好好谈一谈:目录一、Linux操

数据结构-----串(String)详解

目录前言1.串的定义相关类型2.串的储存结构顺序储存表示堆分配储存表示块链储存表示3.串的操作方式4.串的匹配算法(1)BF算法过程原理代码实现(C/C++)算法分析(2)KMP算法过程原理匹配过程:获取next数组:代码实现(C/C++)算法分析前言前面我们学习了顺序表和线性表,这两种数据结构的储存数据域可以是一个任

EasyCode整合mybatis-plus的配置

文章目录entitymapper.javamapper.xmlserviceserviceImplcontroller这篇文章不教你如何安装和使用EasyCode,只是贴出可以使用的配置。具体EasyCode的使用可以查看其它的文章。entity##导入宏定义$!{define.vm}##保存文件(宏定义)#save(

热文推荐