【iOS】 单例模式
1. 认识单例模式
首先让我们先看下关于单例模式的定义
(来自于《设计模式》(Addison-Wesley,1994))
一个类有且仅有一个实例,并且自行实例化向整个系统提供。
如果说每一个人都是一个类,那么从他出生开始,他就是生活中的唯一实例,每当有人要拜访或者联系你的时候,无论别人认识你的时候你是什么状态,他所能联系到的都是现在的你。你本身的状态会在其他地方发生改变,即当你状态改变后,后续所有找到你的,都是看到状态改变后的你。那么,我们就可以认为每一个人都处于单例模式。
在 iOS 中,系统默认就有许多支持单例方法的地方,例如通知中心、应用管理、本地保存等,但是一般都属于非严格的单例模式,也就是你可以使用
[UIApplication sharedApplication]
来获取单例对象,也可以通过[UIApplication new]
生成一个新的对象,而如果按照准确的单例定义来说,依旧以UIApplication
类为例,[UIApplication new]
与[UIApplication sharedApplication]
应该在任何时候返回的对象都是相同的。也就是说无论你怎么操作,只会存在一个实例,不会创建其他的副本内容。
2. 单例模式的使用
单例模式需要实现一个公共访问的类方法,一般命名为 shared + 类名。在该方法的具体实现方案,是推荐通过dispatch_once 来实现类的实例化。
可以直接通过重写父类的方法,把分配内存的方法变成只执行一次。从根本上实现了单例。
如果按照严格的单例写法的话,单例模式一共需要重写五个方法 :
+ (instancetype)allocWithZone:(struct _NSZone *)zone
+ (id)copyWithZone:(struct _NSZone *)zone
+ (id)mutableCopyWithZone:(struct _NSZone *)zone
- (id)copyWithZone:(NSZone *)zone
- (id)mutableCopyWithZone:(NSZone *)zone
或者,可以把这些方法全都通过attribute((unavailable (invalid ))); 方式禁止,以保证一定使用shhared的单例方法。
SingleView.h#import <Foundation/Foundation.h>@interface SingleView : NSObject#pragma mark- method
+ (SingleView *)sharedSingleView;
+ (id)alloc __attribute__((unavailable("invalid, use sharedSingleView instead")));
+ (id)new __attribute__((unavailable("invalid, use sharedSingleView instead")));
- (id)copy __attribute__((unavailable("invalid, use sharedSingleView instead")));
- (id)mutableCopy __attribute__((unavailable("invalid, use sharedSingleView instead")));@end
如果只是在自己的项目中使用的话,那么直接实现一个 shared 的类方法基本都能满足需求,由于是自己的内容,代码是受控的,实现并调用即可。而如果是对外的库的话(静态库、动态库),这需要根据具体的业务内容考虑是否需要按照严格的单例写法来实现了。
懒汉
懒汉式创建单例模式的意思其实就是指我们在创建的时间是我们需要用到这个单例的时候,我们才开始创建这个唯一实例,这种创建模式有助于提高性能,以及节省资源的效果。简单来说,就是我们平时日常生活中的deadline,只要在达到deadline的时候我们才去提交工作,这和他的名字也很相似,懒汉式延迟创建。
dispatch_once
//SingleView.h#import <Foundation/Foundation.h>@interface SingleView : NSObject
+ (SingleView *)sharedSingleView;
@end
//SingleView.m#import "SingleView.h"@implementation SingleView#pragma mark- public method
+ (SingleView *)sharedSingleView {static SingleView *single = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{single = [[super allocWithZone:NULL] init];});return single;
}+ (instancetype)allocWithZone:(struct _NSZone *)zone {return [SingleView shareSingleView];
}+ (id)copyWithZone:(struct _NSZone *)zone {return [SingleView shareSingleView];
}+ (id)mutableCopyWithZone:(struct _NSZone *)zone {return [SingleView shareSingleView];
}- (id)copyWithZone:(NSZone *)zone{return [SingleView shareSingleView];
}- (id)mutableCopyWithZone:(NSZone *)zone{return [SingleView shareSingleView];
}
@end
同步锁实现
static Singleton* instance = nil;
+(id) sharedInstance {if (instance == nil) {@synchronized (self) {if (instance == nil) {instance = [[super allocWithZone:NULL] init];}}}return instance;
}+(id) allocWithZone:(struct _NSZone *)zone {if (instance == nil) {@synchronized (self) {if (!instance) {instance = [[super allocWithZone:NULL] init];}}}return instance;
}-(id) copyWithZone:(NSZone *)zone {return self;
}-(id) mutableCopyWithZone:(NSZone *)zone {return self;
}
@end
饿汉
饿汉式创建单例则是在你的类加载的时候立刻就开始加载一个单例的一种单例模式,这种模式我们需要把我们的加载单例的代码写在类第一次加载的位置。
static id instance = nil;+ (void)load {instance = [[super allocWithZone:NULL] init];
}+ (instancetype)sharedInstance {return instance;
}+ (instancetype)allocWithZone:(struct _NSZone *)zone {return instance;
}- (instancetype)init {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{NSLog(@"init一次");});return self;
}- (id)copyWithZone:(NSZone *)zone {return self;
}- (id)mutableCopyWithZone:(NSZone *)zone {return self;
}
输出结果如下证明单例代码有效:
因为对block部分对不了解,引用学长博客中的一段话:
ispatch_once 主要是根据 onceToken 的值来决定怎么去执行代码。
1.当 onceToken = 0 时,线程执行 dispatch_once 的 block 中代码;
2.当 onceToken = -1 时,线程跳过 dispatch_once 的 block 中代码不执行;
3.当 onceToken 为其他值时,线程被阻塞,等待 onceToken 值改变。
当线程调用mySingleton方法时,此时 onceToken = 0,调用 block 中的代码,此时 onceToken =其他值。
当其他线程再调用 mySingleton 方法时,onceToken为其他值,线程阻塞。当 block 线程执行完 block之后,onceToken = -1,其他线程不再阻塞,跳过 block。下次再调用mySingleton方法 时, block 已经为-1,直接跳过 block。转载自[【iOS】—— 单例模式]
(https://blog.csdn.net/m0_73974920/article/details/132909916?spm=1001.2014.3001.5502)
这里又要注意一下要调用父类的【super allocWithZone:null】这个方法,因为我们这里已经重写这个类的方法了,不然会出现一个循环调用的问题。同时为了防止copy和mutablecopy两个方法的出现崩溃的问题。
3.总结
单例模式是一种非常常见、使用频率也很高的一种设计模式。单例能够在许多场合使用,有时候也可以用来保存数据。
它(严格单例)存在着以下特点:
节省内存;
在程序的运行周期中保存数据;
只能在自己的类中实现实例;
程序的运行周期中,内存不会被释放;
类中有且仅有一个实例;
懒汉模式:
· 优点:
延迟加载:懒汉模式只有在第一次访问单例实例时才会进行初始化,可以节省资源,提高性能,因为实例只有在需要时才会被创建。
节省内存:如果单例对象很大或者初始化过程开销较大,懒汉模式可以避免在程序启动时就创建不必要的对象。
线程安全性:可以通过加锁机制(如双重检查锁定)来实现线程安全。
· 缺点:
线程安全性开销:懒汉模式在实现线程安全时可能需要额外的同步机制,这会引入一些性能开销。
复杂性增加:实现线程安全的懒汉模式可能需要编写复杂的代码,容易引入错误。
缺点:
线程安全性开销:懒汉模式在实现线程安全时可能需要额外的同步机制,这会引入一些性能开销。
复杂性增加:实现线程安全的懒汉模式可能需要编写复杂的代码,容易引入错误。
饿汉模式:
优点:
简单:饿汉模式实现简单,不需要考虑线程安全问题,因为实例在类加载时就已经创建。
线程安全性:由于实例在类加载时创建,不会存在多个实例的风险,因此线程安全。
缺点:
无法实现延迟加载:饿汉模式在程序启动时就创建实例,无法实现延迟加载,可能会浪费资源,特别是当实例很大或初始化开销较大时。
可能引起性能问题:如果单例类的实例在程序启动时没有被使用,那么创建实例的开销可能是不必要的。
不适用于某些情况:如果单例对象的创建依赖于某些外部因素,而这些因素在程序启动时无法确定,那么饿汉模式可能不适用。
总的来说,懒汉模式适用于需要延迟加载实例的情况,可以节省资源和提高性能,但需要考虑线程安全性。饿汉模式适用于需要简单实现和线程安全性的情况,但不支持延迟加载。选择哪种模式应根据具体需求和性能考虑来决定。