这几天闲着没事,就看了看iOS的一些设计模式。iOS的设计模式有各种各样,比如说属于创建型设计模式的单例模式和工厂模式,结构型设计模式的MVC设计模式、装饰器设计模式、适配器设计模式、外观设计模式和组合设计模式、还有行为型设计模式的观察者设计模式、备忘录设计模式等等。这么多的设计模式,我们该如何去设计和使用它们呢?不急,我们需要慢慢来接触它们。那么今天这篇文章,我们就来谈谈iOS设计模式中创建型的设计模式——单例。
什么是单例模式呢?顾名思义,一个类在整个程序中只有一个实例对象,并且易于外界访问,这就是单例。和普通的实现相对比,单例模式只有一个实例对象和外界接触,这就是单例的特点。在iOS的SDK中,也存在着许多通过单例设计的类,比如[NSUserDefaults standardUserDefaults],[UIApplication sharedApplication], [UIScreen mainScreen], [NSFileManager defaultManager],所有的这些方法都返回一个单例对象。
那么,我们什么时候使用单例模式呢?单例一般都是用做管理资源的中心的,用来共享信息。比如在Model层中,你可以通过单例在获取本地文件中的信息,并在Controller中用单例获取数据。写到这我想到了之前初识单例模式的时候提出的一个疑问,为啥非要用单例去做这些事呢?我们就不能创建一个普通的类用来访问本地或者网络请求的数据呢?从我个人对单例的认识来看,单例类中存储的数据一般来讲都相对大一些,如果只是写成普通的类,每一次获取数据的时候都需要重新定义一个实例对象,这对内存来讲是存在一定的负担的,在一定程度上没有做到合理分配利用资源,而利用单例,每次获取数据的时候,只是通过一个实例对象来获取,对内存来讲负担相对普通的类要好一些,对资源浪费的少。这也算是单例模式的一种优点。
我们来总结一下单例模式的优点:
1.单例模式提供了对唯一实例的受控访问
2.节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能
3.作为管理中心可以进行信息共享
有时候,优点也可能变成其缺点。对于单例模式来说,作为一个管理中心,其职责相对来讲变得较重,而且在任何地方使用单例都能获取到其数据,这样有时候是好的,但是有时候如果你没有使用好单例的话,会使程序的耦合性变强,一定程度上造成了代码的重用性变差,这也是单例的缺点。
说了这么多,那么我们该如何设计单例呢?
假设,我们现在要设计一个用来访问用户信息的单例,我们创建一个UserInfoManager类。类的定义部分如下:
#import <Foundation/Foundation.h>
@interface UserInfoManager : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSNumber *age;
+ (instancetype)managerCenter;
@end
实现部分如下:
#import "UserInfoManager.h"
@implementation UserInfoManager
+ (instancetype)managerCenter {
static UserInfoManager *center = nil;
if (center == nil) {
center = [[UserInfoManager alloc] init];
}
return center;
}
@end
这样,我们就设计好了一个简单的单例类。那么我们现在回过头来看看我们写的这个单例,假设说,我在两个不同的Controller里面都同时使用了该单例,而且这两个Controller都同时运行了,这个时候,这个单例还是所谓的单例吗?在一定程度上,可能这个类就会产生两个实例对象,也就是说,这时候,这个类就不是所谓的单例了。那么,我们该如何解决这种问题呢?由于ARC的普及,本文只介绍用ARC+GCD的方法来设计单例,非ARC方法有兴趣的读者可以自己去探究。用ARC+GCD的方法修改的实现部分如下:
+ (instancetype)managerCenter {
static UserInfoManager *center = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
center = [[UserInfoManager alloc] init];
});
return center;
}
这里介绍一下dispatch_once函数,这个函数在整个程序运行过程中可以保证代码只被执行一次。这样,即使出现了上述情况,也能保证这个类只会创建一个实例对象。
但是,从规范的角度来讲,这个单例设计的不算严格。严格的单例需要满足两个条件
1.要防止继承
2.确保实例对象只出现一个
刚刚上面已经确保了实例对象只出现一个,但是,该单例没有防止继承,而且,一旦我不调用那个方法,而是使用alloc init进行创建对象,那么,还是会创建出其他实例。那么,我们需要如何修改实现部分呢?
#import "UserInfoManager.h"
static UserInfoManager *center = nil;
@implementation UserInfoManager
+ (instancetype)managerCenter {
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
center = (UserInfoManager *)@"UserInfoManager";
center = [[UserInfoManager alloc] init];
});
// 防止子类使用
NSString *classString = NSStringFromClass([self class]);
if ([classString isEqualToString:@"UserInfoManager"] == NO) {
NSParameterAssert(nil);
}
return center;
}
- (instancetype)init {
NSString *string = (NSString *)center;
if ([string isKindOfClass:[NSString class]] == YES && [string isEqualToString:@"UserInfoManager"]) {
self = [super init];
if (self) {
// 防止子类使用
NSString *classString = NSStringFromClass([self class]);
if ([classString isEqualToString:@"UserInfoManager"] == NO) {
NSParameterAssert(nil);
}
}
return self;
} else {
return nil;
}
}
@end
我们来看看上面的实现部分。这里先介绍一下NSParameterAssert这个函数。这是一个断言,断言评估一个条件,如果条件为 false ,调用当前线程的断点句柄。每一个线程有它自已的断点句柄,它是一个 NSAsserttionHandler 类的对象。当被调用时,断言句柄打印一个错误信息,该条信息中包含了方法名、类名或函数名。然后,它就抛出一个 NSInternalInconsistencyException 异常。
为了防止子类使用,我们在managerCenter方法中用
NSString *classString = NSStringFromClass([self class]);
获取当前调用该方法的对象的类名,如果和UserInfoManager不同的话,就使用NSParameterAssert使程序崩溃。这样,就防止了子类的使用。那么,我们如何防止调用alloc init方法再创建实例对象呢?此时就应该重写init方法,重写代码在上面。我们来看看这个重写的init方法。我们在dispatch_once方法里面,在调用alloc init方法之前,我们先让center指向一个字符串,这个字符串可以为任意的,然后在init方法里面,先定义一个string让其指向center字符串,然后判断其是否是一个字符串并且该字符串的值为之前center指向的那个值,如果是,进行定义,否则,返回空。这样,当你没有调用managerCenter这个方法的时候,光调用alloc init方法只会返回一个nil值,因为此时并为进入dispatch_once方法里面,所以,调用init方法是不会进行初始化的。这样,一个严格的单例便设计好了。由于用的是ARC+GCD方法设计的单例,不需要再重写dealloc等方法。
讲到这,单例模式也就差不多讲完了。最后,我们来总结一下单例模式。
单例模式的优点
1.单例模式提供了对唯一实例的受控访问
2.节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能
3.作为管理中心可以进行信息共享
单例模式的缺点
1.可能会造成耦合性变强
2.职责较重,滥用可能会造成严重后果
编写单例模式的时候要注意,需要使用ARC+GCD方法防止同时调用该单例所造成的创建多个实例对象的结果,同时也要注意,确保该类不能继承,在managerCenter中获取类名来判断,重写init方法保证调用alloc init方法时候不会创建出其他实例对象。单例模式就讲到这,关于非ARC的实现,有兴趣的读者可以自己探究。
——by 糖糖