Dispatch_once in Swift: Why It's a Mistake and How to Write a Proper Singleton
\n\nIf you've ever wondered "How to make a singleton in Swift using dispatch_once?" — you're not alone. On Stack Overflow, this question has gathered thousands of views, and in the code of many projects, strange constructs still appear that try to emulate the old Objective-C approach. But the good news is that Swift has a much more elegant and safe solution. The bad news: many developers continue to make the same mistakes, trying to transfer habits from Objective-C to Swift.
\n\nIn this article, we'll break down the top 5 mistakes when using dispatch_once in Swift, show how to avoid them, and provide a ready-made template for a proper singleton. Let's go!
\n\nWhat is dispatch_once and why is it not needed in Swift?
\n\ndispatch_once is a function from Grand Central Dispatch (GCD) that guarantees a block of code will execute exactly once during the entire application's runtime. In Objective-C, this was the standard way to create thread-safe singletons:
\n\n+ (instancetype)sharedInstance {\n static MyClass *sharedInstance = nil;\n static dispatch_once_t onceToken;\n dispatch_once(&onceToken, ^{\n sharedInstance = [[MyClass alloc] init];\n });\n return sharedInstance;\n}\n\nHowever, in Swift, starting from version 1.0, dispatch_once is not supported for use in user code. Yes, you can import Dispatch and try to call it, but the compiler will not allow it. Why? Because Swift offers built-in features that make dispatch_once unnecessary.
Top 5 Mistakes When Using dispatch_once in Swift
\n\nMistake 1: Trying to call dispatch_once directly
\n\nThe most common mistake is trying to write something like:
\n\nclass MySingleton {\n static var shared: MySingleton = {\n let instance = MySingleton()\n // configuration\n return instance\n }()\n \n private init() {}\n}\n\nAt first glance, this works. But this is not dispatch_once. It's just a global variable that is initialized lazily on first access. Swift guarantees thread safety for such static properties, but it's not the same as dispatch_once.
\n\nWhy is this bad? Because you lose control over the initialization moment. If your singleton depends on external resources (e.g., a database or network), they might not be ready yet.
\n\nMistake 2: Using dispatch_once through an Objective-C wrapper
\n\nSome developers create a bridge to Objective-C to call dispatch_once from Swift:
\n\n// Objective-C file\n@interface OnceHelper : NSObject\n+ (void)performOnce:(void(^)(void))block;\n@end\n\n@implementation OnceHelper\nstatic dispatch_once_t onceToken;\n+ (void)performOnce:(void(^)(void))block {\n dispatch_once(&onceToken, block);\n}\n@end\n\nThen in Swift:
\n\nclass MySingleton {\n static var shared: MySingleton = {\n var instance: MySingleton!\n OnceHelper.performOnce {\n instance = MySingleton()\n }\n return instance\n }()\n}\n\nWhat's the problem? First, it's a crutch. Second, dispatch_once_t is a global variable, and if you forget to reset the token (e.g., during testing), the singleton will not be recreated. Third, Apple explicitly recommends not using dispatch_once in Swift.
\n\nMistake 3: Forgetting about thread safety
\n\nSome write a singleton like this:
\n\nclass MySingleton {\n static var shared: MySingleton?\n \n private init() {}\n \n static func getInstance() -> MySingleton {\n if shared == nil {\n shared = MySingleton()\n }\n return shared!\n }\n}<