Last active
May 23, 2018 02:43
-
-
Save seifeet/7e80c7ebead341df8bb78c0cef7109a1 to your computer and use it in GitHub Desktop.
ReactiveSwift: flatten and some of its strategies
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*: | |
> # 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