iOS避免滥用单例
2016-01-28 本文已影响1906人
AKyS佐毅
单例模式:
- 类只能有一个实例,而且必须从一个为人熟知的访问点对其进行访问。
- 这个唯一的实例只能通过子类化进行扩展,而且扩展对象不会破坏客户端的代码。
- 单例模式提供了一个为人熟知的访问点,供客户类为共享资源生成唯一实例,并通过它对共享资源进行访问。
- 虽然静态的全局对象引用或者类方法也可以提供全局访问点,但是全局对象无法防止类被初始化一次以上,而且类方法也缺少消除耦合的灵活性。
- 静态全局变量保持着对类的实例的唯一引用,但是当程序中有两个相同的全乎对象类型的话,就不能确保同一份共享资源的读取和输入。
- 类方法提供了共享服务,不用创建其对象就可以访问。资源的唯一实例可以在类方法中维护。但是,当类需要被子类化以提供更好的服务时,类方法并不能解决这一问题。
使用单例模式需要注意的问题
- 如果单例对象要由多个线程访问,那么使它线程安全是至关重要的。但我们取单例对象的时候,一般都需要检查周围加入一些@synchronized()程序块或者NSLock锁的实例。
- "单例就是披着羊皮的全局状态"。一个单例可以被使用在任何地方,而不需要显式地声明依赖。
- 单例应该只用来保存全局的状态,并且不能和任何作用域绑定。如果这些状态的作用域比一个完整的应用程序的生命周期要短,那么这个状态就不应该使用单例来管理。
- 用一个单例来管理用户绑定的状态,不是很好的设计方式,需要重新评估自己的设计方案。
举例说明错误的单例使用
- 假设现在我们创建一个单例,该单例拥有一个属性,我们姑且以MessageType来做假设.
- messageType分为userMessage(用户消息) 和BroadcastMessage(广播消息)。
代码块:
#import <Foundation/Foundation.h>
@interface MessageCenterManager : NSObject
@property (nonatomic ,assign) NSInteger messageType;
+ (instancetype)sharedInstance;
@end
.m实现
#import "MessageCenterManager.h"
static MessageCenterManager *_sharedInstance;
@implementation MessageCenterManager
+ (instancetype)sharedInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[MessageCenterManager alloc] init];
});
return _sharedInstance;
}
-(id)copyWithZone:(NSZone *)zone{
return _sharedInstance;
}
+(id)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [super allocWithZone:zone];
});
return _sharedInstance;
}
@end
此时我们创建了一个单例,且看下边的用法,是错误的,不安全的。
#import "UserMessage.h"
@implementation UserMessage
- (void)someMethod{
//在这里是不能像这样引用单例的,这样的话用户消息和广播消息就会耦合在一起
if([MessageCenterManager sharedInstance].messageType){
//do something
}else{
}
}
@end
#广播消息中
@implementation BroadcastMessage
- (void)someMethod{
[[MessageCenterManager sharedInstance] setMessageType:0];
}
@end
重要的事情加警告
-
这种情况应该只能发生在 BroadcastMessage显式引用了UserMessage,并表明了两者之间的关系时。这里使用了单例,由于其具有全局和多状态的特性,导致隐式地在两个看起来完全不相关的模块之间建立了耦合。
-
二 假设现在我们需要存取用户的头像。当用户需要退出登陆的时候,我们要讲存储在本地的图片删除。
-
当展示图片的时候需要从本地文件中读取。假设用户此时登出程序,我们需要将用户存取数据置为空,即需要将单例置为nil。
-
因为项目中会有两个到三个地方都会有存取的操作,那么此时,单例的操作就必须注意线程安全了。
-
其实这种将用户状态以单例形式保存所有地方都是用方式是不对的。单例应该只用来保存全局的状态,并且不能和任何作用域绑定。
-
如果这些状态的作用域比一个完整的应用程序的生命周期要短,那么这个状态就不应该使用单例来管理。用一个单例来管理用户绑定的状态,不是一种良好的设计思维。