How to deal with singleton in iOS

Issue #18

A single singleton

There are many classes that designed to be used as singleton, like UserDefaults.standard, FileManager.default, NotificationCenter.default or even our own classes like UserManager, Storage, … Singleton is a design patter and has its own use case, sometimes we still need to use it. But if we are to use singleton, we should just use 1, and group all other singleton under this single singleton. Thanks to Vadym for showing this to me

Swift makes it extremely easy to make singleton, let name it App then we have a single point of control for all the singletons

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct App {
static let model = AppModel()
static let realmProvider = RealmProvider()
static let networkingProvider = NetworkingProvider()
static var navigator = Navigator()
static let config = AppConfig()
static let pushNotificationCenter = PushNotificationCenter()
static let lifeCycle = LifeCycle()
}
```

These are use cases where a single instance is needed

### AppModel
This is where we store model for an app, that can be
- is onboarding shown
- organization name
- `Session` that encapsulates token, current profile

### LifeCycle
This is where we listen to app life cycle, I use `rx` to make it easy, see https://github.com/onmyway133/blog/issues/12

### RealmProvider
I prefer `Realm` for storing and caching, usually 1 `Realm` is enough. This is where we return the a certain `Realm` instance

```swift
class RealmProvider {
static func realm() -> Realm {
let configuration = Realm.Configuration(schemaVersion: App.config.schemaVersion)
return try! Realm(configuration: configuration)
}
}

AppConfig

This is where we have configurations for staging and production environment, those can be client key, Firebase configuration, analytics keys, …

I use Compass to do central navigation, and there should be 1 Navigator that does the job

Inject a singleton

Sometime we rely on a singleton to do our job, to make dependencies clear and testing easier, we need to inject this singleton, and leverage Swift default parameter, thanks to John for showing this to me

Here is an example of a ViewModel that relies on networking

1
2
3
4
5
6
7
8
9
10
11
12
13
class ProfileViewModel {

let networking: Networking<APIEndpoint>

init(networking: Networking<APIEndpoint> = App.networking) {
self.networking = networking

networking.rxRequest(APIEndpoint.profile)
.bindNext({ profile in
print(profile)
})
}
}

Comments