读《禅与 Objective-C 编程艺术》

关于单例

要注意两个问题,一是线程安全问题;二是要确保它是一个单例。

线程安全问题推荐通过使用 dispatch_once() 来解决,取代 iOS4.0 之前没有 GCD 时期使用的 @synchronized 方案。

1
2
3
4
5
6
7
8
9
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}

dispatch_once() 的优点是,它更快,而且语法上更干净。同时可以避免 possible and sometimes prolific crashes 。《Effective Objective-C 2.0》在《块与大中枢派发》一章中也有关于使用 dispatch_once() 来创建单例的说明,从安全和性能两个角度说明了为什么要使用 dispatch_once() 。

使用 dispatch_once() 可以简化代码并且彻底保证线程安全,开发者根本无需担心加锁或者同步。所有问题都由GCD底层处理。由于每次调用时都必须使用完全相同的标记,所以标记要声明成static。把该变量定义在static作用域中,可以保证编译器在每次执行sharedInstance方法时都会复用这个变量,而不会创建新变量。

此外,dispatch_once() 更高效。它没有使用重量级的同步机制,若是那样做的话,每次运行代码前都要获取锁,相反,此函数采用“原子访问”(atomic access)来查询标记,以判断其所对应的代码是否已经执行过。笔者在自己装有64位 Mac OS X 10.8.2 系统的电脑上简单测试了性能,分别采用 @synchronized 方式以及 dispatch_once() 方式来实现sharedInstance方法,结果显示,后者速度几乎是前者的两倍。

—— 引自《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》

上面这段代码看上去好像没什么问题,但是真的就能确保这样创建的单例,它一定是一个单例吗?

看看苹果文档中创建一个单例的代码和过程:

代码示例:

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 MyGizmoClass *sharedGizmoManager = nil;

+ (MyGizmoClass*)sharedManager
{
if (sharedGizmoManager == nil) {
sharedGizmoManager = [[super allocWithZone:NULL] init];
}
return sharedGizmoManager;
}

+ (id)allocWithZone:(NSZone *)zone
{
return [[self sharedManager] 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;
}

过程:

  1. It declares a static instance of your singleton object and initializes it to nil.
  2. In your class factory method for the class (named something like “sharedInstance” or “sharedManager”), it generates an instance of the class but only if the static instance is nil.
  3. It overrides the allocWithZone: method to ensure that another instance is not allocated if someone tries to allocate and initialize an instance of your class directly instead of using the class factory method. Instead, it just returns the shared object.
  4. It implements the base protocol methods copyWithZone:, release, retain, retainCount, and autorelease to do the appropriate things to ensure singleton status. (The last four of these methods apply to memory-managed code, not to garbage-collected code.)

注意上面的步骤3、步骤4。

步骤3在创建单例时是有重写 allocWithZone: 方法来避免通过调用 alloc、allocWithZone 创建另一个实例出来。

步骤4重写了遵循 NSCopying 协议的 copyWithZone: 方法,来避免调用 copy 方法时产生崩溃(对于自己定义的类,要实现 copy 就要默认实现 NSCopying 协议,同理实现mutablecopy 就要实现 NSMutableCopying 协议,否则调用相应 copy 方法时会引起崩溃)。

步骤4还重写了部分内存管理相关的方法,这些方法有些在 ARC 时代已经可以不用管他们了。

综上,写一个单例时要注意的三个点:

  1. 使用 dispatch_once() 来保证线程安全和性能。
  2. 重写 allocWithZone: 方法来避免通过调用 alloc、allocWithZone 创建另一个实例出来。
  3. 重写遵循 NSCopying、NSMutableCopying 协议的方法,避免调用相关copy方法时崩溃。

Protocols

在 Objective-C 里是通过 protocol 来实现抽象接口的。Apple 几乎只是在委托模式下使用 protocol。但是,在一些场景下,使用 protocol 可以把非常糟糕的设计的架构改造为一个良好的可复用的代码。

SOLID

S——单一对象原则
O——开闭原则
L——里氏替换原则
I——接口隔离原则
D——依赖反转原则

文章作者: ゴウサク
文章链接: http://dapaner.top/2019/07/10/读《禅与-Objective-C-编程艺术》/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Corner&Coder
微信赞赏码
支付宝赞赏码