Software architecture has a lot to do with making sure the dependency graph is well structured. But in practice, maintaining a clean dependency graph while injecting dependencies in a type safe and runtime safe manner is a lot of repetitive code to write. This problem often leads developers to overuse the singleton pattern, making their code less modular at best, almost untestable at worst. Weaver is a code generation tool which makes it easy to inject dependencies where they are needed while maintaining a safe and clean dependency graph at the same time.
Talk: https://www.youtube.com/watch?v=h3CMMbgozG0
Github: https://github.com/scribd/Weaver
6. final class Foo {
let bar: Bar
init(bar: Bar) {
self.bar = bar
bar.foo = self
}
func barDidSomething() { ... }
}
final class Bar {
weak var foo: Foo?
func doSomething() { foo?.barDidSomething() }
}
7. Weaver is a code generation tool which makes it easy
to inject dependencies.
https://github.com/scribd/Weaver
14. ADVANTAGESADVANTAGES
1. Mimics manual DI technics. No black box.
2. Compile time errors. Fails fast.
3. Type safety. If it compiles, it works!
4. Declarativeness.
15. ADVANTAGESADVANTAGES
1. Mimics manual DI technics. No black box.
2. Compile time errors. Fails fast.
3. Type safety. If it compiles, it works!
4. Declarativeness.
5. No optionality. If it's declared, it's there.
16. ADVANTAGESADVANTAGES
1. Mimics manual DI technics. No black box.
2. Compile time errors. Fails fast.
3. Type safety. If it compiles, it works!
4. Declarativeness.
5. No optionality. If it's declared, it's there.
6. Thread safety. Immutability. No lazy loading.
17. ADVANTAGESADVANTAGES
1. Mimics manual DI technics. No black box.
2. Compile time errors. Fails fast.
3. Type safety. If it compiles, it works!
4. Declarativeness.
5. No optionality. If it's declared, it's there.
6. Thread safety. Immutability. No lazy loading.
7. No additional framework to ship.
34. Injecting a shared instance of URLSession in
APIClient
final class APIClient {
func get(_ url: URL, completion: @escaping (Data?) -> Void) ->
}
}
35. Injecting a shared instance of URLSession in
APIClient
final class APIClient {
private let urlSession: URLSession
func get(_ url: URL, completion: @escaping (Data?) -> Void) ->
}
}
36. Injecting a shared instance of URLSession in
APIClient
final class APIClient {
private let urlSession: URLSession
init(urlSession: URLSession = AppDelegate.shared.urlSession)
self.urlSession = urlSession
}
func get(_ url: URL, completion: @escaping (Data?) -> Void) ->
}
}
37. Injecting a shared instance of URLSession in
APIClient
final class APIClient {
private let urlSession: URLSession
init(urlSession: URLSession = AppDelegate.shared.urlSession)
self.urlSession = urlSession
}
func get(_ url: URL, completion: @escaping (Data?) -> Void) ->
urlSession.dataTask(with: url) { ... }.resume()
}
}
38. Using APIClient in MovieManager
final class MovieManager {
func getMovies(_ completion: @escaping ([Movie]?) -> Void) {
}
}
39. Using APIClient in MovieManager
final class MovieManager {
func getMovies(_ completion: @escaping ([Movie]?) -> Void) {
APIClient().get("http://my_movie_api/movies") { ... }
}
}
40. Using APIClient in MovieManager
final class MovieManager {
func getMovies(_ completion: @escaping ([Movie]?) -> Void) {
APIClient().get("http://my_movie_api/movies") { ... }
// ^ No URLSession to pass in.
}
}
43. Passing down an instance of URLSession in
APIClient
final class APIClient {
private let urlSession: URLSession
init(_ urlSession: URLSession) {
self.urlSession = urlSession
}
func get(_ url: URL, completion: @escaping (Data?) -> Void) ->
urlSession.dataTask(with: url) { ... }.resume()
}
}
44. Passing down an instance of URLSession in
APIClient
final class APIClient {
private let urlSession: URLSession
init(_ urlSession: URLSession) { // <- No default anymore
self.urlSession = urlSession
}
func get(_ url: URL, completion: @escaping (Data?) -> Void) ->
urlSession.dataTask(with: url) { ... }.resume()
}
}
45. From MovieManager to APIClient
final class MovieManager {
func getMovies(_ completion: @escaping ([Movie]?) -> Void) {
}
}
46. From MovieManager to APIClient
final class MovieManager {
private let apiClient: APIClient
func getMovies(_ completion: @escaping ([Movie]?) -> Void) {
}
}
47. From MovieManager to APIClient
final class MovieManager {
private let apiClient: APIClient
init(urlSession: URLSession) {
apiClient = APIClient(urlSession)
}
func getMovies(_ completion: @escaping ([Movie]?) -> Void) {
}
}
48. From MovieManager to APIClient
final class MovieManager {
private let apiClient: APIClient
init(urlSession: URLSession) {
apiClient = APIClient(urlSession)
}
func getMovies(_ completion: @escaping ([Movie]?) -> Void) {
apiClient.get("http://my_movie_api/movies") { ... }
}
}
49. From HomeViewController to MovieManager
final class HomeViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
50. From HomeViewController to MovieManager
final class HomeViewController {
private let movieManager: MovieManager
override func viewDidLoad() {
super.viewDidLoad()
}
}
51. From HomeViewController to MovieManager
final class HomeViewController {
private let movieManager: MovieManager
init(_ urlSession: URLSession) {
movieManager = MovieManager(urlSession: urlSession)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
52. From HomeViewController to MovieManager
final class HomeViewController {
private let movieManager: MovieManager
init(_ urlSession: URLSession) {
movieManager = MovieManager(urlSession: urlSession)
}
override func viewDidLoad() {
super.viewDidLoad()
movieManager.getMovies { ... }
}
}
53. From AppDelegate to HomeViewController
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidFinishLaunching(_ application: UIApplicati
}
}
54. From AppDelegate to HomeViewController
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidFinishLaunching(_ application: UIApplicati
let configuration = ...
let urlSession = URLSession(configuration: configuration)
}
}
55. From AppDelegate to HomeViewController
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidFinishLaunching(_ application: UIApplicati
let configuration = ...
let urlSession = URLSession(configuration: configuration)
let controller = HomeViewController(urlSession)
...
}
}
70. In a real project, we'd have to pass down dozens of
dependencies
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidFinishLaunching(_ application: UIApplicati
let controller = HomeViewController(
)
}
}
71. In a real project, we'd have to pass down dozens of
dependencies
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidFinishLaunching(_ application: UIApplicati
let controller = HomeViewController(
sessionManager: sessionManager,
)
}
}
72. In a real project, we'd have to pass down dozens of
dependencies
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidFinishLaunching(_ application: UIApplicati
let controller = HomeViewController(
sessionManager: sessionManager,
analyticsManager: analyticsManager,
)
}
}
73. In a real project, we'd have to pass down dozens of
dependencies
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidFinishLaunching(_ application: UIApplicati
let controller = HomeViewController(
sessionManager: sessionManager,
analyticsManager: analyticsManager,
reachabilityManager: reachabilityManager,
)
}
}
74. In a real project, we'd have to pass down dozens of
dependencies
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidFinishLaunching(_ application: UIApplicati
let controller = HomeViewController(
sessionManager: sessionManager,
analyticsManager: analyticsManager,
reachabilityManager: reachabilityManager,
logger: logger,
)
}
}
75. In a real project, we'd have to pass down dozens of
dependencies
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidFinishLaunching(_ application: UIApplicati
let controller = HomeViewController(
sessionManager: sessionManager,
analyticsManager: analyticsManager,
reachabilityManager: reachabilityManager,
logger: logger,
...
)
}
}
76. In a real project, we'd have to pass down dozens of
dependencies
Ouch!
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidFinishLaunching(_ application: UIApplicati
let controller = HomeViewController(
sessionManager: sessionManager,
analyticsManager: analyticsManager,
reachabilityManager: reachabilityManager,
logger: logger,
...
)
}
}
79. WHAT METHODOLOGY HAVE WE USED SO FAR?WHAT METHODOLOGY HAVE WE USED SO FAR?
1. Looked at the code.
80. WHAT METHODOLOGY HAVE WE USED SO FAR?WHAT METHODOLOGY HAVE WE USED SO FAR?
1. Looked at the code.
2. Built a representation of the dependency graph.
81. WHAT METHODOLOGY HAVE WE USED SO FAR?WHAT METHODOLOGY HAVE WE USED SO FAR?
1. Looked at the code.
2. Built a representation of the dependency graph.
3. Made sure the dependency graph was ok.
82. WHAT METHODOLOGY HAVE WE USED SO FAR?WHAT METHODOLOGY HAVE WE USED SO FAR?
1. Looked at the code.
2. Built a representation of the dependency graph.
3. Made sure the dependency graph was ok.
4. Used a DI technique to implement the graph.
84. WEAVER DOES THE SAMEWEAVER DOES THE SAME BUT AUTOMATICALLYBUT AUTOMATICALLY
85. WEAVER DOES THE SAMEWEAVER DOES THE SAME BUT AUTOMATICALLYBUT AUTOMATICALLY
1. Scans the code, looking for annotations.
86. WEAVER DOES THE SAMEWEAVER DOES THE SAME BUT AUTOMATICALLYBUT AUTOMATICALLY
1. Scans the code, looking for annotations.
2. Builds a representation of the dependency graph.
87. WEAVER DOES THE SAMEWEAVER DOES THE SAME BUT AUTOMATICALLYBUT AUTOMATICALLY
1. Scans the code, looking for annotations.
2. Builds a representation of the dependency graph.
3. Validates the dependency graph.
88. WEAVER DOES THE SAMEWEAVER DOES THE SAME BUT AUTOMATICALLYBUT AUTOMATICALLY
1. Scans the code, looking for annotations.
2. Builds a representation of the dependency graph.
3. Validates the dependency graph.
4. Generates the code to implement the graph.
96. Referencing to URLSession in APIClient
GENERATED CODEGENERATED CODE
protocol APIClientInputDependencyResolver {
var urlSession: URLSession { get }
}
97. Referencing to URLSession in APIClient
GENERATED CODEGENERATED CODE
protocol APIClientInputDependencyResolver {
var urlSession: URLSession { get }
}
protocol APIClientDependencyResolver {
var urlSession: URLSession { get }
}
98. Referencing to URLSession in APIClient
GENERATED CODEGENERATED CODE
protocol APIClientInputDependencyResolver {
var urlSession: URLSession { get }
}
protocol APIClientDependencyResolver {
var urlSession: URLSession { get }
}
final class APIClientDependencyContainer: APIClientDependencyReso
let urlSession: URLSession
init(injecting dependencies: APIClientInputDependencyResolver
urlSession = dependencies.urlSession
}
}
99. Registering APIClient in MovieManager
final class MovieManager {
func getMovies(_ completion: @escaping ([Movie]?) -> Void) {
}
}
100. Registering APIClient in MovieManager
final class MovieManager {
private let dependencies: MovieManagerDependencyResolver
init(injecting dependencies: MovieManagerDependencyResolver)
self.dependencies = dependencies
}
func getMovies(_ completion: @escaping ([Movie]?) -> Void) {
}
}
101. Registering APIClient in MovieManager
final class MovieManager {
private let dependencies: MovieManagerDependencyResolver
// weaver: apiClient = APIClient
init(injecting dependencies: MovieManagerDependencyResolver)
self.dependencies = dependencies
}
func getMovies(_ completion: @escaping ([Movie]?) -> Void) {
}
}
102. Registering APIClient in MovieManager
final class MovieManager {
private let dependencies: MovieManagerDependencyResolver
// weaver: apiClient = APIClient
init(injecting dependencies: MovieManagerDependencyResolver)
self.dependencies = dependencies
}
func getMovies(_ completion: @escaping ([Movie]?) -> Void) {
dependencies.apiClient.get("http://my_movie_api/movies")
}
}
136. Registering URLSession with a bigger HTTP cache in
ImageManager
final class ImageManager {
private let dependencies: ImageManagerDependencyResolver
init(injecting dependencies: ImageManagerDependencyResolver) { self.d
}
137. Registering URLSession with a bigger HTTP cache in
ImageManager
final class ImageManager {
private let dependencies: ImageManagerDependencyResolver
// weaver: urlSession = URLSession
// weaver: urlSession.scope = .container
// weaver: urlSession.builder = ImageManager.makeURLSession
init(injecting dependencies: ImageManagerDependencyResolver) { self.d
}
138. Registering URLSession with a bigger HTTP cache in
ImageManager
final class ImageManager {
private let dependencies: ImageManagerDependencyResolver
// weaver: urlSession = URLSession
// weaver: urlSession.scope = .container
// weaver: urlSession.builder = ImageManager.makeURLSession
init(injecting dependencies: ImageManagerDependencyResolver) { self.d
static func makeURLSession(_: ImageManagerDependencyResolver) -> URLS
let configuration = ...
configuration.urlCache?.diskCapacity = 1024 * 1024 * 50
configuration.urlCache?.memoryCapacity = 1024 * 1024 * 5
return URLSession(configuration: configuration)
}
}