设计模式通常被认为是更高级的主题,因此对于iOS或OS X开发的新手来说,它们会被忽略或忽视。 但是,从一开始,每个有抱负的iOS或OS X开发人员都会面临许多设计模式。 单例模式就是这样一种模式。
在开始研究在Swift和Objective-C中实现单例模式的技术之前,我想花一点时间来理解单例模式,包括其优缺点。
1.什么是单例?
真实和功能单例
单例模式与面向对象的编程密不可分。 单例模式的定义很简单,只能创建一个单例类实例,或者在任何时候都可以运行。
但是, 布伦特·西蒙斯 ( Brent Simmons)通过区分真实单身人士和功能性单身人士来细化此定义。 Brent将真正的单例定义为始终返回自身相同实例的类。 不能创建一个以上的类实例。 功能单例仅创建一次,并在多个地方使用。
我确定许多开发人员不会将功能单例归类为单例,但是我喜欢布伦特所做的区分。 功能性单例通常没有真正的单例所具有的缺点。 我们将在本文后面部分讨论这些缺点。
如果您熟悉iOS或OS X的开发,那么您会注意到Apple的框架使用了singleton模式。 例如,一个iOS应用程序只能有一个UIApplication
类的实例,您可以通过sharedApplication
类方法进行访问。
UIApplication *sharedApplication = [UIApplication sharedApplication];
let sharedApplication = UIApplication.sharedApplication()
即使UIApplication
类使您可以访问UIApplication
单例,也不会阻止您显式实例化UIApplication
实例。
UIApplication *newApplication = [[UIApplication alloc] init];
let newApplication = UIApplication()
但是,结果是运行时异常。 该异常清楚地表明,任何时候UIApplication
类的一个实例都只能处于活动状态。 换句话说, UIApplication
类在设计时考虑了单例模式。
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'There can only be one UIApplication instance.'
优点
辅助功能
单例模式最明显的优势是可访问性。 以UIApplication
类为例。 通过导入UIKit框架,可以从项目中的任何位置访问UIApplication
单例。 看下面的UIViewController
子类示例。
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
#import "ViewController.h"
@implementation ViewController
#pragma mark -
#pragma mark View Life Cycle
- (void)viewDidLoad {
[super viewDidLoad];
// Access Application Singleton
UIApplication *application = [UIApplication sharedApplication];
}
@end
控制与行为
有时只有一个类的一个实例处于活动状态,这是有用的或必不可少的。 UIApplication
类是此要求的一个示例。 其他示例可能包括用于日志记录,缓存或I / O操作的类。
就是说,重要的是要理解单身人士是实现控制和行为的一种手段。 防御性编码技术可以为您带来相同的结果,而不会造成单例模式的精神负担。
缺点
全球状态
通过创建单例,您实际上是在创建全局状态。 知道这一点很重要。 几年前,MiškoHevery写了一篇有关单例模式的出色文章 ,其中他解释了为什么应避免使用该模式。 Miško强调,单身人士有害的主要原因是全局状态。
虽然有例外。 例如,不可变的单例几乎没有害处。 您仍在创建全局状态,但是该状态是不可变的。 Miško还提到记录器是单例模式的某种可接受的应用程序,因为状态是从应用程序进入记录器的一个方向。
紧耦合
大多数面向对象的编程语言都认为模块紧密耦合是一种不好的做法。 换句话说,建议尽可能将模块解耦。 这使单例模式处于不利位置,因此,许多受尊敬的程序员都认为单例是不好的做法。 一些开发人员甚至认为它是应该避免的反模式 。
为了更好地理解该问题,请考虑以下情形。 您已经创建了带有联网组件的iOS应用程序,您可以在其中访问UIApplication
单例。 您的iOS应用程序非常成功,您可以考虑创建OS X应用程序。 但是,由于缺少UIKit框架,因此UIApplication
类在OS X上不可用。 这意味着您将必须重构或修改iOS应用程序的网络组件。
有许多解决此问题的解决方案,但我想指出的是,您已将网络模块紧密地耦合到UIKit框架,尤其是UIApplication
类。
可重用性
紧密耦合通常与可重用性紧密相关。 紧密耦合的对象图通常很难在不重构的情况下重用。 可重用性有时是不可避免的,但是在开发软件时,一定要牢记这一点。
2.创建一个单例
物镜
在Objective-C中创建单例非常容易。 在以下代码片段中,我们创建并实现了一个日志记录类Logger
。 我们通过sharedLogger
类方法访问单例对象。
#import <Foundation/Foundation.h>
@interface Logger : NSObject
#pragma mark -
#pragma mark Class Methods
+ (Logger *)sharedLogger;
@end
实现很简单。 我们声明一个静态变量_sharedInstance
,它将保存对单例对象的引用。 我们调用Grand Central Dispatch函数dispatch_once
,并传入一个用于初始化Logger
类实例的块。 我们将对实例的引用存储在_sharedInstance
。
#import "Logger.h"
@implementation Logger
#pragma mark -
#pragma mark Class Methods
+ (Logger *)sharedLogger {
static Logger *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
@end
dispatch_once
函数可确保我们通过的块在应用程序的生命周期内执行一次。 这非常重要,因为我们只想初始化Logger
类的一个实例。
dispatch_once
函数使用作为dispatch_once
函数的第一个参数传递的oncePredicate
变量,以确保该块仅执行一次。
sharedLogger
类方法通过返回存储在_sharedInstance
的引用来返回Logger
实例。 sharedLogger
, sharedLogger
的实现可能会让人望而生畏,但是我相信您同意这个想法非常简单。
Swift
为了在Swift中实现单例模式,我们使用了Swift 1.2中引入的类常量。 如果您在以下代码段中遇到问题,请确保您使用的是Xcode 6.3+。
class Logger {
static let sharedInstance = Logger()
}
让我Logger
介绍一下Logger
类的简短实现。 我们首先声明一个名为Logger
的类。 为了实现单例模式,我们声明了Logger
类型的类型属性sharedInstance
。
通过使用static
关键字,将sharedInstance
常量绑定到Logger
类而不是该类的实例。 由于sharedInstance
常量与类型(即Logger
类)相关联,因此将其称为类型属性 。 我们通过Logger
类访问单例对象。
Logger.sharedInstance
您可能想知道为什么我们不像在Objective-C实现中那样使用dispatch_once
函数。 在幕后 ,Swift在初始化静态常量时已经使用了dispatch_once
。 这是您在Swift中免费获得的东西。
实现单例模式的另一种常见方法是利用嵌套类型。 在下面的示例中,我们声明一个Logger
类型的计算类型属性sharedInstance
。 在计算类型属性的关闭中,我们声明一个名为Singleton
的结构。 该结构定义了类型为Logger
的常量类型属性( instance
。
class Logger {
class var sharedInstance: Logger {
struct Singleton {
static let instance: Logger = Logger()
}
return Singleton.instance
}
}
两种策略访问单例对象都是相同的。 如果您使用的是Swift 1.2,则我建议使用第一种方法以使其简洁明了。
3.何时使用单例?
如果您仅有的工具是锤子,我想把所有东西都当作钉子来对待是很诱人的。 — 亚伯拉罕·马斯洛
当我第一次听说单例模式时,我为它在下一个项目中的实用性感到兴奋。 使用似乎可以解决许多问题的工具非常诱人。 乍一看,单例模式可能看起来像银子弹或金锤 ,但这并不是单例模式。
例如,如果您只是为了使单例易于访问而使用单例,则可能是由于错误的原因而使用了单例模式。 并不是因为Apple在自己的框架中使用单例模式才是解决每个问题的正确方法。
单例可能非常有用,但是许多经验丰富的程序员都将它们视为反模式。 他们中的大多数人对得出这一结论的模式都缺乏经验。 从他们的错误中学习并谨慎使用它们。
我不认为单例模式是反模式,但应谨慎使用该模式。 如果您很想将对象变成单例,请问自己一个问题,是否还有其他方法可以解决问题。 在大多数情况下,单例模式不是唯一可用的解决方案。 替代解决方案可能需要更多的工作或一些开销,但从长远来看可能会更好。
布伦特·西蒙斯 ( Brent Simmons)所指的功能单例通常是真正单例的完美有效替代方案。 创建一个类的实例并将其传递给需要它的对象没有错。 下次您要创建另一个单例时,请考虑使用此方法。
结论
单例模式既简单又强大,但应谨慎使用。 您的一些同事可能会建议您不要这样做,但是我认为重要的是要考虑使用它并自己判断是否认为它对您的工具箱有用。
翻译自: https://code.tutsplus.com/articles/design-patterns-singletons--cms-23886