iOS—单例模式

单例模式

保证一个类只有一个实例,并且提供一个全局的访问入口

系统为我们提供了哪些单例类

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

不同的位置存放的变量以及释放时机
在这里插入图片描述
单例对象一旦创建,对象指针编译时分配内存,保存在静态区,程序结束运行时由系统释放,单例对象在堆中分配内存,同样也是在程序结束运行时才会被释放。

单例的优缺点

优:

缺:

单例的两种实现方式

懒汉式

不使用时不创建,第一次使用时再进行创建。
访问量较小时,采用懒汉实现,时间换空间

单线程下实现单例

//为确保通过alloc、copy、mutableCopy创建的对象不会产生新对象
//就需要重写一些方法,对于alloc,需要重写它的底层实现allocWithZone
//而此时我们不能通过当前类调用allocwithZone来创建实例,会死循环
//只能调用父类的allocWithZone
static id instance = nil;
+ (id)shareInstance {
	
	if (instance == nil) {
		instance = [super allocWithZone:NULL] init];
	}
}

以上代码在单线程下不会出现问题
如果是多线程下,可能出现多个线程进入if中,此时就会创建多个实例对象
我们可以加一个锁,以保证每次只有一个线程访问,一个访问完下一个再进行访问

static id instance = nil;
+ (id)shareInstance {
	@synchronized(self) {
		if (instance == nil) {
			instance = [super allocWithZone:NULL] init];
		}
	}
}

添加@synchronized后,可以确保每次只有一个线程可以访问,其他线程则进行等待。
但是存在的问题是,只有在instance未被创建时,加锁才是必要的。
如果已经创建,后面的线程仍进行加锁解锁,会影响性能

为此,还可以进行优化,在锁的外部进行判断,防止在对象已被创建,后面的线程进行多次加锁解锁。

static id instance = nil;
+ (id)shareInstance {
	//防止多次加锁解锁
	if (instance == nil) {
		@synchronized(self) {
			if (instance == nil) {
				instance = [super allocWithZone:NULL] init];
			}
		}
	}
}

但相比@synchronized,GCD中的dispatch_once更高效,它没有使用重量级的同步机制,性能也优于前者
所以,单例推荐写法如下

#import <Foundation/Foundation.h>
 
@interface Manager : NSObject <NSCopying, NSMutableCopying>

+ (instancetype)shareInstance;

@end


#import "Manager.h"

@implementation Manager 

+ (instancetype)shareInstance {
    static Manager *manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[super allocWithZone:NULL] init];
    });
    
    return manager;
}


//对下面三种方法的重写是为了保证通过alloc、copy、mutableCopy创建对象也是返回唯一的实例
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [Manager shareInstance];
}

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

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

dispatch_once 无论使用多线程还是单线程,都只执行一次,在安全的前提下也保证了性能。

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

  • 当onceToken为0时,线程执行dispatch_once的block中的代码
  • 当onceToken为-1时,线程跳过dispatch_once的block中的代码
  • 当onceToken为其他值时,线程被阻塞,等待onceToken值改变

dispatch_once的执行流程

  • 当线程调用shareInstance,此时onceToken为0,执行dispatch_once的block中的代码,此时onceToken中的值为其他值
  • 这时如果有其他线程再调用shareInstance方法时,onceToken值为其他值,线程阻塞
  • 当block线程执行完block后,onceToken变为-1。其他线程不再阻塞,跳过block
  • 下次再调用shareInstance时,onceToken为-1,直接跳过block
    在这里插入图片描述

饿汉式

类被加载时就创建单例对象,一个类在整个生命周期中只会被加载一次,所以单例类只会创建一个单例对象。
也就是说线程访问之前,单例对象就早已创建好了,访问时直接拿到这个唯一的对象。

在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能,空间换时间

//.m中
static Manager *manager = nil;

+ (instancetype)shareInstance {
    return manager;
}

+(void)load {
    [super load];
    manager = [[super allocWithZone:nil] init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return manager;
}

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

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

在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值