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

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

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

class ProfileViewModel {

  let networking: Networking<APIEndpoint>

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

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