单例设计模式确切的说就是一个类只有一个实例,有一个全局的接口来访问这个实例。当第一次载入的时候,它通常使用延时加载的方法创建单一实例。
提示:苹果大量的使用了这种方法。例子:[nsuserdefaults standeruserdefaults], [uiapplication sharedapplication], [uiscreen mainscreen], [nsfilemanager defaultmanager] 都返回一个单一对象。
你可能想知道你为什么要关心一个类有多个的实例。代码和内存都很便宜,不是吗?
在一些情况下,一个类只有一个实例是有意义的。例如,这里没有必要有多个登录实例,除非你一次想写入多个日志文件。或者,一个全局的配置类文件:它可以很容易的很安全的执行一个公共资源,这样的一个配置文件,要比同时修改多个配置类文件好很多。
如何使用单例模式
请看下面的图片
上面的图片显示的是一个登录类,它有一个属性(这个单一实例),有两个方法:sharedinstance 和 init。
首先一个客户端(client)发送 sharedinstance 信息,但是属性 instance 还没有初始化,所以你要先给这个类创建一个实例。
然后你调用 sharedinstance,instance 会马上返回初始化的值。这个逻辑最终只会返回一个实例。
你需要执行这个模式来创建单例类来管理所有的专辑数据。
你需要注意在项目里有一个叫 api 文件夹,给你的 app 提供服务的所有类都需要放在这里。在这个文件夹里用 ios\cocoa touch\object-c class 创建一个新类。类的名字叫 libraryapi,子类选择 nsobject。
打开 libraryapi.h 文件用下面的代码替换里面的内容:
@interface libraryapi: nsobject
(libraryapi*)sharedinstance;
@end
现在打开 libraryapi.m 文件,在 @implentation 后面添加如下方法:
(libraryapi*)sharedinstance
{
// 1
static libraryapi *_sharedinstance = nil;
// 2
static dispatch_once_t oncepredicate;
// 3
dispatch_once(&nocepredicate, ^{
_sharedinstance = [[libraryapi alloc] init];
});
return _sharedinstance;
}
在这个短方法中做了这些事情:
在这个类中,声明一个静态变量来保存这个实例,保证它是一个全局可用的变量。
声明一个静态这是 dispatch_one_t,确保这些初始化代码只能被执行一次。
使用 grand central dispatch(gcd)执行一个 block 来初始化 libraryapi 实例。这是单例设计模式的关键所在:一个类只能被实例化一次。
接下来执行 sharedinstance,在 dispatch_once block 里的代码是不会被执行的(当它已经被执行过一次后),它会返回之前创建的 libraryapi 实例。
提示:想了解更多关于 gcd 和使用它,请点击这里的教程 multithreading and grand central dispatch,如何使用 blocks 在这里。
你现在有一个单例对象来管理专辑了。下一步就是创建一个类用来保存你的专辑数据了。
用 ios\cocoa touch\object-c class 在 api 文件夹下创建一个新的类,名字叫 persistencymanager,子类选择 nsobject。
打开 persistencymanager.h,在顶部引入面文件:
#import "album.h"
然后在 @interface 后面加入下面代码:
- (nsarray *)getalbums;
- (void)addalbums:(album*)album atindex:(int)index;
- (void)deletealbumatindex:(int)index;
上面的三个方法都需要跟专辑的数据相结合。
打开 persistencymanager.m,在 @implementation 上面添加如下代码:
@interface persistencymanager () {
nsmutablearray *albums;
}
上面的代码是给类添加了一个扩展,这是另一种给类添加私有方法和私有属性的方法,类外面的成员是看不到这些的。这里,你声明了一个 nsmutablearray 来保存专辑的数据。这是一个可变数组,你可以很容易的添加和删除专辑。
现在在 @implementation 下面添加实现代码:
- (id)init {
self = [super init];
if (self) {
albums = [nsmutablearray arraywitharray:@[[[album alloc] initwithtitle:@"best of bowie" artist:@"david bowie" coverurl:@"http://www.coversproject.com/static/thumbs/album/album_david bowie_best of bowie.png" year:@"1992"],
[[album alloc] initwithtitle:@"it's my life" artist:@"no doubt" coverurl:@"http://www.coversproject.com/static/thumbs/album/album_no doubt_its my life bathwater.png" year:@"2003"],
[[album alloc] initwithtitle:@"nothing like the sun" artist:@"sting" coverurl:@"http://www.coversproject.com/static/thumbs/album/album_sting_nothing like the sun.png" year:@"1999"],
[[album alloc] initwithtitle:@"staring at the sun" artist:@"u2" coverurl:@"http://www.coversproject.com/static/thumbs/album/album_u2_staring at the sun.png" year:@"2000"],
[[album alloc] initwithtitle:@"american pie" artist:@"madonna" coverurl:@"http://www.coversproject.com/static/thumbs/album/album_madonna_american pie.png" year:@"2000"]]];
}
return self;
}
在 init 里你在数组中加入了 5 张专辑。如果上面的专辑你不喜欢,你可以随意替换成你喜欢的。:]
现存在 persistencymanager.m 添加下面三个方法:
- (nsarray*)getalbums
{
return albums;
}
- (void)addalbum:(album*)album atindex:(int)index
{
if (albums.count >= index)
[albums insertobject:album atindex:index];
else
[albums addobject:album];
}
- (void)deletealbumatindex:(int)index
{
[albums removeobjectatindex:index];
}
这些方法是获取,添加,删除专辑。
build 你的项目,确保所有的代码都能正确编译。
单例模式的使用场合
类只能有一个实例,并且必须从一个为人数值的访问点对其访问。
这个唯一的实例只能通过子类化进行拓展,并且拓展的对象不会破坏客户端代码。
在objective-c中方法都是公有的,而且oc的语言本身是动态类型的,因此所有类都可以相互发送对方的消息。,并且cocoa框架使用计数的内存管理方式来维护对象的内存中的生存期。
下面让我们看一下oc当中的单例模式的写法,首先单例模式在arc\mrc环境下的写法有所不同,需要编写2套不同的代码
可以用宏判断是否为arc环境#if _has_feature(objc_arc)
#else
//mrc
#endif
单例模式- arc -方法一
arc中单例模式的实现
在 .m中保留一个全局的static的实例
static id _instance;
//重写allocwithzone:方法,在这里创建唯一的实例(注意线程安全)
(instancetype)allocwithzone:(struct _nszone *)zone
{
@synchronized(self) {
if (_instance == nil) {
_instance = [super allocwithzone:zone];
}
}
return _instance;
}
提供1个类方法让外界访问唯一的实例
(instancetype)sharedinstancetool{
@synchronized(self){
if(_instance == nil){
_instance = [[self alloc] init];
}
}
return _instance;
}
实现copywithzone:方法
-(id)copywithzone:(struct _nszone *)zone{
return _instance;
}
我们在sharedinstancetool,首先检查类的唯一实例是否已经创建,如果就会创建实例并将其返回。而之所以调用super而不是self,是因为已经在self中重载了基本的对象分配的方法,需要借用父类的功能来帮助处理底层内存的分配。
在allocwithzone:(struct _nszone*)zone方法中,只是返回从sharedinstancetool方法返回的类实例。而同样的在cocoa框架中调用allocwithzone:(struct _nszone*)zone会分配内存,引用计数会设置为1,然后返回实例。同样的重写(id)copywithzone:(struct _nszone *)zone方法,也是为了保证不会返回实例的副本,而是返回self.返回同一个实例。
方法二:
(instancetype)sharedinstance {
static wmsingleton *singleton = nil;
if (! singleton) {
singleton = [[self alloc] initprivate];
}
return singleton;
}
- (instancetype)init {
@throw [nsexception exceptionwithname:@"这个是个单例"
reason:@"应该这样调用 [wmsingleton sharedinstance]"
userinfo:nil];
return nil;
}
//实现自己真正的私有初始化方法
- (instancetype)initprivate {
self = [super init];
return self;
}
上面这段代码中将singleton指针声明为静态变量。当某个定义了静态变量的方法返回时,程序不会释放相应的变量。####singleton变量的初始值是nil,当程序第一次执行sharedinstance方法时会创建一个对象,并将新创建的对象的地址赋值给singleton变量。当徐成再次执行sharedinstance方法时,无论多少次singleton变量仍然会指向最初那个创建的对象。因为指向对象的singleton变量是强引用的,并且程序永远不会释放该变量,所以singleton变量指向的对象也不会释放。
线程安全。
上面的实例中我们通过@synchronized来添加了一个互斥锁,以此来保证线程安全。而现在我们开始尝试用线程的方式来实现一个加单的单例。
static wmobject *_instance;
(instancetype)allocwithzone:(struct _nszone *)zone
{
static dispatch_once_t oncetoken;
dispatch_once(&oncetoken, ^{
_instance = [super allocwithzone:zone];
});
return _instance;
}
(instancetype)sharedinstance
{
static dispatch_once_t oncetoken;
dispatch_once(&oncetoken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
- (id)copywithzone:(nszone *)zone
{
return _instance;
}
从上面的代码我们可以看到,实现的思路基本上也是一致的我们在sharedinstancetool,首先检查类的唯一实例是否已经创建,如果就会创建实例并将其返回。而略有不同的地方就是我们这次通过dispatch_once_t来保证线程的安全性。至于dispatch_once_t的用法这里就一一赘述了,线程的相关教程都会有其相关的描述。
到了这里一个简单的单例模式基本实现完成了,那么我们可以尝试着把它封装到一个宏里,然后方便其以后的调用
创建一个wmsingleton.h
// .h文件
#define wmsingletonh(name) (instancetype)shared##name;
// .m文件
#define wmsingletonm(name) \
static id _instance; \
\
(instancetype)allocwithzone:(struct _nszone *)zone \
{ \
static dispatch_once_t oncetoken; \
dispatch_once(&oncetoken, ^{ \
_instance = [super allocwithzone:zone]; \
}); \
return _instance; \
} \
\
(instancetype)shared##name \
{ \
static dispatch_once_t oncetoken; \
dispatch_once(&oncetoken, ^{ \
_instance = [[self alloc] init]; \
}); \
return _instance; \
} \
\
- (id)copywithzone:(nszone *)zone \
{ \
return _instance; \
}
使用方法
//.h类
//引入这个宏文件
#import "wmsingleton.h"
@interface wmobject : nsobject
wmsingletonh(object)
@end
//.m类
@implementation wmobject
wmsingletonm(car)
@end