• 【iOS】单例模式



    前言

    在最初进行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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

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

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

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

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

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

    extern id instance;
    instance = nil;
    
    • 1
    • 2

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

    具体用法可以参考这篇博客:【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
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    总结

    懒汉模式:

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

    饿汉模式:

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

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

  • 相关阅读:
    在Jetson Nano上安装ncnn深度学习框架
    Javaweb-JSP详解
    使用Kubesec检查YAML文件安全
    逆波兰表达式
    Eureka注册中心
    Qt 将qsqlite数据库中的数据导出为Excel表格
    盘点近年来面试常见的spring面试真题
    JUC_8种锁情况
    无人机基础知识:多旋翼无人机系统基本组成
    Java错题归纳day11
  • 原文地址:https://blog.csdn.net/weixin_72437555/article/details/132911455