I am pretty new to Swift, as a solo developer I was wondering if somebody could pass comments on the singleton implementation below. The code does work, but being new to Swift and knowing that there are a lot of gotchas, I wanted to get some feedback from someone more experienced.
// 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
}
}
Tests:
FGSingleton.sharedInstance().displayGameScore()
FGSingleton.sharedInstance().incrementGameScore(100)
FGSingleton.sharedInstance().incrementGameScore(1)
FGSingleton.sharedInstance().displayGameScore()
FGSingleton.sharedInstance().incrementGameScore(1000)
FGSingleton.sharedInstance().displayGameScore()
Output:
init() displayGameScore() 0 displayGameScore() 101 displayGameScore() 1101
4 Answers 4
You could simplify the above significantly:
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
}
}
And call it like this:
FGSingleton.sharedInstance.displayGameScore()
-
\$\begingroup\$ Works fine with Swift 1.2, just a quick note the
sharedInstance
should be declared asstatic let sharedInstance = FGSingleton()
\$\endgroup\$fuzzygoat– fuzzygoat2015年02月12日 11:22:50 +00:00Commented Feb 12, 2015 at 11:22 -
\$\begingroup\$ You are right. I have removed the class keyword. \$\endgroup\$Rhuantavan– Rhuantavan2015年02月12日 12:28:19 +00:00Commented Feb 12, 2015 at 12:28
-
2\$\begingroup\$ Note, not only is this simpler, but the implementation in the original question is not thread-safe, whereas this is (because statics are instantiated lazily with
dispatch_once
). \$\endgroup\$Rob– Rob2015年02月19日 19:11:44 +00:00Commented Feb 19, 2015 at 19:11 -
1\$\begingroup\$ Shouldn't the initializer be private to make sure only one instance of FGSingleton can be created? \$\endgroup\$Apfelsaft– Apfelsaft2015年04月29日 08:32:41 +00:00Commented Apr 29, 2015 at 8:32
-
\$\begingroup\$ A singleton defined with this pattern that subclasses NSObject is actually deadlocking for me in Swift 2.0. However, the only way I currently have to reproduce is to add a breakpoint that logs data from the shared instance on the same line where the shared instance is first referenced in code. \$\endgroup\$Michael Johnston– Michael Johnston2015年10月02日 01:49:31 +00:00Commented Oct 2, 2015 at 1:49
First and foremost, printing text to the console is absolutely pointless for an iOS application. It's okay to do it as an easy way to test whether our code is working as intended, but we don't want to mistakenly leave this in the final release build.
So, step 1: Follow the instructions in this StackOverflow post to set up a custom compiler flag for your Swift project.
Step 2: Wrap all of your println
statements in #if DEBUG
and #endif
.
Example:
init() {
#if DEBUG
println(__FUNCTION__)
#endif
}
Now our debug builds can print useful debug statements, and we won't accidentally forget to remove them when it comes time to compile our final release build. Perhaps more importantly, we won't have to waste time putting them all back in after we make the final release build and start adding features or fixing bugs.
I highly recommend either making a function to do this debug printing:
func debugPrintln(s: String) {
#if DEBUG
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "HH:mm:ss.zzz"
let timeStamp = dateFormatter.stringFromDate(NSDate())
println("[" + timeStamp + "]: " + s)
#endif
}
Or just make an Xcode code snippet out of the following:
#if DEBUG
println(<#string#>)
#endif
And set the autocomplete to "println"
enter image description here
By doing this, any time you type "println", Xcode will try to autocomplete into the above snippet, and "string" within the parenthesis will be selected and ready to be overwritten by your typing. I have a similar one for Objective-C if you're interested:
enter image description here
Now then, let's review the stuff you've more directly asked questions about...
First, we don't actually need a class
method to get the shared instance. I would go with Rhuantavan's static let
suggestion.
But, let's pay attention to our access modifiers.
Our gameScore
instance variable and our incrementGameScore
instance method have the same access level, so what's the point?
Why would I write this:
FGSingleton.sharedInstance.incrementGameScore(3)
When I could just write this:
FGSingleton.sharedInstance.gameScore += 3
Moreover,
FGSingleton.sharedInstance.incrementGameScore(-3)
seems a little awkward versus:
FGSingleton.sharedInstance.gameScore -= 3
but it gets even worse if we compare setting the score to a specific number.
let targetScore = 5
let currentScore = FGSingleton.sharedInstance.gameScore
FGSingleton.sharedInstance.incrementGameScore((-1 * currentScore) + targetScore)
versus simply:
FGSingleton.sharedInstance.gameScore = targetScore
I'm not recommending what's best. It's certainly very possible that you don't want the user modifying the game score directly. But if that's the case, make the gameScore
property a private
variable, and implement more methods plus more safety checking on what's actually happening in these methods. Or if you don't mind them modifying the game score directly, just eliminate the unnecessary incrementGameScore
method entirely.
Generally speaking, in the few instances when we actually want a singleton pattern, the class doesn't work out that great when people instantiate other instances of the object. The class is designed with the assumption that it will always be used as a singleton. Too often though, developers make the mistake of not putting in any safeguards to prevent the object from being instantiated.
Let's not make that same mistake.
In Swift, it's as simple as marking the init
method as private
:
private init() {}
Now if we try instantiating an object of our class in any other Swift file, we'll get a nasty error:
enter image description here
(I named my class SwiftSingleton
)
This is what we want. If it's a singleton, we probably don't want people to have other instances available... so don't assume they won't try. It's easy enough to completely prevent it!
As some final parting food for thought, we really need to delve far more deeply into why we're making a singleton class at all. In all of my professional experience as an iOS developer, I've never needed to use a singleton, that I can recall.
When I was a less experienced programmer, I felt that a singleton was the answer to my problems of trying to pass data between various objects (or more likely, between view controllers). But it's not the right solution.
Do you actually need this singleton? Would it not be better to just have some sort of GameScore
class that gets passed around among the objects that need to modify or read the data that the GameScore
class holds?
In my opinion, most use cases of singletons are the lazy approach to actually learning how to pass data between your objects appropriately.
-
1\$\begingroup\$ "...and we won't accidentally forget to remove them when it comes time to compile our final release build." Because
println
, unlikeNSLog
, doesn't write to the system log, this concern seems a lot less critical. Don't get me wrong: I'd still take it out because it's silly to include in production code, but it's not nearly the same problem thatNSLog
was. \$\endgroup\$Rob– Rob2015年02月19日 19:16:42 +00:00Commented Feb 19, 2015 at 19:16 -
\$\begingroup\$ @Rob, good information. can you share some links where I can read more about this please? \$\endgroup\$geekay– geekay2015年07月09日 13:36:46 +00:00Commented Jul 9, 2015 at 13:36
-
\$\begingroup\$ Re println performance and not writing to log, it's been mentioned in a number of WWDC videos, but I'm traveling and don't have the precise reference in front of me. \$\endgroup\$Rob– Rob2015年07月09日 13:56:09 +00:00Commented Jul 9, 2015 at 13:56
-
\$\begingroup\$ This is an excellent answer. As a newcomer to iOS development this really helped me learn about Singletons and how to effectively use the debugger. \$\endgroup\$damianesteban– damianesteban2015年07月18日 17:09:13 +00:00Commented Jul 18, 2015 at 17:09
The other answers have already commented on other aspects of the code; I'd simply like to leave here my preferred way of implementing singletons in Swift:
public let FGSingleton = FGSingletonClass()
public class FGSingletonClass {
private init() {}
// Etc...
}
To use the singleton:
let v = FGSingleton.myProperty
As you can see, there's no need for writing sharedInstance
all the time, and you still get thread-safety and lazy instantiation for free. The only gotcha is that you have to slightly change the name of the class, because the variable and the class must have different names (I personally add Class
to the end).
Most of the other answers cover everything in a very detailed and accurate manner but I want to highlight some of the practices that I liked to preach when it comes to singleton classes.
I used to create a singleton and then all the public methods in the class were static. These static methods called the then call the replica instance methods using the private sharedInstance
As its a singleton all my method calls are going to be using shared instance ONLY so why specify it again and again.
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
}
}
and call will be simply
FGSingleton.incrementGameScore(5)
FGSingleton.displayGameScore()
UPDATE Just for a variation if you need to initialize some member variables in your singleton. You can do something like :
public static let sharedInstance: FGSingleton = {
let objBirthDate: NSDate = NSDate()
return FGSingleton(date: objBirthDate)
}()
required private init(data: NSDate){...}
-
1\$\begingroup\$ If this were an appropriate approach, why doesn't Apple do it with their plethora of singletons that we use everyday? \$\endgroup\$nhgrif– nhgrif2015年07月10日 13:05:05 +00:00Commented Jul 10, 2015 at 13:05