Please enable JavaScript.
Coggle requires JavaScript to display documents.
Advance Architecture (Object and their dependencies (Goals (Testability…
Advance Architecture
Object and their dependencies
Goals
Testability
Deterministic unit and UI tests, i.e., tests that don’t rely on things you can’t control, such as the network.
Substitutability
The ability to substitute the implementation of a dependency at compile-time and at runtime
Maintainability
The ability to easily change a code-base without introducing defects
Deferability
Having the ability to defer big decisions such as selecting a database technology.
Parallel work streams
Being able to have multiple developers work independently on the same feature at the same time without stepping on each others toes.
Control during development:
A code-base that developers can quickly iterate on by controlling build and run behavior, e.g., switching from a keychain-based credential store to a fake in-memory credential store so that you don’t need to sign in and out over and over again while working on a sign-in screen.
Minimizing object lifetimes
For any given app, the less state a developer has to manage at once, the more predictably an app behaves. Therefore, you want to have the least amount of objects in-memory at once
Reusability
Building a code-base out of components that can be easily reused across multiple features and multiple apps.
Terms
consumer
uses the object under construction
Outside
refers to code that exists outside the object-under-construction.
object-under-construction
depends on dependencies
Inside
refers to the code that exists inside the object-under-construction
Dependency
dependency is an object that another object depends on in order to do some work
can also depend on other objects. These other objects are called
transitive dependencies
The fundamental considerations
Accessing dependencies
From the inside
Global property
The object-under-construction can simply access any visible global property.
Instantiation
If a dependency is ephemeral “the object-under-construction can instantiate the dependency
From the outside
Mutable stored-property
A dependency can be provided to an already created object-under-construction by setting a visible mutable stored-property on the object-under-construction
Method
Dependencies can be provided to the object-under-construction through visible methods of the object-under-construction.
Initializer argument
A dependency can be provided to the object-under-construction as an initializer argument
Determining substitutability
if the dependency writes something to disk, makes a network call, sends analytic events, navigates the user to another screen, etc. then you probably want to substitute the dependency’s implementation during development or during testing
Designing substitutability
If you do need to substitute a dependency’s implementation then you need to decide if you need to substitute the implementation at compile-time, at runtime or both
To illustrate, you’ll probably need runtime substitutability when you need to provide a different experience to different users for A/B testing. On the other hand, for testing, developers typically rely on compile-time substitutability
Dependency patterns
Dependency Injection
The basic idea of the pattern is to provide all dependencies outside the object-under-construction
Service Locator
is an object that can create dependencies and hold onto dependencies
You provide the object-under-construction with a Service Locator. Whenever the object-under-construction needs a dependency, the object-under-construction can simply ask the Service Locator to create or provide the dependency
This pattern is easier to use than Dependency Injection but results in more work when harnessing automated tests. Many developers use this pattern to successfully achieve the goals outlined in this chapter
Environment
An environment is a mutable struct that provides all the dependencies needed by objects-under-construction
Dependency Injection
Types of injection
Property
After instantiating the object-under-construction, the consumer provides a dependency to the object-under-construction by setting a stored-property on the object-under-construction with the dependency
This injection type is usually used in Interface Builder-backed view controllers because you don’t have control over which initializer UIKit uses to create Interface Builder-backed view controllers
Method
The consumer provides dependencies to the object-under-construction when calling a method on the object-under-construction
Method injection is rarely used. If a dependency is only used within a single method, then you could use method injection to provide the dependency. This way, the object-under-construction doesn’t need to hold onto the dependency
Initializer
you add dependencies to the object-under-construction’s initializer parameter list. This is the best injection type because the object-under-construction can store “the dependency in an immutable stored-property. The object-under-construction doesn’t need to handle the case in which dependencies are nil and doesn’t have to handle the case in which dependencies change
Circular dependencies
Sometimes, two objects are so closely related to each other that they need to depend on one another. For this case to work when using Dependency Injection, you have to use property or method injection in one of the two objects that are in the circular dependency
Substituting dependency implementations
Runtime substitution
Simply grab the launch arguments from UserDefaults and wrap your dependency instantiations with if statements that check launch argument values. You can use this trick during development or even during a continuous integration test
Compile-time substitution
you add compilation condition identifiers to Xcode’s active compilation conditions build setting
DI approaches
Container hierarchy
Goals
One of the problems with centralizing all the initialization logic is you end up with one massive class. You can break a single container down into a hierarchy of containers. That’s what this approach is all about
You want to explicitly design when objects come and go. For example, objects in a user scope are created when a user signs in and are destroyed when a user signs out
Object scopes
User scope
User scope objects are created when a user signs in, and they’re destroyed when a user signs out
Feature scope
Objects in a feature scope are created when the user navigates to a feature and are destroyed when the user navigates away
App scope
Objects in the app scope are created when the app launches and are destroyed when the app is killed
For every scope you design, you create a container class.
Designing a container hierarchy
The app scoped container is always the root container
A child container can ask for dependencies from its parent container including the parent’s parents and so on, all the way to the root container
Single container
Goals
A container is like a factories class that can hold onto long-lived dependencies. A container is a stateful version of a factories class
This approach packages all the initialization logic together into one container. Since there’s state involved, it’s a bit more difficult to put into practice than the previous two approaches
Elements
Dependency factory methods
To get a reference to a long-lived transitive dependency, a dependency factory method simply gets the dependency from a stored property
Object-under-construction factory methods
Factory methods that create objects-under-construction can also use the stored properties to inject long-lived dependencies into objects-under-construction.
Container class
A container class looks just like a factories class except with stored properties that hold onto long-lived dependencies
Substituting long-lived dependency implementations
You can substitute implementations of long-lived dependencies by wrapping their initialization line with a conditional statement. This is possible as long as the long-lived stored properties use a protocol type
Factories
you begin to centralize initialization logic. This approach is also fairly simple and is designed to help you learn the fundamentals
Factories class
Some of the methods create dependencies and some of the methods create objects-under-construction
Dependency factory methods
Resolving protocol dependencies
Dependency factory methods typically have a protocol return type to enable substitutability
When this is true, dependency factory methods encapsulate the mapping between protocol and concrete types. This is typically called resolution because a dependency factory method is resolving which implementation to create for a particular protocol dependency. In other words, these methods know which concrete initializer to use
Creating and getting transitive dependencies
create an ephemeral transitive dependency, a dependency factory method can simply call another dependency factory included in the factories class
To get a reference to a long-lived transitive dependency, a dependency factory method should include a parameter for the transitive dependency. By adding parameters, long-lived transitive dependencies can be provided to the dependency factory method
Object-under-construction factory methods
Getting runtime values
the factory method caller will need to provide the required values as arguments
Substituting dependency implementations
you write the conditional statement once, and only once, for each dependency resolution
create the dependency graph needed to instantiate an object-under-construction
Injecting factories
Purpose: it’s important to give objects-under-construction the ability to create multiple instances of dependencies from the outside
Using protocols
Using closures
On-demand
create dependency graphs when needed in a decentralized fashion. This approach is simple yet not very practical
whenever a consumer needs a new object-under-construction, the consumer creates or finds the dependencies needed by the object-under-construction at the time the consumer instantiates the object-under-construction
MVVM
View Layer
Reacts to state changes through bindings to view model properties
View model layer
Task Methods
Perform tasks in response to user interactions
Update the view model's state
Dependencies
Are passes to the view model through initializers
View State
is stored in the view model
Made up of public observable properties
Model Layer
Push and pull
Observe and push
CRUD