我正在尝试找出适合在Swift中使用的单例模型 . 到目前为止,我已经能够得到一个非线程安全模型:
class var sharedInstance:TPScopeManager {
get {
struct Static {
static var instance : TPScopeManager? = nil
}
if !Static.instance {
Static.instance = TPScopeManager()
}
return Static.instance!
}
}
在Static结构中包装单例实例应该允许单个实例在没有复杂命名方案的情况下不与单例实例发生冲突,并且它应该使事情相当私密 . 显然,这个模型不是线程安全的,所以我试着将dispatch_once添加到整个事情中:
class var sharedInstance:TPScopeManager {
get {
struct Static {
static var instance : TPScopeManager? = nil
static var token : dispatch_once_t = 0
}
dispatch_once(Static.token) { Static.instance = TPScopeManager() }
return Static.instance!
}
}
但是我在 dispatch_once
行上遇到编译器错误:
无法将表达式的类型'Void'转换为'()'类型
我已经尝试了几种不同的语法变体,但它们似乎都有相同的结果:
dispatch_once(Static.token, { Static.instance = TPScopeManager() })
使用Swift dispatch_once
的正确用法是什么?我最初认为问题出在块中,因为错误消息中的 ()
,但是我看的越多,我认为可能是正确定义 dispatch_once_t
的问题 .
30 回答
在看到David 's implementation, it seems like there'之后,不需要单例类函数instanceMethod,因为
let
与sharedInstance类方法几乎完全相同 . 你需要做的就是将它声明为一个全局常量,就是这样 .Swift 1.2以上的最佳方法是单行单例,如 -
要了解有关此方法的更多详细信息,请访问此link .
唯一正确的方法如下
访问
Reasons:
静态类型属性保证只延迟初始化一次,即使同时跨多个线程访问,也不需要使用dispatch_once
私有化init方法,因此其他类不能创建实例 .
final类,因为您不希望其他类继承Singleton类
这是最简单的具有线程安全功能的 . 没有其他线程可以访问相同的单例对象,即使他们想要 . Swift 3/4
我建议使用Enum,就像你在Java中使用的那样,例如:
我倾向于使用以下语法作为最完整的语法:
这适用于Swift 1.2到4,并提供了几个优点:
提醒用户不要实现子类
防止创建其他实例
确保延迟创建和独特的实例化
通过允许访问实例_115645_缩短语法(avoidids())
Swift单例在Cocoa框架中作为类函数公开,例如
NSFileManager.defaultManager()
,NSNotificationCenter.defaultCenter()
,所以我觉得作为镜像这种行为的类函数更有意义,而不是像其他一些解决方案那样使用的类变量,例如通过
MyClass.sharedInstance()
检索单例 .使用静态变量和私有初始值设定项来创建单例类 .
For Swift 1.2 and beyond:
有了正确性证明(所有信用证都是here),现在几乎没有理由使用任何以前的单身人士方法 .
Update :现在这是 official 定义单例的方法,如_135581中所述!
至于使用
static
vsclass
的问题 . 即使class
变量可用,static
也应该是使用的 . 单例并不意味着被子类化,因为这将导致基本单例的多个实例 . 使用static
以漂亮,Swifty的方式强制执行此操作 .For Swift 1.0 and 1.1:
随着最近Swift的变化,主要是新的访问控制方法,我现在倾向于使用全局变量来实现单例的更清晰的方式 .
如Swift博客文章_135589中所述:
这种创建单例的方法是线程安全,快速,懒惰,并且还可以免费桥接到ObjC .
使用:
如何使用:
看看Apple的示例代码,我遇到了这种模式 . 我不确定Swift如何处理静态,但这在C#中是线程安全的 . 我包括Objective-C互操作的属性和方法 .
简单来说,
你可能想阅读Files and Initialization
我更喜欢这个实现:
Swift在过去实现单例,只不过是三种方式:全局变量,内部变量和dispatch_once方式 .
这里有两个很好的单身人士 . (注意:无论何种写作都必须注意私有化的init()方法 . 因为在Swift中,所有对象的构造函数默认都是public,需要重写init才能变为private,防止此类的其他对象'()'默认初始化方法创建对象 . )
方法1:
方法2:
tl; dr:如果您使用的是Swift 1.2或更高版本,请使用 class constant 方法;如果您需要支持早期版本,请使用 nested struct 方法 .
根据我使用Swift的经验,有三种方法可以实现支持延迟初始化和线程安全的Singleton模式 .
类常量
这种方法支持延迟初始化,因为Swift懒惰地初始化类常量(和变量),并且通过
let
的定义是线程安全的 . 现在officially recommended way实例化一个单例 .类常量在Swift 1.2中引入 . 如果需要支持早期版本的Swift,请使用下面的嵌套结构方法或全局常量 .
嵌套结构
这里我们使用嵌套结构的静态常量作为类常量 . 这是Swift 1.1及更早版本中缺少静态类常量的一种解决方法,并且仍然可以作为函数中缺少静态常量和变量的解决方法 .
dispatch_once
传统的Objective-C方法移植到Swift . 我相当肯定没有优于嵌套结构方法的优势,但无论如何我都把它放在这里,因为我发现语法上的差异很有趣 .
有关单元测试,请参阅此GitHub项目 .
Swift 1.2或更高版本现在支持类中的静态变量/常量 . 所以你可以使用一个静态常量:
我在Swift中的实现方式......
ConfigurationManager.swift
通过以下方式从应用程序的任何屏幕访问globalDic .
读:
写:
根据Apple documentation,已多次重复,在Swift中执行此操作的最简单方法是使用静态类型属性:
但是,如果您正在寻找一种方法来执行除简单构造函数调用之外的其他设置,则秘诀是使用立即调用的闭包:
这保证是线程安全的,只能初始化一次 .
然后打电话给它;
这是我的实施 . 它还可以防止程序员创建新实例:
Swift 4+
如果您计划在Objective-C中使用Swift单例类,则此设置将使编译器生成适当的类似Objective-C的标头:
然后在Objective-C课程中,你可以像在Swift之前的日子里那样调用你的单身人士:
这只是我简单的实现 .
有一种更好的方法 . 你可以在类的decleration之上声明一个全局变量,就像这样
这只是调用你的默认初始化,或者在Swift中默认调用init和全局变量dispatch_once . 然后,在您想要获得参考的任何课程中,您只需执行以下操作:
所以基本上你可以摆脱整个共享实例代码块 .
既然Apple已经澄清了静态结构变量的初始化既懒又包裹在dispatch_once中(参见帖子末尾的注释),我认为我的最终解决方案将是:
这利用了静态结构元素的自动惰性,线程安全初始化,安全地隐藏了消费者的实际实现,保持所有紧凑区分以便易读,并消除了可见的全局变量 .
Apple澄清了懒惰的初始化程序是线程安全的,所以不需要
dispatch_once
或类似的保护从here
我刚刚遇到过这个,但是我需要我的单例来允许继承,而这些解决方案都没有实际允许它 .
所以我想出了这个:
这种方式首先执行Singleton.sharedInstance()时会返回Singleton的实例
首先执行SubSingleton.sharedInstance()时,它将返回创建的SubSingleton实例 .
如果完成上述操作,则SubSingleton.sharedInstance()为Singleton为true且使用相同的实例 .
第一个脏方法的问题是我无法保证子类将实现dispatch_once_t并确保每个类仅修改一次sharedInstanceVar ...
我将尝试进一步完善这一点,但是看看是否有人对此有强烈的感情(除此之外)会很有趣这是一个冗长的事实,需要手动更新它 .
From Apple Docs (Swift 3.0.1),
First solution
稍后在您的代码中:
Second solution
稍后在您的代码中,您将能够保持大括号以减少混淆:
仅供参考,以下是Jack Wu / hpique的嵌套结构实现的Singleton实现示例 . 该实现还显示了归档如何工作,以及一些附带的功能 . 我找不到这个完整的例子,所以希望这有助于某人!
如果你不认识其中的一些功能,这里有一个我一直在使用的生活Swift实用程序文件: