iOS中单例模式很常见,比如Cocoa中的一些对象方法, [UIColor redColor]
等等。
顾名思义,单例模式确保了一个类只有一个实例。
一个常见的写法如下:
/* Singleton.h */ #import <Foundation/Foundation.h> @interfaceSingleton:NSObject + (Singleton *)sharedInstance; @end /* Singleton.m */ #import "Singleton.h" staticSingleton *instance =nil; @implementationSingleton + (Singleton *)sharedInstance { if(!instance) { instance = [[superalloc] init]; } returninstance; }
这种写法的优点是,可以延迟加载,按需分配内存以节省开销。
但是,这并非一个线程安全的写法,比如两个或多个线程并发的调用 sharedInstance
方法,有可能会得到多个实例,这里有几种方法来创建一个线程安全的单例。
可以使用@synchronized进行加锁,代码如下:
/* Singleton.h */ #import <Foundation/Foundation.h> @interfaceSingleton:NSObject + (Singleton *)sharedInstance; @end /* Singleton.m */ #import "Singleton.h" staticSingleton *instance =nil; @implementationSingleton + (Singleton *)sharedInstance { @synchronized(self) { if(!instance) { instance = [[superalloc] init]; } } returninstance; }
这种写法也是懒加载,不过虽然保证了线程安全但是由于锁的存在当多线程访问时,性能会降低。
双重检查锁,主要是为了避免对除第一次调用外的所有调用都实行同步的昂贵代价。
就是并不是每次进入 sharedInstance
方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。
进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。
如此,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
代码如下:
/* Singleton.h */ #import <Foundation/Foundation.h> @interfaceSingleton:NSObject + (Singleton *)sharedInstance; @end /* Singleton.m */ #import "Singleton.h" staticSingleton *instance =nil; @implementationSingleton + (Singleton *)sharedInstance { if(!instance) { @synchronized(self) { if(!instance) { instance = [[superalloc] init]; } } } returninstance; }
这段代码乍一看,似乎没什么问题,问题是CPU和编译器可能会对内存访问指令进行重新排序。参考了一下 Care and Feeding of Singletons 这篇文章中的方法。可以在访问变量前插入 barriers
并且使用 volatile
关键字。内存 barriers
在 libkern/OSAtomic.h
头文件中,这用来解决CPU的问题, vilatile
关键字用来解决编译器的问题。代码如下:
/* Singleton.h */ #import <Foundation/Foundation.h> @interfaceSingleton:NSObject + (Singleton *)sharedInstance; @end /* Singleton.m */ #import "Singleton.h" staticSingleton *volatileinstance =nil; @implementationSingleton + (Singleton *)sharedInstance { if(!instance) { @synchronized(self) { if(!instance) { OSMemoryBarrier(); instance = [[superalloc] init]; } } } OSMemoryBarrier(); returninstance; }
虽然相较于上一种方法减少了部分锁的开销,但是这个开销依然不小。而且实现方式本身颇具争议且复杂。
这里主要利用GCD中的 dispatch_once
方法,这是最普遍也是苹果最推荐的方法,函数原型如下:
voiddispatch_once( dispatch_once_t*predicate, dispatch_block_t block);
单例实现代码如下:
/* Singleton.h */ #import <Foundation/Foundation.h> @interfaceSingleton:NSObject + (Singleton *)sharedInstance; @end /* Singleton.m */ #import "Singleton.h" staticSingleton *instance =nil; @implementationSingleton + (Singleton *)sharedInstance { staticdispatch_once_tpredicate; dispatch_once(&predicate, ^{ instance = [[InstanceClass alloc] init]; }); returninstance; }
这样的方法有很多优势,首先满足了线程安全问题,其次很好满足静态分析器要求。
GCD可以确保以更快的方式完成这些检测,它可以保证 block
中的代码在任何线程通过 dispatch_once
调用之前被执行,但它不会强制每次调用这个函数都让代码进行同步控制。
苹果的文档 documentation for dispatch_once 是这么说的:
The predicate must point to a variable stored in global or static scope. The result of using a predicate with automatic or dynamic storage (including Objective-C instance variables) is undefined.
所以,如果你的predicate不是静态的、不是全局的,还是不能用GCD。其实如果你去看这个函数所在的头文件,你会发现目前它的实现其实是一个宏。