Skip to content

Instantly share code, notes, and snippets.

@seifeet
Last active May 23, 2018 02:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seifeet/7e80c7ebead341df8bb78c0cef7109a1 to your computer and use it in GitHub Desktop.
Save seifeet/7e80c7ebead341df8bb78c0cef7109a1 to your computer and use it in GitHub Desktop.
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