GeekerProbe

水滴石穿 Keeping faith.      --- wtlucky's Blog

23种设计模式——单例模式

| Comments

本学期开了一门课程叫做《软件体系结构》,讲的主要是设计模式的东西,而我在之前也看过设计模式的书,正好借此机会来整理一下自己所学到的知识,因为自己在做iOS开发,所以基本上这23种设计模式我都通过objective-C来实现了。此系列文章的类图都是来自《设计模式之禅》,有兴趣的同学可以去买这本书看。

话说,在编码编到一定的程度以后,由于代码体系的庞大,结构的复杂,自然就会上升到设计模式高度,而现在的软件设计又基本都是面向对象的,所以有了设计模式作支持,可以使软件更加的稳定安全,也更易于维护与拓展。

首先来介绍最常用最简单的单例模式(Singleton),在以后的文章中再依次介绍其他的模式。

单例模式定义

Ensure a class has only one instance, and provide a global point of access to it. (确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)

单例模式类图

单例模式介绍

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

单例模式优点

  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方法来实例化一个对象,所以这并不是严格意义上的单例模式,但是一般程序都是我们自己写,我们自己记得就好了,这也没什么可担心的,从这一点上来说第二种方法又是比较好的,具体使用的时候呢,根据实际情况来吧,各取所需就好了。

Comments