【Objective-C】单例模式

单例模式定义


单例模式:保证一个类有且仅有一个实例,并提供一个访问ta的全局访问点(易于外界访问)

在 iOS 中,系统默认就有许多支持单例方法的地方,例如通知中心、应用管理、本地保存等,但是一般都属于非严格的单例模式,也就是你可以使用[UIApplication sharedApplication]来获取单例对象,也可以通过[UIApplication new] 生成一个新的对象,而如果按照准确的单例定义来说,依旧以UIApplication 类为例, [UIApplication new][UIApplication sharedApplication]应该在任何时候返回的对象都是相同的。也就是说无论你怎么操作,只会存在一个实例,不会创建其他的副本内容

什么时候使用单例?


  • 类只能有一个实例,而且必须从一个为人熟知的访问点对其进行访问,比如工厂方法
  • 这个唯一的实例只能通过子类化进行扩展,而且扩展的对象不会破坏客户端代码
  • 为了节省内存,部分类可以使用单例模式,例如多处使用的图片等

单例类图如下:
请添加图片描述
不同客户端调用SingletonshareInstance方法生成的只有唯一的一个Singleton对象
本质就是让不同的指针指向同一块内存

具体代码实现

单例模式一般会封装一个静态属性(私有静态变量),并提供静态实例的创建方法(公有静态变量)

单例通常有两种写法:

  1. 一般写法
@interface Singleton : NSObject 

//获得单例实例方法
+ (instancetype)shareInstance;

@end

@implementation Singleton

//私有静态变量
static Singleton* instanceVariable = nil;

+ (instancetype)shareInstance {
    if (!instanceVariable) {
        instanceVariable = [[Singleton alloc]init];
    }

    return instanceVariable;
}

@end

在成员变量部分用static创建静态对象,保证返回对象的唯一性
但这样写会在多线程情况下导致重复创建,这样会存在两块内存,不安全。通常会添加一个同步锁

@implementation Singleton

static Singleton* instanceVariable = nil;

+ (instancetype)shareInstance {
    
    @synchronized (self) {
        if (!instanceVariable) {
            instanceVariable = [[Singleton alloc]init];
        }
    }
    
    return instanceVariable;

}

@end

继续优化,为防止每次读取造成卡顿,再在外层加一层判断

@implementation Singleton

static Singleton* instanceVariable = nil;

+ (instancetype)sharedInstance {

    //判断对象是否存在,若不存在,创建对象
    if (!instanceVariable) {
        //线程安全:仅仅只让一个线程进行,一个线程结束后,才能进行另一个线程
        @synchronized (self) {
            if (!instanceVariable) {
                instanceVariable = [[Singleton alloc] init];
            }
        }
    }

    return instanceVariable;

}

@end

接下来测试一下整个程序中是否有仅含有一个单例:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Singleton* singleV1 = [Singleton sharedInstance];
        NSLog(@"%p", singleV1);
        Singleton* singleV2 = [Singleton sharedInstance];
        NSLog(@"%p", singleV2);
    }
    return 0;
}

运行结果:
在这里插入图片描述

可以看出声明的两个指针都指向同一块内存(地址),这表明自始至终仅有一个对象(实例)存在

  1. GCD写法

GCD(Grand Central Dispatch)是一种基于C语言的多线程访问技术,GCD写法是苹果官方提供的单例的实现写法

+ (instancetype)sharedInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instanceVariable = [[Singleton alloc] init];
        //instanceVariable = [[self alloc] init];
    });
    
    return instanceVariable;
}

这段代码中GCD提供的

  • dispatch_once函数,作用是在整个应用程序生命周期中只执行一次代码块(^{ … })
  • dispatch_once_t结构体,使用时需要将GCD地址(也是为了保证唯一性)传给dispatch_once函数

dispatch_once函数不仅意味着代码仅会被运行一次,而且意味着此运行还是线程同步的

为了更形象地理解以上这段代码,我们利用NSLog函数打印消息来看一下:

void haha(void) {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"Hahahahah233");
    });
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        haha();
        haha();
        haha();
        haha();
        haha();
    }
    return 0;
}

用C风格函数封装打印Hahahahah233,然后在主函数里调用5次,看看结果
请添加图片描述
调了5遍,只打印了1遍,说明GCD提供的这段代码可以将我们想实现的代码只实现1次


单例模式的缺点

这是需要了解的点

  • 单例实例一旦创建,对象指针是保存在静态区的,那么在堆区分配空间只有在应用程序终止后才会被释放。单例对象只要程序在运行中就会一直占用系统内存,该对象在闲置时并不能销毁,在闲置时也消耗了系统内存资源
  • 单例不能被继承,不能有子类,因为它们共享一份资源,有子类单例模式就没有意义了

为什么要用static修饰私有变量?

在单例模式中,我们希望只创建一个类的实例,并且在整个应用程序生命周期内共享这个实例。通过使用static修饰全局变量,我们可以确保该变量只被初始化一次,并且在后续的访问中保持其状态

另外,使用static修饰全局变量还有几个好处:

  • 静态变量对外部不可见,无法直接访问或修改,可以保护全局变量的安全性
  • 静态变量不会被其他类或实例所覆盖,确保单例实例的唯一性
  • 静态变量属于类级别,可以通过类名直接访问,方便全局使用

总结

单例模式还有很多实现方式,但现在在主流的iOS开发中都是用GCD写法的,其他实现方法了解即可,可以作为我们理解单例模式的辅助

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值