Swift Language
RxSwift
Szukaj…
Podstawy RxSwift
FRP lub Functional Reactive Programming ma kilka podstawowych terminów, które musisz znać.
Każdy kawałek danych może być reprezentowany jako Observable
, który jest asynchronicznym strumieniem danych. Moc FRP polega na reprezentowaniu zdarzeń synchronicznych i asynchronicznych w postaci strumieni, Observable
s i zapewnia ten sam interfejs do pracy z nim.
Zwykle Observable
przechowuje kilka (lub nie ma) zdarzeń, które zawierają datę - .Next
zdarzenia, a następnie można je pomyślnie zakończyć (. .Success
) lub z błędem (. .Error
).
Rzućmy okiem na następujący marmurowy schemat:
--(1)--(2)--(3)|-->
W tym przykładzie jest strumień wartości Int
. W miarę upływu czasu wystąpiły trzy zdarzenia .Next
, a następnie strumień zakończył się pomyślnie.
--X->
Powyższy schemat pokazuje przypadek, w którym nie zostały wyemitowane żadne dane, a zdarzenie .Error
kończy Observable
.
Zanim przejdziemy dalej, istnieje kilka przydatnych zasobów:
- RxSwift . Spójrz na przykłady, przeczytaj dokumenty i zacznij.
- Pokój RxSwift Slack ma kilka kanałów rozwiązywania problemów edukacyjnych.
- Pobaw się z RxMarbles, aby dowiedzieć się, co robi operator i który jest najbardziej przydatny w twoim przypadku.
- Spójrz na ten przykład , poznaj kod samodzielnie.
Tworzenie obserwowalnych
RxSwift oferuje wiele sposobów na stworzenie Observable
, spójrzmy:
import RxSwift
let intObservale = Observable.just(123) // Observable<Int>
let stringObservale = Observable.just("RxSwift") // Observable<String>
let doubleObservale = Observable.just(3.14) // Observable<Double>
Tak więc tworzone są obserwowalne. Trzymają tylko jedną wartość, a następnie kończą się sukcesem. Niemniej jednak nic się nie stało po jego utworzeniu. Dlaczego?
Istnieją dwa etapy pracy z Observable
s: obserwujesz coś, aby utworzyć strumień, a następnie subskrybujesz strumień lub wiążesz go z czymś, co wchodzi w interakcję z nim.
Observable.just(12).subscribe {
print($0)
}
Konsola wydrukuje:
.Next(12)
.Completed()
A jeśli chciałbym pracować tylko z danymi, które mają miejsce w zdarzeniach .Next
, .Next
operatora subscribeNext
:
Observable.just(12).subscribeNext {
print($0) // prints "12" now
}
Jeśli chcę utworzyć obserwowalne wiele wartości, używam różnych operatorów:
Observable.of(1,2,3,4,5).subscribeNext {
print($0)
}
// 1
// 2
// 3
// 4
// 5
// I can represent existing data types as Observables also:
[1,2,3,4,5].asObservable().subscribeNext {
print($0)
}
// result is the same as before.
I wreszcie, może chcę Observable
, który działa. Na przykład wygodnie jest zawinąć operację sieciową w Observable<SomeResultType>
. Zobaczmy, jak można to osiągnąć:
Observable.create { observer in // create an Observable ...
MyNetworkService.doSomeWorkWithCompletion { (result, error) in
if let e = error {
observer.onError(e) // ..that either holds an error
} else {
observer.onNext(result) // ..or emits the data
observer.onCompleted() // ..and terminates successfully.
}
}
return NopDisposable.instance // here you can manually free any resources
//in case if this observable is being disposed.
}
Utylizacja
Po utworzeniu subskrypcji ważne jest, aby zarządzać jej prawidłowym zwolnieniem.
Doktorzy nam to powiedzieli
Jeśli sekwencja kończy się w skończonym czasie, brak wywołania dispose lub niestosowanie addDisposableTo (disposeBag) nie spowoduje trwałych wycieków zasobów. Jednak zasoby te będą wykorzystywane do momentu ukończenia sekwencji, albo przez zakończenie produkcji elementów, albo przez zwrócenie błędu.
Istnieją dwa sposoby zwolnienia zasobów.
- Korzystanie z
disposeBag
i operatoraaddDisposableTo
. - Korzystanie z operatora
takeUntil
.
W pierwszym przypadku ręcznie przekazujesz subskrypcję do obiektu DisposeBag
, który poprawnie usuwa całą DisposeBag
pamięć.
let bag = DisposeBag()
Observable.just(1).subscribeNext {
print($0)
}.addDisposableTo(bag)
W rzeczywistości nie musisz tworzyć DisposeBag
w każdej tworzonej klasie, wystarczy spojrzeć na projekt Społeczności RxSwift o nazwie NSObject + Rx . Za pomocą frameworka powyższy kod można przepisać w następujący sposób:
Observable.just(1).subscribeNext {
print($0)
}.addDisposableTo(rx_disposeBag)
W drugim przypadku, jeśli zbiega się w czasie z subskrypcji self
życia obiektu, jest to możliwe do wykonania przy użyciu utylizacji takeUntil(rx_deallocated)
:
let _ = sequence
.takeUntil(rx_deallocated)
.subscribe {
print($0)
}
Wiązania
Observable.combineLatest(firstName.rx_text, lastName.rx_text) { $0 + " " + $1 }
.map { "Greetings, \($0)" }
.bindTo(greetingLabel.rx_text)
Za pomocą operatora combineLatest
każdym razem, gdy element jest emitowany przez jednego z dwóch Observables
, połącz najnowszy element emitowany przez każdego Observable
. W ten sposób łączymy wynik tworzenia dwóch UITextField
z tekstem "Greetings, \($0)"
za pomocą interpolacji łańcuchów, aby później połączyć się z tekstem UILabel
.
Możemy powiązać dane z dowolnym UITableView
i UICollectionView
w bardzo prosty sposób:
viewModel
.rows
.bindTo(resultsTableView.rx_itemsWithCellIdentifier("WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in
cell.title = viewModel.title
cell.url = viewModel.url
}
.addDisposableTo(disposeBag)
To jest opakowanie Rx wokół metody źródła danych cellForRowAtIndexPath
. A także Rx dba o implementację metody numberOfRowsAtIndexPath
, która jest wymaganą metodą w tradycyjnym znaczeniu, ale nie musisz jej tutaj wdrażać, o nią zadbano.
RxCocoa i ControlEvents
RxSwift zapewnia nie tylko sposoby kontrolowania danych, ale także reaktywne reprezentowanie działań użytkownika.
RxCocoa zawiera wszystko, czego potrzebujesz. Zawija większość właściwości komponentów interfejsu użytkownika w Observable
, ale nie bardzo. Istnieje kilka ulepszonych Observable
nazwie ControlEvent
(które reprezentują zdarzenia) i ControlProperties
(które reprezentują właściwości, niespodzianka!). Te rzeczy trzymają Observable
strumienie pod maską, ale mają również pewne niuanse:
- Nigdy nie zawodzi, więc nie ma błędów.
-
Complete
sekwencję przy zwolnieniu kontroli. - Dostarcza zdarzenia do głównego wątku (
MainScheduler.instance
).
Zasadniczo możesz z nimi pracować jak zwykle:
button.rx_tap.subscribeNext { _ in // control event
print("User tapped the button!")
}.addDisposableTo(bag)
textField.rx_text.subscribeNext { text in // control property
print("The textfield contains: \(text)")
}.addDisposableTo(bag)
// notice that ControlProperty generates .Next event on subscription
// In this case, the log will display
// "The textfield contains: "
// at the very start of the app.
Jest to bardzo ważne, aby używać: tak długo, jak używasz Rx, zapomnij o rzeczach @IBAction
, wszystkim, czego potrzebujesz, aby związać i skonfigurować jednocześnie. Na przykład metoda viewDidLoad
kontrolera widoku jest dobrym kandydatem do opisania działania składników interfejsu użytkownika.
Ok, inny przykład: załóżmy, że mamy pole tekstowe, przycisk i etykietę. Chcemy, aby sprawdzić poprawność tekstu w polu tekstowym, gdy dotknij przycisk i wyświetlić wyniki w etykiecie. Tak, wygląda na to, że jest to kolejne zadanie z potwierdzeniem adresu e-mail, co?
Przede wszystkim bierzemy button.rx_tap
ControlEvent:
----()-----------------------()----->
Tutaj puste nawiasy pokazują krany użytkownika. Następnie bierzemy to, co jest zapisane w polu tekstowym za withLatestFrom
operatora withLatestFrom
(spójrz na to tutaj , wyobraź sobie, że górny strumień reprezentuje withLatestFrom
użytkownika, dolny - tekst w polu tekstowym).
button.rx_tap.withLatestFrom(textField.rx_text)
----("")--------------------("123")--->
// ^ tap ^ i wrote 123 ^ tap
Fajnie, mamy strumień ciągów do sprawdzania poprawności, emitowany tylko wtedy, gdy musimy sprawdzać poprawność.
Każdy Observable
ma takie znane operatory, jak map
lub filter
, weźmiemy map
aby sprawdzić poprawność tekstu. Utwórz samodzielnie funkcję validateEmail
, użyj dowolnego wyrażenia regularnego, które chcesz.
button.rx_tap // ControlEvent<Void>
.withLatestFrom(textField.rx_text) // Observable<String>
.map(validateEmail) // Observable<Bool>
.map { (isCorrect) in
return isCorrect ? "Email is correct" : "Input the correct one, please"
} // Observable<String>
.bindTo(label.rx_text)
.addDisposableTo(bag)
Gotowy! Jeśli potrzebujesz więcej niestandardowej logiki (takiej jak wyświetlanie widoków błędów w przypadku błędu, przejście do kolejnego ekranu po sukcesie ...), po prostu subskrybuj końcowy strumień Bool
i tam go zapisz.