Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save caiozullo/547ed5483221e6cb9509e4134cb1ce09 to your computer and use it in GitHub Desktop.
Save caiozullo/547ed5483221e6cb9509e4134cb1ce09 to your computer and use it in GitHub Desktop.
Careful With “Singleton” Lookalikes episode
//
// Copyright © 2018 Essential Developer. All rights reserved.
//
import UIKit
/*
The Singleton design pattern is intended to enforce
a class has only one instance.
However, it is often used only as a convenience design pattern,
since the instance can be easily located
(usually Singleton.instance or Singleton.shared)
Many consider "Singleton as a convenience" an anti-pattern
because it can introduce (bad) global mutable state,
can increase coupling (immobility+viscosity),
make dependencies implicit, make code harder to test, and more.
In the episode "Careful With “Singleton” Lookalikes"
we explain the problems and propose some solutions.
Learn more at https://www.essentialdeveloper.com/articles/careful-with-singleton-lookalikes-way-too-common
Below is one of the modular solutions proposed in the video where one wouldn't need Singletons in the API layer.
*/
// Main Module
// Modules Composition (dependency injection) +
// Adapter Implementations (We can also create a separate adapter module if necessary)
extension ApiClient: LoginApi {
func login(completion: (LoggedInUser) -> Void) {}
}
extension ApiClient {
func loadFeed(completion: ([FeedItem]) -> Void) {}
}
// Api Module
// Generic/Reusable module (can be URLSession/AFNetworking/etc.)
// In this modular approach, the ApiClient doesn't need to be a Singleton.
// But if one decides to use a Singleton, it's downsides are mostly
// contained and won't affect the features.
class ApiClient {
static let shared = ApiClient()
func execute(_ : URLRequest, completion: (Data) -> Void) {}
}
// Login Module (Protocol example)
// A feature module agnostic of API clients / HTTP requests.
// It exposes a protocol which must be implemented by a another module
struct LoggedInUser {}
protocol LoginApi {
func login(completion: @escaping (LoggedInUser) -> Void)
}
class LoginViewController: UIViewController {
var api: LoginApi?
func didTapLoginButton() {
api?.login { user in
// do something
}
}
}
// Feed Module (Closure example)
// Another feature module agnostic of API clients / HTTP requests.
// It requires a closure which must be passed in by a another module.
struct FeedItem {}
class FeedService {
let loadFeed: (([FeedItem]) -> Void) -> Void
init(loadFeed: @escaping (([FeedItem]) -> Void) -> Void) {
self.loadFeed = loadFeed
}
func load() {
loadFeed { loadedItems in
// do something
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment