iOS设计模式:单例模式

 

因为单例是全局哪里要用直接调用就行非常方便简单,一般我们可以用单例来作对用户信息的存储,其次单例可以做成购物车之类的页面等等。当然单例最大的优势个人感觉就是对数据的存储和读取非常方便,就可以解决页面之间传值困难的问题。简单讲下怎样用单例对数据传输吧,把需要的数据都定义成属性,当需要存储的时候直接调用存储就行,要用的时候把它调出使用就行了这里不做过多描述了。

 

单例模式是一个类在系统中只有一个实例对象。通过全局的一个入口点对这个实例对象进行访问。在iOS开发中,单例模式是非常有用的一种设计模式。如下图,是一个简单的例模式的UML类图。

 

 

单例模式是一个类在系统中只有一个实例对象。通过全局的一个入口点对这个实例对象进行访问。在iOS开发中,单例模式是非常有用的一种设计模式。如下图,是一个简单的例模式的UML类图。

 

iOS SDK中也有许多类使用了单例模式,例如,UIApplication:当程序启动的时候,会调用UIApplicationMain方法,在该方法中,会实例化一个UIApplication对象,之后在程序中的任意地方调用sharedApplication方法都将返回一个与当前应用程序相关的UIApplication实例(UIApplicationMain方法中创建的UIApplication单例)。

 

什么时候使用单例模式?

在程序中,单例模式经常用于只希望一个类只有一个实例,而不运行一个类还有两个以上的实例。当然,在iOS SDK中,根据特定的需求,有些类不仅提供了单例访问的接口,还为开发者提供了实例化一个新的对象接口,例如,NSFileManager可以通过defaultManager方法返回相同的一个NSFileManager对象。如果需要新的一个NSFileManager实例对象,可以通过init方法。

 

iOS中单例模式的实现

iOS中单例模式的实现方式一般分为两种:Non-ARC(非ARC)和ARC+GCD。


1.Non-ARC(非ARC)
非ARC的实现方法如下所示:

BVNonARCSingleton.h
1.//
2.//  BVNonARCSingleton.h
3.//  SingletonPattern
4.//
5.//  Created by BeyondVincent on 13-5-9.
6.//  Copyright (c) 2013年 BeyondVincent. All rights reserved.
7.//
8. 
9.#import <Foundation/Foundation.h>
10. 
11.@interface BVNonARCSingleton : NSObject
12. 
13.@property  ( nonatomic, retain) NSString  *tempProperty;
14.+ (BVNonARCSingleton *)sharedInstance; 
15. 
16.@end11.@implementation BVNonARCSingleton
12. 
13.static BVNonARCSingleton *sharedInstance = nil;
14. 
15.// 获取一个sharedInstance实例,如果有必要的话,实例化一个
16.+ (BVNonARCSingleton *)sharedInstance {
17.    if (sharedInstance == nil) {
18.        sharedInstance = [[super allocWithZone:NULL] init];
19.    }
20. 
21.    return sharedInstance;
22.}
23. 
24.// 当第一次使用这个单例时,会调用这个init方法。
25.- (id)init
26.{
27.    self = [super init];
28. 
29.    if (self) {
30.        // 通常在这里做一些相关的初始化任务
31.    }
32. 
33.    return self;
34.}
35. 
36.// 这个dealloc方法永远都不会被调用--因为在程序的生命周期内容,该单例一直都存在。(所以该方法可以不用实现)
37.-(void)dealloc
38.{
39.    [super dealloc];
40.}
41. 
42.// 通过返回当前的sharedInstance实例,就能防止实例化一个新的对象。
43.+ (id)allocWithZone:(NSZone*)zone {
44.    return [[self sharedInstance] retain];
45.}
46. 
47.// 同样,不希望生成单例的多个拷贝。
48.- (id)copyWithZone:(NSZone *)zone {
49.    return self;
50.}
51. 
52.// 什么也不做——该单例并不需要一个引用计数(retain counter)
53.- (id)retain {
54.    return self;
55.}
56. 
57.// 替换掉引用计数——这样就永远都不会release这个单例。
58.- (NSUInteger)retainCount {
59.    return NSUIntegerMax;
60.}
61. 
62.// 该方法是空的——不希望用户release掉这个对象。
63.- (oneway void)release {
64. 
65.}
66. 
67.//除了返回单例外,什么也不做。
68.- (id)autorelease {
69.    return self;
70.}
71. 
72.@end2.@synchronized (self)
3.{
4.    if(sharedInstance == nil)
5.    {
6.        sharedInstance = [[super allocWithZone:NULL] init];
7.    }
8.}11.@interface BVARCSingleton : NSObject
12. 
13.@property  ( nonatomic, weak) NSString  *tempProperty;
14.+ (BVARCSingleton *)sharedInstance;
15. 
16.@end11.@implementation BVARCSingleton
12. 
13.+ (BVARCSingleton *) sharedInstance
14.{
15.    static  BVARCSingleton *sharedInstance = nil ;
16.    static  dispatch_once_t onceToken;  // 锁
17.    dispatch_once (& onceToken, ^ {     // 最多调用一次
18.        sharedInstance = [[self  alloc] init];
19.    });
20.    return  sharedInstance;
21.}
22. 
23.// 当第一次使用这个单例时,会调用这个init方法。
24.- (id)init
25.{
26.    self = [super init];
27. 
28.    if (self) {
29.        // 通常在这里做一些相关的初始化任务
30.    }
31. 
32.    return self;
33.}
34. 
35.@end2.@synchronized (self)


BVNonARCSingleton.m
1.//
2.//  BVNonARCSingleton.m
3.//  SingletonPattern
4.//
5.//  Created by BeyondVincent on 13-5-9.
6.//  Copyright (c) 2013年 BeyondVincent. All rights reserved.
7.//
8. 
9.#import "BVNonARCSingleton.h"
10. 
 
实际上上面的代码苹果官网也有提供:Creating a Singleton Instance只不过没有给出头文件的定义。上面用非ARC实现单例的方法是线程不安全的,如果有多个线程同时调用sharedInstance方法获取一个实例,而sharedInstance方法需要花费1-2秒钟的时间,那么BVNonARCSingleton的init方法就可能会被多次调用,也就是不同线程获得的BVNonARCSingleton有可能不是同一个实例。怎么解决线程的不安全呢?答案是使用@synchronized来创建互斥锁即可。

1.// 保证在实例化的时候是线程安全的(当然,该方法不能保证该单例中所有方法的调用都是线程安全的)
2.@synchronized (self)
3.{
4.    if(sharedInstance == nil)
5.    {
6.        sharedInstance = [[super allocWithZone:NULL] init];
7.    }
8.}
 

通过上面的代码就能保存线程安全。
1.<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px;">提醒:在iOS中,一般不建议使用非ARC来实现单例模式。更好的方法是使用ARC+GCD来实现。</span>

 

2.ARC+GCD
通过ARC+GCD的方法来实现单例模式的非常简单的。下面先来看看具体实现:

BVARCSingleton.h
1.//
2.//  BVARCSingleton.h
3.//  SingletonPattern
4.//
5.//  Created by BeyondVincent on 13-5-9.
6.//  Copyright (c) 2013年 BeyondVincent. All rights reserved.
7.//
8. 
9.#import &lt;Foundation/Foundation.h&gt;
10. 
 
BVARCSingleton.m
1.//
2.//  BVARCSingleton.m
3.//  SingletonPattern
4.//
5.//  Created by BeyondVincent on 13-5-9.
6.//  Copyright (c) 2013年 BeyondVincent. All rights reserved.
7.//
8. 
9.#import "BVARCSingleton.h"
10. 
 
在上面的代码中,调用Grand Central Dispatch (GCD)中的dispatch_once方法就可以确保BVARCSingleton只被实例化一次。并且该方法是线程安全的,我们不用担心在不同的线程中,会获得不同的实例。(当然,该方法同样不能保证该单例中所有方法的调用都是线程安全的)。

 

当然,在ARC中,不用GCD也是可以做到线程安全的,跟之前非ARC代码中使用@synchronized一样,如下代码:
1.    // 不使用GCD,通过@synchronized
2.@synchronized (self)
3.{
4.    if(sharedInstance == nil)
5.    {
6.        sharedInstance = [[self alloc] init];
7.    }
8.}
 
为了简化使用ARC+GCD来创建单例,可以定义下面这样的一个宏:
1.#define DEFINE_SHARED_INSTANCE_USING_BLOCK(block) \
2.static dispatch_once_t onceToken = 0; \
3.__strong static id sharedInstance = nil; \
4.dispatch_once(&amp;onceToken, ^{ \
5.sharedInstance = block(); \
6.}); \
7.return sharedInstance; \


实例化的实现方法如下所示:
1.+ (BVARCSingleton *) sharedInstance
2.{
3.    DEFINE_SHARED_INSTANCE_USING_BLOCK(^{
4.        return [[self alloc] init];
5.    });
6.}


单例的使用

单例的使用方法很简单,在代码中的任意位置,如下使用即可:

在BVAppDelegate.m中添加头文件:
1.#import "BVNonARCSingleton.h"
2.#import "BVARCSingleton.h"


如下使用方法:
1.- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
2.{
3.    [BVNonARCSingleton sharedInstance].tempProperty = @"非ARC单例的实现";
4.    NSLog(@"%@", [BVNonARCSingleton sharedInstance].tempProperty);
5. 
6.    [BVARCSingleton sharedInstance].tempProperty = @"ARC单例的实现";
7.    NSLog(@"%@", [BVARCSingleton sharedInstance].tempProperty);
8. 
9.    return YES;
10.}


运行程序,会在控制台窗口输出如下内容:
1.2013-05-09 16:44:07.649 SingletonPattern[5159:c07] 非ARC单例的实现
2.2013-05-09 16:44:33.204 SingletonPattern[5159:c07] ARC单例的实现

 

 

 

iOS单例的两种实现

单例模式算是开发中比较常见的一种模式了。在iOS中,单例有两种实现方式(至少我目前只发现两种)。根据线程安全的实现来区分,一种是使用@synchronized,另一种是使用GCD的dispatch_once函数。

要实现单例,首先需要一个static的指向类本身的对象,其次需要一个初始化类函数。下面是两种实现的代码。

1、@synchronized

static InstanceClass *instance; + (InstanceClass *)defaultInstance{ @synchronized (self){ if (instance == nil) { instance = [[InstanceClass alloc] init]; } } return instance; } 

2、GCD

static InstanceClass *instance; + (InstanceClass *)defaultInstance{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[InstanceClass alloc] init]; }); return instance; } 

总的来说,两种实现效果相同,但第二种GCD的实现方式写起来比较简单。如果不习惯GCD的方式,可以使用第一种方式。

 

 

 

无论是爱还是恨,你都需要单例。实际上每个iOS或Mac OS应用都至少会有UIApplication或NSApplication.
什么是单例呢?Wikipedia是如此定义的:
在软件工程中,单例是一种用于实现单例的数学概念,即将类的实例化限制成仅一个对象的设计模式。
或者我的理解是:
单例是一种类,该类只能实例化一个对象。
    尽管这是单例的实际定义,但在Foundation框架中不一定是这样。比如NSFileManger和NSNotificationCenter,分别通过它们的类方法defaultManager和defaultCenter获取。尽管不是严格意义的单例,这些类方法返回一个可以在应用的所有代码中访问到的类的共享实例。在本文中我们也会采用该方法。
    使用Objective-C实现单例模式的最佳方式向来有很多争论,开发者(包括Apple在内)似乎每几年就会改变他们的想法。当Apple引入了 Grand Central Dispatch (GCD)(Mac OS 10.6和iOS4.0),他们也引入了一个很适合用于实现单例模式的函数。
    该函数就是dispatch_once:
void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);
    该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用)。它还接收一个希望在应用的生命周期内仅被调度一次的代码块,对于本例就用于shared实例的实例化。
dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题。
    Apple的 GCD Documentation证实了这一点:
如果被多个线程调用,该函数会同步等等直至代码块完成。
    实际要如何使用这些呢?
    好吧,假设有一个AccountManager类,你想在整个应用中访问该类的共享实例。你可以按如下代码简单实现一个类方法:
+ (AccountManager *)sharedManager { 
    static AccountManager *sharedAccountManagerInstance = nil; 

    static dispatch_once_t predicate; dispatch_once(&predicate, ^{       
          sharedAccountManagerInstance = [[self alloc] init]; 
    });

    return sharedAccountManagerInstance; 

}
    这就意味着你任何时候访问共享实例,需要做的仅是:
AccountManager *accountManager = [AccountManager sharedManager];
    就这些,你现在在应用中就有一个共享的实例,该实例只会被创建一次。
    该方法有很多优势: 
           1 线程安全
           2 很好满足静态分析器要求
           3 和自动引用计数(ARC)兼容 
           4 仅需要少量代码
    该方法的劣势就是它仍然运行创建一个非共享的实例:
AccountManager *accountManager = [[AccountManager alloc] init];
    有些时候你希望有这种行为,但如果正在想要的是仅一个实例被实例化就需要注意这点。
 
 
单例模式介绍

单例模式确保在一个应用中只产生一个实例,这是很有必要的,因为在我们做软件设计的时候,有很多对象都是只需要一个就可以了,而不需要创建众多的对象,这样最显而易见的就是节省了内存空间。而且避免了这个类的频繁的初始化与销毁。有时为了实现某一种功能与操作而创建的类(工具类)往往也不需要多个对象,使用单例模式再合适不过。再延伸一点,有时为了节省内存对一个对象进行复用的话也可以通过单例来实现,这在手机软件的开发中用得比较多,因为手机的内存实在是少得可怜。

单例模式优点
  1. 正如前面说的,单例模式在内存中只有一个实例,减少了内存开支。特别是一个对象需要频繁的创建、销毁时,而创建与销毁的性能有无法优化,单例模式的优势就非常明显。
  2. 单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
  3. 单例模式可以避免对资源的多重占用。
  4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问。
单例模式缺点
  1. 单例模式一般没有接口,扩展很困难,除了修改代码基本上没有第二种途径实现。
  2. 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的。
  3. 单例模式与单一职责原则有冲突。
单例模式在iOS中的使用

单例模式在iOS开发中的使用还是蛮多的,许多FoundationCocoaUIKit中的类都实现了单例模式,比如应用程序本身UIApplication、文件操作类NSFileManager、消息中心NSNotificitonCenter等系统都已经给我们实现单例,我们只需要使用就好了。在iOS中使用单例模式要使用类方法,通过类方法返回该类的唯一对象。

我知道的在iOS开发中实现单例模式主要有以下三种方式:

第一种

该方法是苹果的官方文档中写的一种方式,通过覆盖NSObject的部分方法实现,使该类无法allocretainrelease。这是最麻烦的一种方法,也是最不好的一种方法。

Singleton
1
2
3
4
5
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 
static Singleton *instance = nil;  + (Singleton *)sharedInstance {  if (instance == nil) {  instance = [[super allocWithZone:NULL] init];  }  return instance; }  + (id)allocWithZone:(NSZone *)zone {  return [[self sharedInstance] retain]; }  - (id)copyWithZone:(NSZone *)zone {  return self; }  - (id)retain {  return self; }  - (NSUInteger)retainCount {  return NSUIntegerMax; //denotes an object that cannot be released }  - (void)release {  //do nothing }  - (id)autorelease {  return self; } 

可以看到这种方式,使用静态成员维持了一个永久存在的对象,而且覆盖了alloc方法(alloc方法会调用allocWithZone:方法),并且也覆盖了所有与引用技术有关的方法,这都使这个对象不会被销毁。这样看上去基本实现了我们需要的,但是写起来麻烦不说,还有很大的一个问题,那就是多线程问题,如果是在多线程中那么该种方法就不能保证只产生一个对象了。所以这种方式只是介绍一下,并不推荐使用。

第二种

第二种跟第一种差不多,也是通过覆盖NSObject的方法实现的,但是它在第一种的基础上增加了多线程的处理,所以即使在多线程下,该种方法创建的对象也是唯一的。这种方法已经有大牛为我们写好了,全都都是通过C的宏定义#define出来了。现给出该头文件:

(SynthesizeSingleton.h) download
1
2
3
4
5
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值