【iOS】单例模式

发布于:2025-09-12 ⋅ 阅读:(18) ⋅ 点赞:(0)

前言

单例模式是很重要的一部分内容,这里重点总结复习一下。

定义

单例模式是一种设计模式,保证一个类在程序运行中只会有一个实例,并且提供全局访问点。这样防止一个实例被重复创建而占用内存空间,大大节省了内存。

优缺点

优点

  • 一个类只被实例化了一次,提供了对唯一实例的受控访问
    • 保证所有人拿到的是同一个对象
    • 保证在需要时才初始化,也就是之前博客中提到的懒加载
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
NSLog(@"%p %p", p1, p2);
//每次alloc init,会输出不同地址
MyManager *m1 = [MyManager sharedManager];
MyManager *m2 = [MyManager sharedManager];
NSLog(@"%p %p", m1, m2); 
//避免重复创建,输出的是相同地址
  • 节省系统资源

缺点

  • 全局状态:单例模式可能导致全局状态存在,就是程序不同模块都可能修改这个单例对象的状态,增加代码复杂性,且bug不好定位。
  • 难以扩展:单例模式的实例是固定的,导致很难扩展以支持多个实例。
  • 不适用于多线程环境:在多线程环境下,需要处理保证初始化代码只执行一次,而且线程是安全的。
    • 互斥锁:
    + (instancetype)sharedInstance {
        @synchronized (self) {
            if (_instance == nil) {
                _instance = [[self alloc] init];
            }
        }
        return _instance;
    }
    
    • dispatch_once
    + (instancetype)sharedInstance {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _instance = [[self alloc] init];
        });
        return _instance;
    }
    

种类

懒汉式

主要指在需要时才会创建单例对象的实例,而不是在程序启动时就立即创建(懒加载)。懒汉式通过延迟对象的初始化来节省资源和提高性能。

这里展示两种写法:

  • 双重检查锁
#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 = @"单例模式";
    NSLog(@"%@", _instance);
}

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

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

@end

这里详细解释一下这种写法中几个重要的需要注意的点:

  1. static id _instance:static用于保存类的唯一实例,保证进程中只有一份,更具体的将在单独写篇博客来比较static、const、extern关键字。
  2. 重写allocWithZone::双重检查锁来保证线程安全,当外部调用alloc init时,保证始终只分配一次内存。
  3. copyWithZone:mutableCopyWithZone::用于复制对象的方法,它们都被重写后返回同一个_instance,保证不会生成新对象,仍然符合单例模式的要求。
  • GCD写法
#import "sharedSingleton.h"

static id _instance = nil;

@implementation sharedSingleton

+(instancetype)sharedSingleton {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[super allocWithZone:NULL] init];
    });
    return _instance;
}

+(instancetype)allocWithZone:(struct _NSZone *)zone {
    return [sharedSingleton sharedSingleton];;
}

-(id)copyWithZone:(NSZone *)zone {
    return [sharedSingleton sharedSingleton];
}

-(id)mutableCopyWithZone:(NSZone *)zone {
    return [sharedSingleton sharedSingleton];
}

@end

dispatch_once主要是根据onceToken的值来决定怎么去执行代码:

  1. 当onceToken = 0时,线程执行block中的代码
  2. 当onceToken = -1时,线程跳过block中的代码不执行
  3. 当onceToken = 其他值时,线程被阻塞,等待onceToken值改变

饿汉式

饿汉式创建单例是在你的类加载时立即开始加载一个单例的一种单例模式,这种模式需要我们把加载单例的代码写在类第一次加载的位置。

#import "ehanSingleton.h"

static id _instance;

@implementation ehanSingleton

//当类加载到OC运行时的环境内存中调用
+(void)load {
    _instance = [[self alloc] init];
}

+(instancetype)ehanSingleton {
    return _instance;
}

+(instancetype)allocWithZone:(struct _NSZone *)zone {
    if (!_instance) {
        _instance = [super allocWithZone:zone];
    }
    return _instance;
}

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

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

@end

优缺点比较

  • 懒汉式:
    • 优点:
      • 懒加载:只有在第一次访问单例实例的时候才会被创建,这样可以节省资源,提高性能。
      • 线程安全:通过GCD或加锁来保证线程安全性。
      • 节省内存:如果单例对象很大或者初始化过程开销较大,懒汉模式可避免在程序启动时就创建不必要的对象。
    • 缺点:
      • 线程安全性的开销:为保证线程安全的操作带来性能上的开销。
  • 饿汉式:
    • 优点:
      • 创建方式简单:不出现多线程的问题。
      • 线程安全:实例在类创建时就创建,不会存在多个实例的风险,保证线程安全。
    • 缺点:
      • 性能问题:如果单例类的实例在启动程序时没有被使用,那么创建实例的开销可能是不必要的。
      • 无法实现延迟加载:饿汉模式在程序启动时就创建实例,无法实现延迟加载,可能会浪费资源,尤其是当实例很大或初始化开销较大时。

总结

总的来说,懒汉模式适用于需要延迟加载实例的情况,饿汉模式适用于需要简单实现和线程安全性的情况,具体情况具体分析。


网站公告

今日签到

点亮在社区的每一天
去签到