Singletons are a very important type of design pattern and are very common in iOS. I was asked about singletons in the interview process before, but I didn’t think I did a good job, so I studied them more. This article focuses on a simple analysis of a singleton and discusses the implementation of a singleton in Swift.

Introduction to Singleton

What is a singleton?

Singleton patterns are one of the simplest design patterns. This type of design pattern is a creation pattern that provides an optimal way to create objects. This pattern involves a single class that is responsible for creating its own objects, while ensuring that only a single object is created.

Basic requirements:

  • There can be only one instance.
  • You must create your own unique instance.
  • This instance must be provided for all other objects.

Singletons in iOS

  • UIApplication.shard: Each application has one and only one UIApplication instance, which is created as a singleton object by the UIApplicationMain function when the application starts.
  • NotificationCenter.defualt: Manages notifications in iOS
  • FileManager.defualt: Obtains the path of the sandbox home directory
  • URLSession.shared: Manages network connections
  • UserDefaults.standard: Stores lightweight local data
  • SKPaymentQueue.default(): Manages the in-app purchase queue. System will useStoreKitFramework creates a payment queue, each time used by a class methoddefault()To get this queue.

Advantages of singletons

  • Provides controlled access to a unique instance: a singleton class encapsulates its unique instance, prevents other objects from instantiating it, and ensures that all objects have access to an instance.
  • Saving system resources: Since there is only one object in the system memory, it can save system resources. For some objects that need to be created and destroyed frequently, singleton mode can definitely improve system performance.
  • Scalability: The singleton class itself controls the instantiation process, so the class has the corresponding scalability to change the instantiation process.
  • Avoid multiple resource usage: For example, do not write to the same resource file at the same time because only one instance exists in memory

Implementation of Singleton in Swift

The first way:

It’s also the most straightforward and concise way: define the instance as a global variable. For example, the following code declares an instance variable sharedManager.

let sharedManager = MyManager(string: someString)
class MyManager {
    // Properties
    let string: String
    // Initialization
    init(string: String) {
        self.string = string
    }
}Copy the code

If the instance variable is called for the first time in the global namespace, the global variable is lazy initialize in Swift. So, in the application (_ : didFinishLaunchingWithOptions:) after the call, shardManager will be initialized in the AppDelegate class, The sharedManager instance will then be used wherever it is called in the program.

In addition, the Swift global variable is initialized using dispatch_once by default, which ensures that the initializer of the global variable is invoked only once and ensures the atomicity of shardManager.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Initialize the location, and how to use it
    print(sharedManager)
    return true
}Copy the code
  • About lazy loading of global variables in Swift

Initialize lazily, run the initializer for a global the first time it is referenced, similar to Java. It allows custom initializers, startup time in Swift scales cleanly with no global initializers to slow it down, Look for the lazy loading of global variables in a similar way to Java. This is designed so that the constructor can be customized, startup time is not slowed by loading global variables, and the sequence of operations is controlled.

  • On dispatch_once and atomicity in Swift

The lazy initializer for a global variable … is launched as dispatch_once to make sure that the initialization is atomic. This enables a cool way to use dispatch_once in your code: just declare a global variable with an initializer and mark it private. Lazy loads of global variables are initialized using dispatch_once to ensure atomicity of the initialization. So this is a cool way to use dispatch_once: just mark the constructor of a global variable private when you define it.

The second way:

Swift 2 started adding the static keyword, which is used to limit the scope of variables. If static (such as let String) is not used, then each instance of MyManager has a string variable. With static, shared becomes a global variable and a singleton.

Notice also that because the constructor uses the private keyword, the atomicity of singletons is guaranteed.

class MyManager {
    // Global variables
    static let shared = MyManager(string: someString)

    // Properties
    let string: String
    // Initialization
    private init(string: String) {
        self.string = string
    }
}Copy the code

The use of the second method is as follows. As you can see, by implementing singletons in the second way, the readability of the code increases, and it’s easy to tell that this is a singleton.

/ / use
print(MyManager.shared)Copy the code

The third way:

The third approach is a more complex variant of the second. Let the singleton be initialized in a Closure, and add a class method to get the singleton.

class MyManager {
    // Global variables
    private static let sharedManager: MyManager = {
        let shared = MyManager(string: someString) 
        // You can do some other configuration
        // ...
        return shared
    }()
    // Properties
    let string: String
    // Initialization
    private init(string: String) {
        self.string = string
    }
    // Accessors
    class func shared() - >MyManager {
        return sharedManager
    }
}Copy the code

As you can see, the third approach is more complex, but you can do some extra configuration in the closure. At the same time, the singleton is called differently by calling its class method shared()

print(MyManager.shared())Copy the code

Defects of singletons

Singleton state confusion

Since singletons are shared, the programmer has no clear idea of the current state of the singletons when they are used.

When a user logs in, an instance is responsible for the actions of the current user. However, due to sharing, it is possible that the current user’s state has been changed by another instance, and the original instance remains unaware of the change. To solve this problem, the instance must monitor the state of the singleton. Notifications are one way to do this, but it can make the program too complex and generate a lot of unnecessary Notifications.

The test difficulty

Testing difficulties are mainly caused by the confusion of singleton states. Because the state of a singleton can be modified by other shared instances, it is difficult to start each test case with a clean, clean state when performing tests that rely on singletons

The chaos of singleton access

Because singletons are global, access cannot be restricted. Singletons can be accessed from any location and any instance of the program, which can easily cause administrative confusion.

The resources

  • What is a Singleton and How to create one in Swift
  • Files and Initialization – Swift Blog – Apple Developper
  • Avoiding singletons in Swift – John Sundell – Medium