RxSwift – Grundlagen und praktische Beispiele

Seite 4: Ein komplexeres Beispiel

Inhaltsverzeichnis

Abschließend fasst ein etwas komplexerer Beispiel alle Konzepte zusammen. Zur Ausführung ist der Umgebung des vorherigen Kapitels noch der Pod RxTest hinzuzufügen. Es soll folgende Fachlichkeit abgebildet werden: Coding Kid ist einer der Superhelden aus Coding Valley. Weil er ein vielbeschäftigter Held ist, möchte er sich ein System bauen, das die Nachrichten aus mehreren Kanälen integriert und ihn auf relevante Gefahren hinweist. Als geeignete Input-Kanäle identifiziert er Polizeifunkmeldungen und Tweets.

Erstere wird modelliert als:

struct Funkspruch {
var desc: String;
var severity: Int;
var requiresHero: Bool;
}

Und ein Tweet als:

struct Tweet {
var location: String;
var text: String;
}

Das Ziel von Coding Kit ist es, die beiden Event-Streams zusammenzufassen und als Event auszugeben:

struct Event : Equatable{
var desc: String;
// Hilfsmethode zur Evaluierung der Tests
static func ==(lhs: Event, rhs: Event) -> Bool {
return lhs.desc == rhs.desc
}
}

Allerdings möchte er nur eine Information erhalten, wenn die Severity eines Funkspruchs größer als sieben ist und explizit ein Held angefordert wird. Über Tweets möchte er nur informiert werden, wenn sie in Coding Valley erstellt wurden und das Wort "Bug" enthalten. Das Ergebnis ist folgendes Warnsystem:

struct Warnsystem {
let eventStream: Observable<Event>

init(polizeiFunk: Observable<Funkspruch>, twitterFeed: Observable<Tweet>) {
eventStream = Observable.of(
polizeiFunk
.filter {$0.requiresHero && $0.severity > 7}
.map {funkspruch in return Event(desc: funkspruch.desc)},
twitterFeed
.filter {$0.location == "Coding Valley" && $0.text.contains("Bug")}
.map {tweet in return Event(desc: tweet.text)}
).merge()
// merge führt zwei Streams zusammen, in dem jedes Event der beiden Streams
// als eigenes Event in einem neuen Observable ausgegeben werden.
}
}

Um zu überprüfen, ob sein System funktioniert, baut er einen Test mit RxTest und sendet ein paar Tweets und Funksprüche an sein System:

func testWarnsystem(){
// Der scheduler wird die Test-Events zu definierten Zeitpunkten simulieren
let scheduler = TestScheduler(initialClock: 0)

// Definition der Funksprüche, die emittiert werden
let funkspruch1 = Funkspruch(desc: "Writing a callback-pyramid",
severity: 10,
requiresHero: true)
let funkspruch2 = Funkspruch(desc: "Not testing your Rx-Code",
severity: 6,
requiresHero: false)

// Erstellen eines Observables mit den definierten Funksprüchen
let funkObservable = scheduler.createHotObservable([
Recorded.next(1, funkspruch1),
Recorded.next(4, funkspruch2),
Recorded.completed(6)
]).asObservable();

// Definition der Tweets, die emittiert werden
let tweet1 = Tweet(location: "Coding Valley",
text: "Let's go and deploy.")
let tweet2 = Tweet(location: "Coding Valley",
text: "Bug in Production. Please send help!")
let tweet3 = Tweet(location: "Somewhere else",
text: "Boring day.")

// Erstellen eines Observables mit den definierten Tweets
let tweetObservable = scheduler.createHotObservable([
Recorded.next(2, tweet1),
Recorded.next(3, tweet2),
Recorded.next(5, tweet3),
Recorded.completed(8)
]).asObservable();

// Definition der Events, die als Ausgabe des Warnsystems erwartet werden.
let expectedEvents = [
Recorded.next(1, Event(desc: "Writing a callback-pyramid")),
Recorded.next(3, Event(desc: "Bug in Production. Please send help!")),
Recorded.completed(8)
]

// Initialisierung des Warnsystems
let warnsystem = Warnsystem(polizeiFunk: funkObservable, twitterFeed: tweetObservable)

// Zuweisung des Ausgabe-Streams zu einem testObserver, sodass die
// Ergebnisse mit den erwarteten Ergebnissen verglichen werden können
let testObserver = scheduler.createObserver(Event.self)
warnsystem.eventStream
.subscribe(testObserver)
.disposed(by: disposeBag)

// Start der Erzeugung der Events
scheduler.start()

// Vergleich der Ausgabe mit der erwarteten Ausgabe
XCTAssertEqual(expectedEvents, testObserver.events)
}

Der Test ist erfolgreich und alles funktioniert. Aus dem Beispiel lässt sich eine weitere Erkenntnis gewinnen, die für die reaktive Entwicklung sehr wichtig ist: Statt des üblichen Informations-Pulls der imperativen Entwicklung übernimmt RxSwift den Informations-Push, auf den Entwickler reagieren. Imperative Programme müssten sich darum kümmern, regelmäßig nach neuen Tweets beziehungsweise Funksprüchen zu fragen, die letzten Ergebnisse vorzuhalten, Deltas zu identifizieren et cetera. Mit RxSwift lässt sich eine Funktion definieren, die über neue Events informiert wird und diese entsprechend verarbeitet. Asynchronität, Nebenläufigkeit und Zustände sind dabei vollständig abstrahiert.