我对Swift还是很陌生,作为一个单独的开发人员,我想知道是否有人可以通过下面的单例实现注释。该代码确实有效,但是作为Swift的新手,并且知道有很多陷阱,我想从经验更丰富的人那里获得一些反馈。

// Test Singleton Swift 1.2
class FGSingleton {
    static var instance: FGSingleton!
    var gameScore: Int = 0

    // SHARED INSTANCE
    class func sharedInstance() -> FGSingleton {
        self.instance = (self.instance ?? FGSingleton())
        return self.instance
    }

    // METHODS
    init() {
        println(__FUNCTION__)
    }
    func displayGameScore() {
        println("\(__FUNCTION__) \(self.gameScore)")
    }
    func incrementGameScore(scoreInc: Int) {
        self.gameScore += scoreInc
    }
}


输出:


FGSingleton.sharedInstance().displayGameScore()
FGSingleton.sharedInstance().incrementGameScore(100)
FGSingleton.sharedInstance().incrementGameScore(1)
FGSingleton.sharedInstance().displayGameScore()
FGSingleton.sharedInstance().incrementGameScore(1000)
FGSingleton.sharedInstance().displayGameScore()



#1 楼

您可以大大简化上述内容:

class FGSingleton {
    static let sharedInstance = FGSingleton()

    var gameScore: Int = 0

    // METHODS
    private init() {
        println(__FUNCTION__)
    }
    func displayGameScore() {
        println("\(__FUNCTION__) \(self.gameScore)")
    }
    func incrementGameScore(scoreInc: Int) {
        self.gameScore += scoreInc
    }
}


并这样称呼:

评论


\ $ \ begingroup \ $
在Swift 1.2上可以正常工作,仅需快速注意一下,sharedInstance应该声明为静态let sharedInstance = FGSingleton()
\ $ \ endgroup \ $
–fuzzygoat
2015年2月12日在11:22



\ $ \ begingroup \ $
你是对的。我删除了class关键字。
\ $ \ endgroup \ $
–花hu
15年2月12日在12:28

\ $ \ begingroup \ $
请注意,这不仅更简单,而且原始问题中的实现也不是线程安全的,而这是安全的(因为静态数据是通过dispatch_once延迟实例化的)。
\ $ \ endgroup \ $
–抢夺
15年2月19日在19:11

\ $ \ begingroup \ $
初始化程序是否应该私有以确保只能创建一个FGSingleton实例?
\ $ \ endgroup \ $
– Apfelsaft
15年4月29日在8:32

\ $ \ begingroup \ $
用这种模式定义的NSObject子类实际上在Swift 2.0中对我来说是死锁。但是,我当前唯一要重现的方法是添加一个断点,该断点将来自共享实例的数据记录在代码中首次引用该共享实例的同一行上。
\ $ \ endgroup \ $
–迈克尔·约翰斯顿(Michael Johnston)
2015年10月2日,下午1:49

#2 楼

首先,对于iOS应用程序而言,将文本打印到控制台绝对是毫无意义的。做为一种简单的方法来测试我们的代码是否按预期工作是可以的,但是我们不想误将其保留在最终版本中。

因此,步骤1:按照

步骤2:在println#if DEBUG中包装所有#endif语句。

示例:<现在,我们的调试版本可以打印有用的调试语句,并且当我们要编译最终版本时,我们不会意外地删除它们。也许更重要的是,在完成最终版本的构建并开始添加功能或修复错误之后,我们将不必浪费时间将它们全部放回原处。

我强烈建议您要么为此提供一个功能调试打印:

    init() {
#if DEBUG
        println(__FUNCTION__)
#endif
    }


,或仅使用以下代码制作Xcode代码段:

    func debugPrintln(s: String) {
#if DEBUG
        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "HH:mm:ss.zzz"
        let timeStamp = dateFormatter.stringFromDate(NSDate())
        println("[" + timeStamp + "]: " + s)    
#endif
    }


将自动完成设置为“ println”



这样,每次键入“ println”时,Xcode都会尝试自动完成上述代码段和“ string”括号内的内容将被选中,并准备通过您的键入将其覆盖。如果您有兴趣,我也可以使用Objective-C的类似产品:




现在,让我们回顾一下您直接问过的问题关于...

首先,我们实际上不需要class方法来获取共享实例。我会选择Rhuantavan的static let建议。

但是,让我们注意我们的访问修饰符。

我们的gameScore实例变量和incrementGameScore实例方法具有相同的访问级别,因此有什么意义?

为什么要这样写:

#if DEBUG
    println(<#string#>)
#endif


我什么时候可以写:

FGSingleton.sharedInstance.incrementGameScore(3)


此外,

FGSingleton.sharedInstance.gameScore += 3


与:

相比似乎有点尴尬。 />
FGSingleton.sharedInstance.incrementGameScore(-3)


相对于简单:

FGSingleton.sharedInstance.gameScore -= 3


我不建议最好的。当然,您不希望用户直接修改游戏得分的可能性很大。但是,在这种情况下,请将gameScore属性设置为private变量,并实现更多方法,并对这些方法中实际发生的情况进行更多的安全检查。或者,如果您不介意他们直接修改游戏得分,只需完全消除不必要的incrementGameScore方法。当人们实例化对象的其他实例时,class效果不佳。该类的设计假设是始终将其用作单例。但是,开发人员经常会犯这样的错误,即不采取任何保护措施来防止实例化对象。

我们不要犯同样的错误。

在Swift中,它就像只需将init方法标记为private即可:

let targetScore = 5
let currentScore = FGSingleton.sharedInstance.gameScore
FGSingleton.sharedInstance.incrementGameScore((-1 * currentScore) + targetScore)


现在,如果我们尝试在任何其他Swift文件中实例化我们的类的对象,我们将得到一个讨厌的错误:



(我将我的班级命名为SwiftSingleton

这就是我们想要的。如果是单例,我们可能不希望人们有其他实例可用...因此不要以为他们不会尝试。完全防止它很容易!


作为一些最终的思考,我们真的需要更深入地研究为什么我们要进行单例课程。在我作为iOS开发人员的所有专业经验中,我从不需要回想起单例。

当我是一个经验不足的程序员时,我感到单身是我试图在各种对象之间(或更可能在视图控制器之间)传递数据的问题的答案。但这不是正确的解决方案。

您真的需要这个单例吗?在需要修改或读取GameScore类所保存数据的对象之间传递某种GameScore类会更好吗?

在我看来,大多数用例单例是实际学习如何在对象之间适当地传递数据的惰性方法。

评论


\ $ \ begingroup \ $
“ ...而且,当我们需要编译最终版本时,我们也不会忘记删除它们。”因为与NSLog不同,println不会写入系统日志,所以这种担心似乎没有那么重要。不要误会我的意思:我仍然会删除它,因为将它包含在生产代码中很愚蠢,但这与NSLog几乎没有相同的问题。
\ $ \ endgroup \ $
–抢夺
15年2月19日在19:16

\ $ \ begingroup \ $
@Rob,很好的信息。您能否分享一些链接,以便在其中阅读更多内容?
\ $ \ endgroup \ $
–summersign
2015年7月9日在13:36

\ $ \ begingroup \ $
在许多WWDC视频中都提到过要恢复println的性能,而不要写日志,但是我在旅途中却没有确切的参考。
\ $ \ endgroup \ $
–抢夺
2015年7月9日在13:56

\ $ \ begingroup \ $
这是一个很好的答案。作为iOS开发的新手,这确实帮助我了解了Singletons以及如何有效使用调试器。
\ $ \ endgroup \ $
– Damianesteban
15年7月18日在17:09

#3 楼

其他答案已经对代码的其他方面进行了评论;我只是想离开我在Swift中实现单例的首选方法:

public let FGSingleton = FGSingletonClass()

public class FGSingletonClass {
    private init() {}
    // Etc...
}


要使用单例: />
您可以看到,不需要一直编写sharedInstance,并且仍然可以免费获得线程安全和惰性实例化。唯一的问题是您必须稍微更改类的名称,因为变量和类的名称必须不同(我个人在末尾添加了Class)。

#4 楼

其他大多数答案都以非常详细和准确的方式涵盖了所有内容,但我想强调一些我喜欢讲的有关单例课程的实践。

我曾经创建一个单例并那么该类中的所有公共方法都是静态的。这些静态方法称为the,然后使用private sharedInstance调用副本实例方法。

我的所有方法调用都将仅使用共享实例,因此为什么要一遍又一遍地指定它。
class FGSingleton {
private static let sharedInstance = FGSingleton()
private var gameScore: Int = 0

// METHODS
private init() {
    println(__FUNCTION__)
}
class func displayGameScore() {
    println("\(__FUNCTION__) \(self.sharedInstance.gameScore)")
}
class func incrementGameScore(scoreInc: Int) {
    self.sharedInstance.gameScore += scoreInc
}
}


调用将很简单

  FGSingleton.incrementGameScore(5)
  FGSingleton.displayGameScore()


UPDATE
如果需要初始化一些变量单例中的成员变量。您可以执行以下操作:

    public static let sharedInstance: FGSingleton = {
    let objBirthDate: NSDate = NSDate()
    return FGSingleton(date: objBirthDate)
}()

required private init(data: NSDate){...}


评论


\ $ \ begingroup \ $
如果这是一种适当的方法,那么Apple为什么不采用我们每天使用的大量单例呢?
\ $ \ endgroup \ $
– nhgrif
15年7月10日在13:05