ReactiveSwift: flatten and some of its strategies
/*: | |
> # IMPORTANT: To use `ReactiveSwift.playground`, please: | |
1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveSwift project root directory: | |
- `git submodule update --init` | |
**OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed | |
- `carthage checkout --no-use-binaries` | |
1. Open `ReactiveSwift.xcworkspace` | |
1. Build `Result-Mac` scheme | |
1. Build `ReactiveSwift-macOS` scheme | |
1. Finally open the `ReactiveSwift.playground` | |
1. Choose `View > Show Debug Area` | |
*/ | |
import Result | |
import ReactiveSwift | |
import Foundation | |
/*: | |
## flatten | |
The power of ReactiveSwift lies within its operators. | |
Let's try to unleash some of its power by with a simple example. | |
You are working hard and almost ready to advance to the next level of Pokemon when your product manager appears out of nowhere. | |
"Hey geek, I have this great idea for our new product and our super smart CTO created a super secret function that does some super secret work" | |
*/ | |
func superSecretFunc(_ text:String, completion: @escaping ((Void) -> Void)) { | |
DispatchQueue.global(qos: .userInitiated).async { | |
let diceRoll = Int(arc4random_uniform(99) + 1) | |
usleep(UInt32(diceRoll)) | |
print("Ran \(text) on thread \(Thread.current) for \(diceRoll) milliseconds") | |
completion() | |
} | |
} | |
/*: | |
"Your task is to run this superSecretFunc 3 times but here is the tricky part; the calls must be executed serially, i.e. one after the other." | |
"Sure" - you reply - "Come back in a week and I will have it done" | |
2 weeks later ... | |
*/ | |
class CrappySolution1 { | |
public func runTasks() { | |
print("Solution 1 ...") | |
superSecretFunc("1") { | |
superSecretFunc("2") { | |
superSecretFunc("3") { } | |
} | |
} | |
Thread.sleep(forTimeInterval: 0.3) | |
} | |
} | |
/*: | |
"Hey, I saw you completed my task in Jira!" | |
"Hmm, I marked the task as completed a week ago!" | |
"I know, I know but I was busy watching Game of Thrones; I mean Enterprise Gamification but good job, can you demonstrate it to me?" | |
*/ | |
let so1 = CrappySolution1() | |
so1.runTasks() | |
/*: | |
"Oh man, this is so awesome. Can we run it 5 times though?" | |
"Sure, come back in 2 weeks." | |
Awesome, you think, I will have enough time to advance to Pokemon's level 22. | |
*/ | |
class CrappySolution2 { | |
public func runTasks() { | |
print("Solution 2 ...") | |
superSecretFunc("1") { | |
superSecretFunc("2") { | |
superSecretFunc("3") { | |
superSecretFunc("4") { | |
superSecretFunc("5") { | |
} | |
} | |
} | |
} | |
} | |
Thread.sleep(forTimeInterval: 0.3) | |
} | |
} | |
/*: | |
3 weeks later ... | |
*/ | |
let so2 = CrappySolution2() | |
so2.runTasks() | |
/*: | |
Of course your PM will now ask you to run it 7 or maybe even 9 times. | |
Number of nested calls will keep on growing and maintainability of your code will keep on declining. | |
That is where ReactiveSwift comes to our rescue! | |
*/ | |
class ReactiveSolution1 { | |
public func runTasks(times times:UInt) { | |
print("Reactive Solution 1 ...") | |
var producers = [SignalProducer<Void, NoError>]() | |
for i in 1...times { | |
let sp = SignalProducer<Void, NoError> { observer, disposable in | |
superSecretFunc(String(i)) { | |
observer.send(value: ()) | |
observer.sendCompleted() | |
} | |
} | |
producers.append(sp) | |
} | |
let fsp = SignalProducer<SignalProducer<Void, NoError>, NoError>(producers) | |
fsp.flatten(.concat) | |
.on(completed: { | |
print("Done!") | |
}, value: {}).start() | |
Thread.sleep(forTimeInterval: 0.5) | |
} | |
} | |
/*: | |
We can even run it a 100 times without a signle nested loop. | |
### And that is the beauty of ReactiveSwift. | |
*/ | |
let rso1 = ReactiveSolution1() | |
rso1.runTasks(times: 7) | |
/*: | |
So how does it work? | |
A SignalProducer represents an operation or a task. | |
SignalProducers might seem complicated but in its most basic usage SPs are simple. | |
The real power is of course in the operators. | |
### flatten | |
joins SignalProducers together. In other words it has the potential of executing tasks serially, one after another. | |
Flatten accepts only one parameter: FlattenStrategy | |
Let's take a look at some of them: | |
1. latest - will complete if the last SP completes. | |
1. concat - will complete if all SPs complete. | |
- It has an internal counter with a default value of 1. | |
- Every time a SP completes it increments the counter. | |
- Every time a value is received it decrements the counter. | |
- It will disregard a value if the counter is 0. | |
1. merge - will complete if all SPs complete. | |
Let's see it in action. | |
*/ | |
let s1 = SignalProducer<String, NoError> { observer, disposable in | |
observer.send(value: "1") | |
// observer.sendCompleted() | |
} | |
let s2 = SignalProducer<String, NoError> { observer, disposable in | |
observer.send(value: "2") | |
// observer.sendCompleted() | |
} | |
let s3 = SignalProducer<String, NoError> { observer, disposable in | |
observer.send(value: "3") | |
observer.sendCompleted() | |
} | |
let s5 = SignalProducer<SignalProducer<String, NoError>, NoError>([s1, s2, s3]) | |
print("\n ---> latest") | |
s5.flatten(.latest) | |
.on(completed: { | |
print("latest completed") | |
}, | |
value: { values in | |
print(values) | |
}).start() | |
print("\n ---> concat") | |
print("concat never completes because it is waiting for all inputs (producers) to complete") | |
s5.flatten(.concat) | |
.on(completed: { | |
// will never complete | |
print("concat completed") | |
}, | |
value: { values in | |
print(values) | |
}).start() | |
print("\n ---> merge") | |
print("like concat, merge never completes because it is waiting for all inputs (producers) to complete") | |
print("unlike concat, merge will send all the values from each input") | |
s5.flatten(.merge) | |
.on(completed: { | |
// will never complete | |
print("merge completed") | |
}, | |
value: { values in | |
print(values) | |
}).start() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment