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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
//
//  SynthesizeSingleton.h
//
// Modified by Karl Stenerud starting 16/04/2010.
// - Moved the swizzle code to allocWithZone so that non-default init methods may be
//   used to initialize the singleton.
// - Added "lesser" singleton which allows other instances besides sharedInstance to be created.
// - Added guard ifndef so that this file can be used in multiple library distributions.
// - Made singleton variable name class-specific so that it can be used on multiple classes
//   within the same compilation module.
//
//  Modified by CJ Hanson on 26/02/2010.
//  This version of Matt's code uses method_setImplementaiton() to dynamically
//  replace the +sharedInstance method with one that does not use @synchronized
//
//  Based on code by Matt Gallagher from CocoaWithLove
//
//  Created by Matt Gallagher on 20/10/08.
//  Copyright 2009 Matt Gallagher. All rights reserved.
//
//  Permission is given to use this source code file without charge in any
//  project, commercial or otherwise, entirely at your risk, with the condition
//  that any redistribution (in part or whole) of source code must retain
//  this copyright and permission notice. Attribution in compiled projects is
//  appreciated but not required.
//

#ifndef SYNTHESIZE_SINGLETON_FOR_CLASS

#import <objc/runtime.h>


#pragma mark -
#pragma mark Singleton

/* Synthesize Singleton For Class
 *
 * Creates a singleton interface for the specified class with the following methods:
 *
 * + (MyClass*) sharedInstance;
 * + (void) purgeSharedInstance;
 *
 * Calling sharedInstance will instantiate the class and swizzle some methods to ensure
 * that only a single instance ever exists.
 * Calling purgeSharedInstance will destroy the shared instance and return the swizzled
 * methods to their former selves.
 *
 *
 * Usage:
 *
 * MyClass.h:
 * ========================================
 *      #import "SynthesizeSingleton.h"
 *
 *      @interface MyClass: SomeSuperclass
 *      {
 *              ...
 *      }
 *      SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(MyClass);
 *
 *      @end
 * ========================================
 *
 *
 *      MyClass.m:
 * ========================================
 *      #import "MyClass.h"
 *
 *      // This line is optional. Use it if you've enabled GCC_WARN_UNDECLARED_SELECTOR
 *      SYNTHESIZE_SINGLETON_FOR_CLASS_PROTOTYPE(MyClass);
 *
 *      @implementation MyClass
 *
 *      SYNTHESIZE_SINGLETON_FOR_CLASS(MyClass);
 *
 *      ...
 *
 *      @end
 * ========================================
 *
 *
 * Note: Calling alloc manually will also initialize the singleton, so you
 * can call a more complex init routine to initialize the singleton like so:
 *
 * [[MyClass alloc] initWithParam:firstParam secondParam:secondParam];
 *
 * Just be sure to make such a call BEFORE you call "sharedInstance" in
 * your program.
 */

#define SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(SS_CLASSNAME)     \
\
+ (SS_CLASSNAME*) sharedInstance;       \
+ (void) purgeSharedInstance;


#define SYNTHESIZE_SINGLETON_FOR_CLASS_PROTOTYPE(SS_CLASSNAME) \
@interface SS_CLASSNAME (SynthesizeSingletonPrivate)    \
- (NSUInteger)retainCountDoNothing;     \
- (NSUInteger)retainCountDoSomething;   \
- (void)releaseDoNothing;       \
- (void)releaseDoSomething;     \
- (id)autoreleaseDoNothing;     \
- (id)autoreleaseDoSomething; \
@end

#define SYNTHESIZE_SINGLETON_FOR_CLASS(SS_CLASSNAME)    \
\
static volatile SS_CLASSNAME* _##SS_CLASSNAME##_sharedInstance = nil;   \
\
+ (volatile SS_CLASSNAME*) sharedInstanceNoSynch        \
{       \
return (volatile SS_CLASSNAME*) _##SS_CLASSNAME##_sharedInstance;       \
}       \
\
+ (volatile SS_CLASSNAME*) sharedInstanceSynch  \
{       \
@synchronized(self)     \
{       \
if(nil == _##SS_CLASSNAME##_sharedInstance)     \
{       \
_##SS_CLASSNAME##_sharedInstance = [[self alloc] init]; \
}       \
}       \
return (volatile SS_CLASSNAME*) _##SS_CLASSNAME##_sharedInstance;       \
}       \
\
+ (volatile SS_CLASSNAME*) sharedInstance       \
{       \
return (volatile SS_CLASSNAME*)[self sharedInstanceSynch]; \
}       \
\
+ (id)allocWithZone:(NSZone*) zone      \
{       \
@synchronized(self)     \
{       \
if (nil == _##SS_CLASSNAME##_sharedInstance)    \
{       \
_##SS_CLASSNAME##_sharedInstance = [super allocWithZone:zone];  \
if(nil != _##SS_CLASSNAME##_sharedInstance)     \
{       \
Method newSharedInstanceMethod = class_getClassMethod(self, @selector(sharedInstanceNoSynch));  \
method_setImplementation(class_getClassMethod(self, @selector(sharedInstance)), method_getImplementation(newSharedInstanceMethod));     \
method_setImplementation(class_getInstanceMethod(self, @selector(retainCount)), class_getMethodImplementation(self, @selector(retainCountDoNothing)));  \
method_setImplementation(class_getInstanceMethod(self, @selector(release)), class_getMethodImplementation(self, @selector(releaseDoNothing)));  \
method_setImplementation(class_getInstanceMethod(self, @selector(autorelease)), class_getMethodImplementation(self, @selector(autoreleaseDoNothing)));  \
}       \
}       \
}       \
return (id)_##SS_CLASSNAME##_sharedInstance;    \
}       \
\
+ (void)purgeSharedInstance     \
{       \
@synchronized(self)     \
{       \
if(nil != _##SS_CLASSNAME##_sharedInstance)     \
{       \
Method newSharedInstanceMethod = class_getClassMethod(self, @selector(sharedInstanceSynch));    \
method_setImplementation(class_getClassMethod(self, @selector(sharedInstance)), method_getImplementation(newSharedInstanceMethod));     \
method_setImplementation(class_getInstanceMethod(self, @selector(retainCount)), class_getMethodImplementation(self, @selector(retainCountDoSomething)));        \
method_setImplementation(class_getInstanceMethod(self, @selector(release)), class_getMethodImplementation(self, @selector(releaseDoSomething)));        \
method_setImplementation(class_getInstanceMethod(self, @selector(autorelease)), class_getMethodImplementation(self, @selector(autoreleaseDoSomething)));        \
[_##SS_CLASSNAME##_sharedInstance release];     \
_##SS_CLASSNAME##_sharedInstance = nil; \
}       \
}       \
}       \
\
- (id)copyWithZone:(NSZone *)zone       \
{       \
return self;    \
}       \
\
- (id)retain    \
{       \
return self;    \
}       \
\
- (NSUInteger)retainCount       \
{       \
NSAssert1(1==0, @"SynthesizeSingleton: %@ ERROR: -(NSUInteger)retainCount method did not get swizzled.", self); \
return NSUIntegerMax;   \
}       \
\
- (NSUInteger)retainCountDoNothing      \
{       \
return NSUIntegerMax;   \
}       \
- (NSUInteger)retainCountDoSomething    \
{       \
return [super retainCount];     \
}\
\
- (oneway void)release  \
{       \
NSAssert1(1==0, @"SynthesizeSingleton: %@ ERROR: -(void)release method did not get swizzled.", self);   \
}       \
\
- (void)releaseDoNothing{}      \
\
- (void)releaseDoSomething      \
{       \
@synchronized(self)     \
{       \
[super release];        \
}       \
}       \
\
- (id)autorelease       \
{       \
NSAssert1(1==0, @"SynthesizeSingleton: %@ ERROR: -(id)autorelease method did not get swizzled.", self); \
return self;    \
}       \
\
- (id)autoreleaseDoNothing      \
{       \
return self;    \
}       \
\
- (id)autoreleaseDoSomething    \
{       \
return [super autorelease];     \
}


#pragma mark -
#pragma mark Lesser Singleton

/* A lesser singleton has a shared instance, but can also be instantiated on its own.
 *
 * For a lesser singleton, you still use SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(),
 * but use SYNTHESIZE_LESSER_SINGLETON_FOR_CLASS() in the implementation file.
 * You must specify which creation methods are to initialize the shared instance
 * (besides "sharedInstance") via CALL_LESSER_SINGLETON_INIT_METHOD()
 *
 * Example:
 *
 * MyClass.h:
 * ========================================
 *      #import "SynthesizeSingleton.h"
 *
 *      @interface MyClass: SomeSuperclass
 *      {
 *              int value;
 *              ...
 *      }
 *      SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(MyClass);
 *
 *      + (void) initSharedInstanceWithValue:(int) value;
 *
 * - (id) initWithValue:(int) value;
 *
 *      @end
 * ========================================
 *
 *
 *      MyClass.m:
 * ========================================
 *      #import "MyClass.h"
 *
 *      // This line is optional. Use it if you've enabled GCC_WARN_UNDECLARED_SELECTOR
 *      SYNTHESIZE_SINGLETON_FOR_CLASS_PROTOTYPE(MyClass);
 *
 *      @implementation MyClass
 *
 *      SYNTHESIZE_LESSER_SINGLETON_FOR_CLASS(MyClass);
 *
 *      + (void) initSharedInstanceWithValue:(int) value
 *      {
 *              CALL_LESSER_SINGLETON_INIT_METHOD(MyClass, initWithValue:value);
 *      }
 *
 *      ...
 *
 *      @end
 * ========================================
 *
 *
 * Note: CALL_LESSER_SINGLETON_INIT_METHOD() will not work if your
 * init call contains commas. If you need commas (such as for varargs),
 * or other more complex initialization, use the PRE and POST macros:
 *
 *      + (void) initSharedInstanceComplex
 *      {
 *              CALL_LESSER_SINGLETON_INIT_METHOD_PRE(MyClass);
 *
 *              int firstNumber = [self getFirstNumberSomehow];
 *              _sharedInstance = [[self alloc] initWithValues:firstNumber, 2, 3, 4, -1];
 *
 *              CALL_LESSER_SINGLETON_INIT_METHOD_POST(MyClass);
 *      }
 */
#define SYNTHESIZE_LESSER_SINGLETON_FOR_CLASS(SS_CLASSNAME)     \
\
static volatile SS_CLASSNAME* _##SS_CLASSNAME##_sharedInstance = nil;   \
\
+ (SS_CLASSNAME*) sharedInstanceNoSynch \
{       \
return (SS_CLASSNAME*) _##SS_CLASSNAME##_sharedInstance;        \
}       \
\
+ (SS_CLASSNAME*) sharedInstanceSynch   \
{       \
@synchronized(self)     \
{       \
if(nil == _##SS_CLASSNAME##_sharedInstance)     \
{       \
_##SS_CLASSNAME##_sharedInstance = [[self alloc] init]; \
if(_##SS_CLASSNAME##_sharedInstance)    \
{       \
Method newSharedInstanceMethod = class_getClassMethod(self, @selector(sharedInstanceNoSynch));  \
method_setImplementation(class_getClassMethod(self, @selector(sharedInstance)), method_getImplementation(newSharedInstanceMethod));     \
}       \
}       \
}       \
return (SS_CLASSNAME*) _##SS_CLASSNAME##_sharedInstance;        \
}       \
\
+ (volatile SS_CLASSNAME*)sharedInstance        \
{       \
return (volatile SS_CLASSNAME*) [self sharedInstanceSynch]; \
}       \
\
+ (void)purgeSharedInstance     \
{       \
@synchronized(self)     \
{       \
Method newSharedInstanceMethod = class_getClassMethod(self, @selector(sharedInstanceSynch));    \
method_setImplementation(class_getClassMethod(self, @selector(sharedInstance)), method_getImplementation(newSharedInstanceMethod));     \
[_##SS_CLASSNAME##_sharedInstance release];     \
_##SS_CLASSNAME##_sharedInstance = nil; \
}       \
}
#define CALL_LESSER_SINGLETON_INIT_METHOD_PRE(SS_CLASSNAME) \
@synchronized(self)     \
{       \
if(nil == _##SS_CLASSNAME##_sharedInstance)     \
{
#define CALL_LESSER_SINGLETON_INIT_METHOD_POST(SS_CLASSNAME) \
if(_##SS_CLASSNAME##_sharedInstance)    \
{       \
Method newSharedInstanceMethod = class_getClassMethod(self, @selector(sharedInstanceNoSynch));  \
method_setImplementation(class_getClassMethod(self, @selector(sharedInstance)), method_getImplementation(newSharedInstanceMethod));     \
}       \
}       \
}
#define CALL_LESSER_SINGLETON_INIT_METHOD(SS_CLASSNAME,__INIT_CALL__) \
CALL_LESSER_SINGLETON_INIT_METHOD_PRE(SS_CLASSNAME); \
_##SS_CLASSNAME##_sharedInstance = [[self alloc] __INIT_CALL__];        \
CALL_LESSER_SINGLETON_INIT_METHOD_POST(SS_CLASSNAME)
#endif /* SYNTHESIZE_SINGLETON_FOR_CLASS */

使用时也非常方便,该头文件也已给出使用方法,在这里我在说一下,供那些E文不好的同学使用。

使用这种方式首先把该头文件加到我们的项目中,然后直接使用就可以了:

Singleton.h
1
2
3
4
5
6
7
8
9
10
#import <Foundation/Foundation.h>
#import "SynthesizeSingleton.h"

@interface Singleton : NSObject

SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(Singleton);

//定义该类的属性,方法等

@end
Singleton.m
1
2
3
4
5
6
7
@implementation Singleton

SYNTHESIZE_SINGLETON_FOR_CLASS(Singleton);

//属性方法的实现

@end

如此一来在使用时,通过[Singleton sharedInstance]就可以获得该类的单例对象了。 这种方法由于有了这个头文件的支持,所以使得使用单例方便多了,而且也避免了多线程的问题。

第三种

这是最后一种也是我最推荐的一种。iOS在4.0以后推出了blockGCD,这两个特性给iOS开发带来的很大的便利,也使开发变得更加趣味话。那么如何通过GCD+block来实现单例模式呢,这主要归功于dispatch_once(dispatch_once_t *predicate, ^(void)block)这个GCD的函数,他有两个参数第一参数是一个指向dispatch_once_t类型结构体的指针,用来测试block是否执行完成,该指针所指向的结构体必须是全局的或者静态的,第二个参数是一个返回值与参数均为空的block,在block体中进行对象的初始化即可。dispatch_once在程序的生命周期中保证只会被调用一次,所以在多线程中也不会有问题。 该种方法使用方法:

Singleton
1
2
3
4
5
6
7
8
9
10
11
+ (Singleton *)sharedInstance
{
    static Singleton *instance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[Singleton alloc]init];
    });

    return instance;
}

使用该种方法只需要这简单的几句代码就可以实现单例了。使用起来非常方便,但是这种创建单例的方法也不是完美的,它并不能阻止人们通过alloc方法来实例化一个对象,所以这并不是严格意义上的单例模式,但是一般程序都是我们自己写,我们自己记得就好了,这也没什么可担心的,从这一点上来说第二种方法又是比较好的,具体使用的时候呢,根据实际情况来吧,各取所需就好了。

 

posted @ 2015-11-17 13:01  FMDN  阅读(402)  评论(0编辑  收藏  举报