Singleton in Swift

Swift inheritance of singleton pattern?

Ended up adding an interface:

protocol Instance {
associatedtype T
// static var _instance: T {get set} # Can't use; can't override :(
static func instance() -> T
}

Usable like so:

class ApiBase: Instance {
var root: String = ""

static var _instance: ApiBase = ApiBase()

open class func instance() -> ApiBase {
if _instance.root == "" {
_instance = ApiBase()
}
return _instance
}
}

Inheritance:

class FooApi: ApiBase {
static var _fooApi: FooApi = FooApi()

override class func instance() -> FooApi {
if _fooApi.root == "" {
_fooApi = FooApi()
}
return _instance
}
}

This is suboptimal, as the body's of the instance functions are identical in pattern. But it works.

How to use the singleton pattern in conjunction with dependency injection?

  1. Could you show me how to properly use dependency injection with the singleton pattern in Swift?

    Rather than accessing ServiceSingleton.shared directly, you access an instance variable that is injected into your object, usually in the initializer if possible, otherwise as a settable property, post-initialization:

    protocol FooService {
    func doFooStuff()
    }

    class ProductionFooService: FooService {

    private init() {}

    static let shared = ProductionFooService()

    func doFooStuff() {
    print("real URLSession code goes here")
    }

    }

    struct MockFooService: FooService {
    func doFooStuff() {
    print("Doing fake foo stuff!")
    }
    }

    class FooUser {
    let fooService: FooService

    init(fooService: FooService) { // "initializer based" injection
    self.fooService = fooService
    }

    func useFoo() {
    fooService.doFooStuff() // Doesn't directly call ProductionFooService.shared.doFooStuff
    }
    }

    let isRunningInAUnitTest = false

    let fooUser: FooUser
    if !isRunningInAUnitTest {
    fooUser = FooUser(fooService: ProductionFooService.shared) // In a release build, this is used.
    }
    else {
    fooUser = FooUser(fooService: MockFooService()) // In a unit test, this is used.
    }

    fooUser.useFoo()

    Typically initialization of ViewControllers is done by your storyboards, so you can't ingect your dependancies via initializer parameters, and will have to instead use stored properties that are set after object initialization.

  2. Could you explain to me what this achieves?

    Your code is no longer coupled to ProductionFooService.shared. As a result of this, you can introduce different implementations of FooService, such as one for a beta environment, a mock one for unit testing, etc.

    If all your code pervasively directly uses your prod dependancies, you'll...

    1. find that it's impossible to instantiate your objects in a test environment. You don't want your unit tests, CI test environments, beta environments, etc. connecting to prod databases, services and APIs.

    2. Have no true "unit" tests. Every test will be testing a unit of code, plus all of the common dependancies that it transitively depends on. If you were to ever make a code change to one of these dependancies, it would break most of the unit tests in your system, which makes it harder to pin down exactly what failed. By decoupling your dependancies, you can use mock objects that do the bare minimum necessary to support a unit test, and ensure that each test is only testing a particular unit of code, and not the transitive dependancies it relies on.

  3. Should I always use DI when I use the singleton pattern in my iOS projects from now on?

    It's a good habit to pick up. Of course, there are qucik-and-dirty-projects for which you just want to move fast and won't really care, but it'll surprise you how many of these supposed qucik-and-dirty-projects actually take off, and pay the cost down the road. You just need to be cognizant of when you're hindering yourself by not taking some extra time to decouple your decencies.

Using Arrays in Singleton Class in iOS

Array in swift is implemented as Struct, which means Array is a value type and not a reference type. Value types in Swift uses copy on write (COW) mechanism to handle the changes to their values.

So in your ViewController when you assigned

var PetArray = PetInfo.shared.petArray

your PetArray was still pointing to the same array in your PetInfo.shared instance (I mean same copy of array in memory) . But as soon as you modified the value of PetArray by using

PetArray.append(pet)

COW kicks in and it creates a new copy of petArray in memory and now your PetArray variable in your ViewController and PetInfo.shared.petArray are no longer pointing to same instances instead they are pointing to two different copies of array in memory.

So all the changes you did by using PetArray.append(pet) is obviously not reflected when you access PetInfo.shared.petArray in secondViewController

What can I do?

remove PetArray.append(pet) and instead use PetInfo.shared.petArray.append(pet)

What are the other issues in my code?

Issue 1:

Never use Pascal casing for variable name var PetArray = PetInfo.shared.petArray instead use camel casing var petArray = PetInfo.shared.petArray

Issue 2:

class PetInfo {

static let shared: PetInfo = PetInfo()

lazy var petArray: [PetInfo] = []
var PetID:Int
var PetName:String
...

init(){ .. }
}

This implementation will not ensure that there exists only one instance of PetInfo exists in memory (I mean it cant ensure pure singleton pattern), though you provide access to instance of PetInfo using a static variable named shared there is nothing which stops user from creating multiple instances of PetInfo simply by calling PetInfo() as you did in let pet = PetInfo()

rather use private init(){ .. } to prevent others from further creating instances of PetInfo

Issue 3:

You are holding an array of PetInfo inside an instance of PetInfo which is kind of messed up pattern, am not really sure as to what are you trying to accomplish here, if this is really what you wanna do, then probably you can ignore point two (creating private init) for now :)

Singleton in Swift

The standard singleton pattern is:

final class Manager {
static let shared = Manager()

private init() { ... }

func foo() { ... }
}

And you'd use it like so:

Manager.shared.foo()

Credit to appzYourLife for pointing out that one should declare it final to make sure it's not accidentally subclassed as well as the use of the private access modifier for the initializer, to ensure you don't accidentally instantiate another instance. See https://stackoverflow.com/a/38793747/1271826.

So, returning to your image cache question, you would use this singleton pattern:

final class ImageCache {

static let shared = ImageCache()

/// Private image cache.

private var cache = [String: UIImage]()

// Note, this is `private` to avoid subclassing this; singletons shouldn't be subclassed.

private init() { }

/// Subscript operator to retrieve and update cache

subscript(key: String) -> UIImage? {
get {
return cache[key]
}

set (newValue) {
cache[key] = newValue
}
}
}

Then you can:

ImageCache.shared["photo1"] = image
let image2 = ImageCache.shared["photo2"])

Or

let cache = ImageCache.shared
cache["photo1"] = image
let image2 = cache["photo2"]

Having shown a simplistic singleton cache implementation above, we should note that you probably want to (a) make it thread safe by using NSCache; and (b) respond to memory pressure. So, the actual implementation is something like the following in Swift 3:

final class ImageCache: NSCache<AnyObject, UIImage> {

static let shared = ImageCache()

/// Observer for `UIApplicationDidReceiveMemoryWarningNotification`.

private var memoryWarningObserver: NSObjectProtocol!

/// Note, this is `private` to avoid subclassing this; singletons shouldn't be subclassed.
///
/// Add observer to purge cache upon memory pressure.

private override init() {
super.init()

memoryWarningObserver = NotificationCenter.default.addObserver(forName: .UIApplicationDidReceiveMemoryWarning, object: nil, queue: nil) { [weak self] notification in
self?.removeAllObjects()
}
}

/// The singleton will never be deallocated, but as a matter of defensive programming (in case this is
/// later refactored to not be a singleton), let's remove the observer if deallocated.

deinit {
NotificationCenter.default.removeObserver(memoryWarningObserver)
}

/// Subscript operation to retrieve and update

subscript(key: String) -> UIImage? {
get {
return object(forKey: key as AnyObject)
}

set (newValue) {
if let object = newValue {
setObject(object, forKey: key as AnyObject)
} else {
removeObject(forKey: key as AnyObject)
}
}
}

}

And you'd use it as follows:

ImageCache.shared["foo"] = image

And

let image = ImageCache.shared["foo"]

For Swift 2.3 example, see previous revision of this answer.

Where Singleton object is allocated?

  • Swift allocates storage for MyClass.shared in the data segment, initialized to nil. The data segment's layout and initial contents are defined by the executable file. Historically, the heap started immediately at the end of the data segment, but on modern 64-bit systems with address space layout randomization (ASLR), I don't know if that's still true.

  • Swift also allocates a swift_once_t in the data segment to record whether MyClass.shared has been initialized yet.

  • Swift generates a getter function for MyClass.shared in the code segment. The getter function uses the swift_once_t to initialize the storage of MyClass.shared the first time the getter is called. It looks approximately like this:

    var _storage_MyClass_shared: MyClass? = nil
    var _once_MyClass_shared: swift_once_t = .init() // essentially, false

    func _getter_MyClass_shared() -> MyClass {
    swift_once(&_once_MyClass_shared, {
    _storage_MyClass_shared = MyClass()
    })
    return _storage_MyClass_shared!
    }
  • The instance of MyClass is stored on the heap. It starts with a word containing the isa pointer (to the MyClass metadata), followed by a word containing (usually) reference counts, followed by storage for the object's instance variables. In your case, there are no instance variables, so there is no additional storage. The blue box labeled Myclass() in your diagram, and the arrow pointing to it, do not exist.

  • If myClass is at top-level (not inside a method or data type declaration), then it is also stored in the data segment along with another swift_once_t that tracks whether it's been initialized, and Swift generates a getter for it in the code segment.

  • If myClass is an instance variable of a data type, then it is stored as part of its containing object, which may be either on the stack or the heap (in the case of a struct, enum, or tuple) or always on the heap (in the case of a class or actor).

  • If myClass is a local variable in a function, then it is stored on the stack.

Swift - init in singleton class?

The init gets called only the first time you invoke MyClass.shared

At that point the instance of MyClass is saved inside the shared static constant.

Example

Let's consider this Singleton class

final class Singleton {
static let shared = Singleton()
private init() {
print("Singleton initialized")
}

var count = 0
}

Now let's look at the output into the console

Sample Image

As you can see the Singleton initialized string is printed only once. This means the init is only called once.

Note: Of course I assumed the implementation of your Singleton class is correct.

Why not use a struct-based singleton in Swift

You can't use a struct fora singleton because struct is a value type so when you assign it to a variable you get a copy. This can be easily shown

struct Singleton {
static var shared = Singleton()
var value: Int

private init() {
value = 0
}
}

Singleton.shared.value = 1
var otherSingleton = Singleton.shared
otherSingleton.value = 2

Now if we print the value of both

print(Singleton.shared.value, otherSingleton.value)

we get

1 2

So otherSingleton is clearly a separate instance so now we have 2 singletons :)

But if we simply change the type of Singleton to class that is a reference type and then run the same code the result of the print is

2 2

since it is the same instance we have changed the value property for.

Swift singleton vs. static properties/methods

To me, a simpler alternative would be to convert all properties and methods to static, and drop the sharedInstance property.

These do not do the same thing. The recommended approach is not actually a singleton at all. It's just a well-known instance. The concept of the Singleton pattern is that there must only be one instance. The concert of the shared instance pattern is that there can be more than one instance, but there is one that you probably want, and you would like easy access to it.

The advantage of shared instances is that they are not magical. They're just instances. That means that they can be handed around as values. They can be replaced with other instances that may be configured differently. They are easier to test (because they can be passed into functions).

True singletons are a very rigid pattern that should only be used when it is absolutely necessary that no other instance exist, usually because they interact with some external unique resource in a way that would create conflicts if there were multiples (this is pretty rare). Even in this case, in Swift, you should generally just make init private to prevent additional instances being created.

If you look around Cocoa, you'll find that shared instances are extremely common for things that would be Singletons in other frameworks, and this has been very powerful. For instance, there is a well known NotificationCenter called default, and it's probably the only one you've ever used. But it's completely valid to create a private NotificationCenter that's independent (I've actually done this in production code).

The fact that UIDevice.current is how you access the device, rather than static methods, leaves open the possibility of new APIs that can handle multiple devices (it also helps with unit testing). In the earliest versions of iOS, the only UIScreen was .main, and it might have made sense to make it a singleton. But because Apple didn't, when mirroring was added in 4.3, it was simple to talk about the second screen (UIScreen.mirrored). You should generally be very slow to assume that there can only be one of something.



Related Topics



Leave a reply



Submit