Skip to content
This repository was archived by the owner on Jun 13, 2024. It is now read-only.

Commit 65dcdd5

Browse files
authored
Merge pull request #81 from square/sebastianv1/readme-updates
README Updates and declare Singleton Scope.
2 parents 02bb47c + 79a2c41 commit 65dcdd5

2 files changed

Lines changed: 66 additions & 58 deletions

File tree

Cleanse/Scope.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public protocol _ScopeBase {
1818
public protocol Scope : _ScopeBase {
1919
}
2020

21+
public struct Singleton : Scope {}
22+
2123
/// This a special scope that means its not scoped
2224
public struct Unscoped : _ScopeBase {
2325
}

README.rst

Lines changed: 64 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ Cleanse - Swift Dependency Injection
1515
Cleanse is a `dependency injection`_ framework for Swift. It is designed from the ground-up with *developer experience*
1616
in mind. It takes inspiration from both `Dagger`_ and `Guice`_.
1717

18-
Cleanse is currently in *beta* phase. This means its API is subject to change (but for the better).
19-
2018
.. _dependency injection: https://en.wikipedia.org/wiki/Dependency_injection
2119
.. _Guice: https://github.com/google/guice
2220
.. _Dagger: http://google.github.io/dagger/
@@ -29,8 +27,13 @@ A full-fledged example of using Cleanse with Cocoa Touch can be found in ``Examp
2927

3028
Installation
3129
````````````
32-
Cleanse can be added to your project multiple ways. How to add it, depends on your environment (whether using Xcode
33-
or the open source toolchain) as well as what your preferred dependency management software.
30+
Using `CocoaPods`_
31+
~~~~~~~~~~~~~~~
32+
You can pull in the latest Cleanse version into your ``Podfile`` using
33+
34+
``pod 'Cleanse'``
35+
36+
.. _CocoaPods: https://github.com/cocoapods/cocoapods/
3437

3538
Using Xcode
3639
~~~~~~~~~~~
@@ -81,89 +84,90 @@ construction of that, so we have to use `Property Injection`_ to populate the re
8184
`Property Injection`_ should be used only when absolutely necessary
8285
(when we don't control the construction of a type)
8386

84-
Let's start by defining the Root ``Component``:
87+
Let's start by defining the ``RootComponent``:
8588

8689
.. code-block:: swift
8790
8891
extension AppDelegate {
89-
struct Component : Cleanse.Component {
90-
// When we call AppComponent().build() it will return the Root type if successful
92+
struct Component : Cleanse.RootComponent {
93+
// When we call build() it will return the Root type, which is a PropertyInjector<AppDelegate>.
94+
// More on how we use the PropertyInjector type later.
9195
typealias Root = PropertyInjector<AppDelegate>
92-
93-
func configure<B : Binder>(binder binder: B) {
94-
// Will fill out contents later
96+
97+
// Required function from Cleanse.RootComponent protocol.
98+
static func configureRoot(binder bind: ReceiptBinder<PropertyInjector<AppDelegate>>) -> BindingReceipt<PropertyInjector<AppDelegate>> {
99+
100+
}
101+
102+
// Required function from Cleanse.RootComponent protocol.
103+
static func configure(binder: Binder<Unscoped>) {
104+
// We will fill out contents later.
95105
}
96106
}
97107
}
108+
109+
After creating our root component, we find that we're required to implement two functions:
110+
``static func configureRoot(binder bind: ReceiptBinder<PropertyInjector<AppDelegate>>) -> BindingReceipt<PropertyInjector<AppDelegate>>`` and ``static func configure(binder: Binder<Unscoped>)``
98111

99-
Now, in our App Delegate we should add:
100-
101-
.. code-block:: swift
102-
103-
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
104-
// Build our component, and make the property injector
105-
let propertyInjector = try! Component().build()
106-
107-
// Now inject the properties into ourselves
108-
propertyInjector.injectProperties(into: self)
112+
The parameters and return types are confusing right now, but will make more sense as we go along.
109113

110-
window!.makeKeyAndVisible()
111-
112-
return true
113-
}
114-
115-
Now, if we ran the app as is, it would blow up. We haven't told cleanse how to make a `PropertyInjector<AppDelegate>`,
116-
so let's do that. For the simplest app delegates, we need to populate just one property:
114+
The first function is required of any `Component` since it tells Cleanse how to construct the root object. Since we have to use property injection, we will fill out its contents with the following:
117115

118116
.. code-block:: swift
119117
120-
var window: UIWindow?
118+
static func configureRoot(binder bind: ReceiptBinder<PropertyInjector<AppDelegate>>) -> BindingReceipt<PropertyInjector<AppDelegate>> {
119+
return bind.propertyInjector(configuredWith: { bind in
120+
bind.to(injector: AppDelegate.injectProperties)
121+
})
122+
}
123+
124+
**Note**: Even though we can configure property injection with closures, it is generally cleaner to make a method that sets the
125+
properties like we did with `AppDelegate.injectProperties`:
121126

122-
Even though we can configure property injection with closures, it is generally cleaner to make a method that sets the
123-
properties. Let's define a method like:
124127

125-
.. code-block:: swift
128+
Now, in our App Delegate let's add the new function we referenced when configuring our root object.
129+
This tells Cleanse to use the ``injectProperties`` function when a ``PropertyInjector<AppDelegate>`` is
130+
requested.
126131

127-
extension AppDelegate {
128-
/// Requests the main window and sets it
129-
func injectProperties(window: UIWindow) {
130-
self.window = window
131-
}
132+
.. code-block::swift
133+
func injectProperties(_ window: UIWindow) {
134+
self.window = window
132135
}
133136
134-
And add the following to ``AppDelegate.Component.configure``
137+
We've successfully wired up our root component! Our root object `PropertyInjector<AppDelegate>` is configured properly, so in our App Delegate we can now `build` the component (and graph) to use.
135138

136139
.. code-block:: swift
137140
138-
func configure<B : Binder>(binder binder: B) {
139-
binder
140-
.bindPropertyInjectionOf(AppDelegate.self)
141-
.to(injector: AppDelegate.injectProperties)
142-
}
141+
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
142+
// Build our component, and make the property injector
143+
let propertyInjector = try! ComponentFactory.of(AppDelegate.Component.self).build(())
143144
144-
This tells Cleanse to use the ``AppDelegate.injectProperties()`` function when a ``PropertyInjector<AppDelegate>`` is
145-
requested.
145+
// Now inject the properties into ourselves
146+
propertyInjector.injectProperties(into: self)
147+
148+
window!.makeKeyAndVisible()
146149
150+
return true
151+
}
147152
148153
Satisfying Dependencies
149154
~~~~~~~~~~~~~~~~~~~~~~~
150155

151-
Running the app now, would yield a new error saying a provider for ``UIWindow`` is missing. That's because we haven't
152-
configured it.
156+
Running the app now will yield a new error saying a provider for ``UIWindow`` is missing. That's because we referenced it from our `injectProperties` function, but Cleanse didn't find a binding for the ``UIWindow`` type. So let's create one!
153157

154158
A ``Module`` in Cleanse is similar to a ``Component`` but doesn't define a root object, ``Component``\ s can *install*
155159
``Module``\ s and ``Modules``\ s can install other ``Modules`` using ``binder.install(module:)``.
156160

157-
Let's define a module that creates our main window. The following will declare `UIWindow` as a singleton.
161+
Let's define a module that creates our main window. The following will declare `UIWindow` as a singleton. We can do this by changing the parameter `Binder<Unscoped>` to `Binder<Singleton>`. You can learn more about scopes in the `Scope Step`_ section.
158162

159163
.. code-block:: swift
160164
161165
extension UIWindow {
162166
struct Module : Cleanse.Module {
163-
public func configure<B : Binder>(binder binder: B) {
167+
public func configure(binder: Binder<Singleton>) {
164168
binder
165169
.bind(UIWindow.self)
166-
.asSingleton()
170+
.sharedInScope()
167171
.to { (rootViewController: TaggedProvider<UIViewController.Root>) in
168172
let window = UIWindow(frame: UIScreen.mainScreen().bounds)
169173
window.rootViewController = rootViewController.get()
@@ -177,7 +181,7 @@ and in our ``AppDelegate.Component.configure`` method we want to install this mo
177181

178182
.. code-block:: swift
179183
180-
binder.install(module: UIWindow.Module())
184+
binder.install(module: UIWindow.Module.self)
181185
182186
We have satisfied the dependency for our App Delegate (``UIWindow``), but we have a new dependency,
183187
``TaggedProvider<UIViewController.Root>``. The ``TaggedProvider<UIViewController.Root>`` represents a "special" view
@@ -227,24 +231,26 @@ And we'll want to make a module to configure it:
227231
func configure<B : Binder>(binder binder: B) {
228232
// Configures the RootViewController to be provided by the initializer
229233
binder
230-
.bind()
234+
.bind(RootViewController.self)
231235
.to(factory: RootViewController.init)
232236
233237
// This satisfies UIWindow depending on TaggedProvider<UIViewController.Root>
234238
// The actual root is our RootViewController wrapped in a UINavigationController
235239
binder
236-
.bind()
240+
.bind(UIViewController.self)
237241
.tagged(with: UIViewController.Root.self)
238242
.to { UINavigationController(rootViewController: $0 as RootViewController) }
239243
}
240244
}
241245
}
242246
247+
(note: One can omit the `RootViewController.self` from the binding builder as it's not required, but we recommend you include it anyways to make it easier to discover where specific types are constructed in Cleanse.)
248+
243249
and in our ``AppDelegate.Component.configure`` method we want to install this module by adding
244250

245251
.. code-block:: swift
246252
247-
binder.install(module: RootViewController.Module())
253+
binder.install(module: RootViewController.Module.self)
248254
249255
250256
Now, all of our dependencies should be satisfied and the app should launch successfully.
@@ -484,7 +490,7 @@ One passes it an instance of a module to be installed. It is used like:
484490

485491
.. code-block:: swift
486492
487-
binder.install(module: PrimaryAPIURLModule())
493+
binder.install(module: PrimaryAPIURLModule.self)
488494
489495
It essentially tells the binder to call ``configure(binder:)`` on ``PrimaryAPIURLModule``.
490496

@@ -513,7 +519,7 @@ process of configuring a binding through code completion. A simplified grammar f
513519
binder
514520
.bind([Element.self]) // Bind Step
515521
[.tagged(with: Tag_For_Element.self)] // Tag step
516-
[.asSingleton()] // Scope step
522+
[.sharedInScope()] // Scope step
517523
{.to(provider:) | // Terminating step
518524
.to(factory:) |
519525
.to(value:)}
@@ -532,12 +538,12 @@ An optional step that indicates that the provided type should actually be
532538
`Type Tags`_ for more information
533539

534540

535-
Scope Step (Optional)
541+
Scope Step
536542
~~~~~~~~~~~~~~~~~~~~~
537543

538544
By default, whenever an object is requested, Cleanse constructs a new one.
539-
If `.asSingleton()` is specified, Cleanse will memoize and return the same instance in the scope of the ``Component``
540-
it was configured in.
545+
If the optional `.sharedInScope()` is specified, Cleanse will memoize and return the same instance in the scope of the ``Component``
546+
it was configured in. So if this is configured as a singleton in the `RootComponent`, then will return the same instance for the entire app.
541547

542548
In the future we may want to allow a class conforming to protocol (possibly named ``Singleton``) to indicate that it
543549
should be bound as a singleton. It is tracked by `this issue`_

0 commit comments

Comments
 (0)