Reason - Example - Idea - Get Started - Installation
Because the classic way of building iOS Apps introduces tight coupling, often with the network layer. Dependencies grow and code is harder to test.
By providing a simple convention on how to cleanly plug implementations in an iOS App. Developers spend less time architecting and more time adding value for their users.
- Cleaner Architecture (think easier to maintain)
- Faster build times
- Less Dependencies (think Better Testability, & Faster tests)
- Code 90% of the App without the backend implementation
Download and launch the Example Project
to see how a Typical LikePhoto
use case would be implemented using this approach.
The main idea is that you want to decouple your App from it's delivery mechanisms.
Let me rephrases that for you in a classic iOS App context.
Your ViewControllers should not know how an action is performed. For instance when you like a photo, the controller should'nt know if it's going to send a web request
, a database command
, or even a local request
.
The ONLY thing it should know is : "I want to like this photo"
This is based on Robert-C Martin (Uncle Bob) thoughts, the guy behind the SOLID principles. You can watch one of his terrific talks here : https://skillsmatter.com/skillscasts/2437-uncle-bob-web-architecture His examples are often written in Java, and the swift examples I came across were often Java directly translated into swift.
This is an alternative approach aiming at the same goal, but leveraging swift awesomeness.
The entire Approach is built on the concept of Actions
.
An Action
has an Input
and an Output
.
Here is what it looks like in swift :
public protocol IsAction {
associatedtype Input
associatedtype Output
func perform(_ input: Input) -> Output?
}
For instance in the case of liking a photo, the LikePhoto
action has an input of Photo
and an Output of Promise<Void>
A cool thing is that Output can be synchronous or asynchronous, it's yours to choose \o/.
Here is how our App defines the LikePhoto
use-case :
class LikePhoto: Action<Photo,Promise<Void>> { }
action(LikePhoto.self, Photo()).then {
// photo liked !
}
Note : This uses dependency injection behind the hood to provide the concrete LikePhoto
implementation at runtime.
This phase is optional. But software is built for humans and we want this to be as readable as possible !
extension Photo {
func like() -> Promise<Void> {
return action(LikePhoto.self, self)
}
}
You can now like a photo like this:
photo.like().then {
// Photo liked \o/
}
class MyLikePhoto: LikePhoto {
override func perform(_ input: Photo) -> Promise<Void> {
return network.post("/photos/\(input.identifier)/like")
}
}
We can now Inject this implementation in our App form the AppDelegate
:
Actions.plug(LikePhoto.self, to: MyLikePhoto())
Or if you prefer the short version :
LikePhoto.self <~ MyLikePhoto()
All the LikePhoto
actions of our App will now use our concrete Implementation preforming a network call :).
This is now super easy to provide a dummy MockLikePhoto
for testing purposes!
Using this approach we have:
- A
Type-Safe
way to decouple and inject actions in our App. - A clean and readable way to call actions on models.
https://github.com/s4cha/Plug
github "s4cha/Plug"
Swift 3 -> version 0.2.0
Swift 4 -> version 0.3.0
Swift 4.2 -> version 1.0.0
Swift 5.0 -> version 1.1.0
Swift 5.1 -> version 1.1.1
Swift 5.1.3 -> version 1.1.2