单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
在iOS中,UIApplication,NSNotificationCenter,NSFileManager等类都是单例模式的实例。
在ARC环境下,结合GCD可以实现单例模式:
单例类Singleton类的接口部分:
#import <Foundation/Foundation.h>
@interface Singleton : NSObject <NSCopying>
+ (Singleton *)sharedInstance;
@property (strong, nonatomic) NSString *str;
- (void)print;
@end
其中通过sharedInstance方法可以获取该类的单例。
实现部分:
#import "Singleton.h"
@implementation Singleton
__strong static Singleton *singleton = nil;
+ (Singleton *)sharedInstance {
NSLog(@"调用sharedInstance方法");
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
NSLog(@"创建Singleton实例");
singleton = [[super allocWithZone:NULL] init];
singleton.str = @"Say something";
});
return singleton;
}
+ (id)allocWithZone:(NSZone *)zone {
NSLog(@"调用alloc方法");
return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone {
NSLog(@"调用copy方法");
return self;
}
- (void)print {
NSLog(@"%@", self.str);
}
@end
dispatch_once函数确保该类的实例对象只创建一次。
allocWithZone方法和copyWithZone方法确保该类的实例对象不会再次被创建。
关于dispatch_once函数:
使用dispatch_once提价的代码块,即便你提交多次,只能执行一次。 void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block); 第一个参数是一个传出参数用来保存代码块在队列运行时被赋的值,如果你想让自己的代码只执行一次的话,你必须指定一个同样的标识符,其实它是long类型的长整数,即typedef long dispatch_once_t。 第二个参数是一个代码块,这个代码块没有参数和返回值。 dispatch_once 中的代码块默认的情况下在当前的线程内中执行(也就是被调用函数所在的线程)
dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的。
该方法有很多优势:
1 线程安全
2 很好满足静态分析器要求
3 和自动引用计数(ARC)兼容
4 仅需要少量代码
测试结果:
我们在程序运行的时候创建一个Singleton对象:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
Singleton *sin = [Singleton sharedInstance];
NSLog(@"---------------------");
return YES;
}
在View Controller类多次使用sharedInstance方法,alloc init方法和copy方法来尝试创建Singleton类对象:
- (void)viewDidLoad
{
[super viewDidLoad];
Singleton *singleton = [Singleton sharedInstance];
if (singleton) {
[singleton print];
}
NSLog(@"---------------------");
Singleton *singleton2 = [Singleton sharedInstance];
if (singleton2) {
[singleton2 print];
}
NSLog(@"---------------------");
singleton = nil;
singleton = [[Singleton alloc] init];
if (singleton) {
[singleton print];
}
NSLog(@"---------------------");
Singleton *singleton3 = [singleton2 copy];
if (singleton3) {
[singleton3 print];
}
}
运行结果:
2014-02-05 21:41:25.323 Singleton[4827:70b] 调用sharedInstance方法
2014-02-05 21:41:25.324 Singleton[4827:70b] 创建Singleton实例
2014-02-05 21:41:25.324 Singleton[4827:70b] ---------------------
2014-02-05 21:41:25.325 Singleton[4827:70b] 调用sharedInstance方法
2014-02-05 21:41:25.326 Singleton[4827:70b] Say something
2014-02-05 21:41:25.326 Singleton[4827:70b] ---------------------
2014-02-05 21:41:25.326 Singleton[4827:70b] 调用sharedInstance方法
2014-02-05 21:41:25.327 Singleton[4827:70b] Say something
2014-02-05 21:41:25.327 Singleton[4827:70b] ---------------------
2014-02-05 21:41:25.327 Singleton[4827:70b] 调用alloc方法
2014-02-05 21:41:25.327 Singleton[4827:70b] 调用sharedInstance方法
2014-02-05 21:41:25.328 Singleton[4827:70b] Say something
2014-02-05 21:41:25.328 Singleton[4827:70b] ---------------------
2014-02-05 21:41:25.328 Singleton[4827:70b] 调用copy方法
2014-02-05 21:41:25.328 Singleton[4827:70b] Say something
由控制台输出可见,在程序刚刚启动时通过sharedInstance中的dispatch_once函数创建了一个Singleton实例,随后的sharedInstance方法或alloc init方法或copy方法都不会导致dispatch_once函数的调用,也就是Singleton实例只被创建了一次。
参考资料:
后记:
dispatch_once函数的原型为:void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block),其中dispatch_once_t是long类型的typedef,dispatch_block_t是一个代码块的typedef。
这个函数的作用是使得block在整个程序的生命周期中只执行一次,每次调用这段代码时通过predicate来检查,在这里predicate必须严格地初始化为0。
可以测试下其输出:
+ (UIColor *)boringColor;
{
static UIColor *color;
static dispatch_once_t onceToken;
NSLog(@"%ld", onceToken);
dispatch_once(&onceToken, ^{
NSLog(@"dispatch_once");
color = [UIColor colorWithRed:0.380f green:0.376f blue:0.376f alpha:1.000f];
});
NSLog(@"%ld", onceToken);
NSLog(@"--------------");
return color;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[Color boringColor];
[Color boringColor];
[Color boringColor];
}
2014-03-23 14:01:30.653 once_t[1215:60b] 0
2014-03-23 14:01:30.655 once_t[1215:60b] dispatch_once
2014-03-23 14:01:30.655 once_t[1215:60b] -1
2014-03-23 14:01:30.655 once_t[1215:60b] --------------
2014-03-23 14:01:30.656 once_t[1215:60b] -1
2014-03-23 14:01:30.656 once_t[1215:60b] -1
2014-03-23 14:01:30.656 once_t[1215:60b] --------------
2014-03-23 14:01:30.656 once_t[1215:60b] -1
2014-03-23 14:01:30.657 once_t[1215:60b] -1
2014-03-23 14:01:30.657 once_t[1215:60b] --------------
若onceToken被初始化为0,那么在调用dispatch_once函数时检查到其值为0,就执行block,执行完毕后onceToken减一。下一次调用dispatch_once函数时检查到onceToken = -1,将不会执行block。
下面我们修改一下:
static dispatch_once_t onceToken = 1L;
控制台输出一直停留在1,程序加载不了、内存保存不变,一直卡在dispatch_once函数那里。
如果将onceToken设置为-2,和设置为1的情况一样。
如果将onceToken设置为-1,不会陷入死循环中,但是block没有被执行,某些变量可能没有被初始化。
可见随意修改onceToken的值有多危险了,同时这也是将其属性设置为static的原因,因为要在文件域中一直持有其值。
小结:
调用dispatch_once(predicate, block)时,函数首先检查predicate:
如果predicate = 0,那么执行block。
如果predicate = -1,那么不执行block并继续往下执行。
如果predicate != 0 或 -1,那么程序将一直卡在dispatch_once函数那里。
结论:一定要将predicate初始化为0,并将其范围设置为static,另外不要随意改变其值(不要将其设置为类的属性,实例变量等),否则会导致无法预见的行为。