singletons


import UIKit

//: ## What is a singleton

class MySingletonClass {
    static let singleton = MySingletonClass() //1.2 syntax
    
    struct ArbitraryStruct { //1.1 syntax
        static let singleton = MySingletonClass()
    }
}

MySingletonClass.singleton

//: ## Singleton as global variables
//: It turns out that singletons are pretty much like global variables:
//: 
//: 1. Accessible from everywhere
//: 2. State may "come from" anywhere and be used by anywhere
//: 3.  Is there really any difference between

MySingletonClass.singleton

// and

let GlobalVariable = MySingletonClass()

//: ## Singletons as real life
//:
//: In the real world, we have singletons.
//:
//: 1.  You only have one iPhone
//: 2.  There's only one internet
//: 3.  There's only one GPS
//: 4.  There's only one display
//:
//: Practical singletonology is about figuring out how to handle the **border condition** between "singleton world" and "instance world".  
//: Even if *you* never create a singleton, they still exist, because your app runs in the real world and we only have one display.

//: ## Or do we?
//:
//: 1.  We now have external displays in iOS.  You're reading this on one.
//: 2.  Apple watch is internally an external display for iOS
//: 3.  You may (read: **should**) have unit tests that create UIViews that don't exist on any display
//:
//: Beware the singletons that aren't quite.  I call them "snigletons".



//: ### Snigleton #1: unit test knock-off

// We have one instance for the app to use

MySingletonClass.singleton

//and another one for the unit tests

import XCTest
class MyTest : XCTestCase {
    func testFoo() {
        let secondInstance = MySingletonClass() //now we have 2 singletons!
    }
}

//: The following is real code in production

class MySingleton1 {
    static let singleton = MySingleton1()
    init(for_unit_testing_only_i_know_what_im_doing : Bool = false) {
        if for_unit_testing_only_i_know_what_im_doing {
            //snip
        }
        else {
            //snip
        }
    }
}

import XCTest
class MyTest2 : XCTestCase {
    func testFoo() {
        let secondInstance = MySingleton1(for_unit_testing_only_i_know_what_im_doing: true)//now we have 2 singletons!
    }
}

//: ### Snigleton #2: The disappearing instance
//: The insight here is that singletons can accumulate state, and sometimes that state needs to get blown away.
//:
//: Examples:
//:
//: 1.  Caching
//: 2.  Unit test isolation
//: 3.  Document/user isolation


class Snigleton2 {
    static private var _singleton : Snigleton2? = nil
    static var singleton : Snigleton2 {
        get {
            if _singleton == nil {
                _singleton = Snigleton2()
            }
            return _singleton!
        }
    }
    
    static func die() {
        _singleton = nil
    }
}

//: ### Snigleton #3: The Doubleton

class Snigleton3 {
    let firstInstance = Snigleton3()
    let secondInstance = Snigleton3()
}

//: Examples
//: 
//: 1.  You're writing pong, which has exactly 2 players
//: 2.  You're doing audio, and users have exactly 2 ears
//: 3.  You're talking to the database, either from the foreground or the background

//: ### Snigleton #4: The proxy

class Snigleton4 {
    private static let instances : [Snigleton4] = [Snigleton4(), Snigleton4(), Snigleton4(), Snigleton4()]
    
    func foo() { }
    
    static func foo() {
        let randomIndex = Int(arc4random_uniform(UInt32(instances.count)))
        instances[randomIndex].foo()
    }
}

Snigleton4.foo()
//: Examples
//:
//: 1.  Load balancing
//: 2.  Multithreading
//: 3.  Distributed networking

//: ## Lifecycle of a singleton
//:
//: Most singletons tend to go through 3 life stages
//: 
//: ### Singleton
//:
//: You're in a design meeting, and somebody tells you "We only support a single user".  So you write a singleton, it works, you check it in, you go home.
//:
//: ### Snigleton
//:
//: It turns out that in reality you want unit tests that involve multiple users (unit test snigleton), or that users can log out (disappearing snigleton), so your singleton becomes a snigleton.
//:
//: ### Instance
//:
//: Feature request: support multiple users
//:
//: So you throw up your hands and reimplement it.
//:
//: ## Conclusion
//: You will waste less code if you can skip some of the steps.  Ask yourself:
//: 1.  How confident are we that our application supports only 1 user? Will we change our mind later? (Skip from singleton to instance)
//: 2.  Is this really a singleton, or do one of the snigleton patterns fit better?  (Skip from singleton to snigleton)
//: 3.  Have I thought about how I want to test this?
//: 4.  Have I thought about accumulation of state and do I need to reset it?
//: 5.  Have I thought about the Apple Watch (display is not a singleton)?  Have I thought about iOS extensions?  (PID is not a singleton)?  Have I considered that Apple reliably announces stuff like this once a year?


Want me to build your app / consult for your company / speak at your event? Good news! I'm an iOS developer for hire.