Implementieren Sie MVVM unter iOS mit RxSwift

Ursprünglicher Autor: Marin Benčević
  • Übersetzung
Es gibt unzählige Artikel zum MVVM-Muster in iOS, aber ein wenig über RxSwift, und wenige konzentrieren sich darauf, wie das MVVM-Muster in der Praxis aussieht und wie es implementiert wird.

Reactivex

ReactiveX ist eine Bibliothek zum Erstellen von asynchronen und ereignisbasierten Programmen unter Verwendung einer beobachtbaren Sequenz. -  reactivex.io

RxSwift - ein relativ neues Framework , das Ihnen erlaubt , „ reaktive Programmierung .“ Wenn Sie nichts über ihn wissen, stellen Sie Anfragen, da die Funktionale Reaktive Programmierung (FRP) an Dynamik gewinnt und nicht aufhören wird.

Wie sieht MVVM in iOS aus?


Die Art und Weise, wie das Modell bei Verwendung des MVC-Musters eine Verbindung zum ViewController herstellt, sieht häufig wie ein Hack aus. In der Regel rufen Sie so etwas wie die updateUI () -Funktion im Controller auf, wenn Sie glauben, dass sich das Modell geändert hat, was zu Inkonsistenzen zwischen dem Modell und dem ViewController, unnötigen Aktualisierungen und unverständlichen Fehlern führen kann.

Wir brauchen einen ViewController, der sowieso den wahren Zustand des Modells anzeigt. Im Wesentlichen benötigen wir einen ViewController, bei dem es sich um einen einfachen Proxyserver handelt, der Daten entsprechend dem aktuellen Status des Modells auf dem Bildschirm anzeigt.

In den meisten Anwendungen ist es natürlich nutzlos, wenn das Modell nur auf dem Bildschirm angezeigt wird. Wir müssen Daten aus dem Modell abrufen und sie für die Anzeige vorbereiten. Aus diesem Grund führen wir die ViewModel- Klasse ein .ViewModel bereitet alle Daten vor, die auf dem Bildschirm angezeigt werden müssen.

Aber hier ist der lustige Teil: ViewModel weiß nichts über ViewController. Es bezieht sich nie darauf und hat kein Eigentum direkt. Stattdessen überwacht der ViewController ständig alle Änderungen am ViewModel und sobald Änderungen auftreten, werden sie sofort auf dem Bildschirm angezeigt. Es ist zu beachten, dass der ViewController jede Eigenschaft mit dem ViewModel einzeln auf dem Bildschirm anzeigt, d. H. Wenn Sie eine Zeile und ein Bild nacheinander herunterladen möchten, können Sie die Zeile sofort anzeigen, da die Daten bereits vorhanden sind, und Sie müssen nicht warten, bis das Bild geladen ist, um sie zusammen anzuzeigen.

ViewController zeigt jedoch nicht nur Daten auf dem Bildschirm an, sondern empfängt auch Dateneingaben vom Benutzer. Da unser ViewController nur ein Proxyserver ist, kann er diese Daten nicht verwenden. Alles, was er tun muss, ist, sie an ViewModel weiterzuleiten. Den Rest erledigt ViewModel.

Bild

In gewisser Weise handelt es sich um eine einseitige Verbindung zwischen dem ViewController und dem ViewModel. Der ViewController kann das ViewModel sehen und darauf zugreifen, aber der ViewModel weiß nicht, wer ViewController ist. Dies bedeutet, dass Sie ViewController vollständig aus Ihrer Anwendung entfernen können und Ihre gesamte Logik wie vorgesehen funktioniert! 

Es klingt alles gut, aber wie machen wir das?

MVVM in Verbindung mit RxSwift


Lassen Sie uns eine einfache Anwendung erstellen, die die Wettervorhersage für eine Stadt anzeigt, die ein Benutzer eingibt.

In diesem Artikel wird davon ausgegangen, dass Sie mit RxSwift vertraut sind. Wenn Sie nichts darüber wissen, lesen Sie weiter. Ich empfehle Ihnen jedoch, mehr über ReactiveX zu erfahren .

post_images

Wir haben ein UITextField zur Eingabe des Stadtnamens und ein paar UILabels zur Anzeige der aktuellen Temperatur.

Hinweis: Für diese Anwendung erhalte ich Wetterdaten von OpenWeatherMap .

Unser Modell wird also eine Weather-Klasse mit mehreren Eigenschaften für Namen und Grade und einem Initialisierer sein, der ein JSON-Objekt verwendet, das analysiert und die Eigenschaften festlegt.

class Weather {
   var name:String?
   var degrees:Double?
   init(json: AnyObject) {
      let data = JSON(json)
      self.name = data["name"].stringValue
      self.degrees = data["main"]["temp"].doubleValue
   }
}

Hinweis:  SwiftyJSON  ist erforderlich, um JSON in Swift zu analysieren.

Jetzt müssen wir dem ViewModel erlauben, das Modell über die öffentliche Eigenschaft searchText zu steuern, auf die der ViewController später zugreifen kann.

class ViewModel {
  private struct Constants {
      static let URLPrefix = "http://api.openweathermap.org/data/2.5/weather?q="
      static let URLPostfix = "/* my openweathermap APPID */"
    }
   let disposeBag = DisposeBag()
   var searchText = PublishSubject()

Unser searchText ist ein PublishSubject. Die Probanden sind sowohl beobachtbar als auch beobachtend. Mit anderen Worten, Sie können ihnen Elemente senden, die sie regenerieren können.

PublishSubjects sind eindeutig, da bei der Übertragung von Daten an PublishSubject diese an alle Abonnenten gesendet werden, die sie derzeit abonnieren. Dies ist erforderlich, da in MVVM in Abhängigkeit vom Lebenszyklus der Anwendung in verschiedenen Klassen beobachtbare Elemente manchmal empfangen können, bevor Sie sie abonnieren. Sobald der ViewController die ViewModel-Eigenschaft abonniert, sollte er feststellen, dass beim letzten Element, das sie anzeigt, alles in Ordnung ist und umgekehrt.

Jetzt müssen wir die Eigenschaft im ViewModel für jedes UI-Element deklarieren, das Sie programmgesteuert ändern möchten.

var cityName = PublishSubject()
var degrees = PublishSubject()

Legen wir auch eine Eigenschaft für unser Modell fest und ändern Sie die Eigenschaften jedes Mal, wenn sich unser Modell ändert. Wir werden dies tun, indem wir den 'altmodischen' Modus (Swift Property Observers) mit Rx kombinieren. Wir senden die Eigenschaften des Weather-Objekts an unsere PublishSubjects, damit diese Werte im Modell generieren können.

var weather:Weather? {
   didSet {
      if let name = weather?.name {
         dispatch_async(dispatch_get_main_queue()) {
            self.cityName.onNext(name)
         }
      }
      if let temp = weather?.degrees {
         dispatch_async(dispatch_get_main_queue()) {
            self.degrees.onNext("\(temp)°F")
         }
      }
   }
}

Hinweis: Wir müssen sicherstellen, dass dies im Haupt-Thread ausgeführt wird, da die onNext () -Methode in einem anderen Thread ausgeführt wird! (Die onNext-Methode löst ein Observer-Element aus.)

Hängen wir nun unser Modell an die oben angekündigte searchText-Eigenschaft an. Dazu erstellen wir jedes Mal eine NSURLRequest, wenn Änderungen am searchText vorgenommen werden. Anschließend abonnieren wir diese Anforderung für unser Modell. Wir werden dies in der init () -Methode tun, da wir wissen, dass alle unsere Eigenschaften festgelegt sind, wenn die init () -Methode aufgerufen wird.

init() {
   let jsonRequest = searchText
      .map { text in
         return NSURLSession.sharedSession().rx_JSON(self.getURLForString(text)!)
      }
      .switchLatest()
   jsonRequest
      .subscribeNext { json in
         self.weather = Weather(json: json)
       }
      .addDisposableTo(disposeBag)
}

Auf diese Weise ändert sich jsonRequest bei jeder Änderung von searchText in die entsprechende NSURLRequest. Bei jeder Änderung erhält unser Modell die Daten, die wir von NSURLRequest erhalten haben.

Anmerkung: Die Methode rx_JSON () ist eine beobachtbare Sequenz. JsonRequest ist also eigentlich ein Observable eines Observable. Aus diesem Grund verwenden wir .switchLatest (), wodurch sichergestellt wird, dass jsonRequest nur eine neue Sequenz zurückgibt. Denken Sie auch daran, dass die Anforderung erst dann abgerufen wird, wenn Sie sie abonnieren.

Jetzt muss nur noch der ViewController mit dem ViewModel verbunden werden. Dazu binden wir PublishSubjects im ViewModel an den Ausgang im Controller.

class ViewController: UIViewController {
   let viewModel = ViewModel()
   let disposeBag = DisposeBag()
   @IBOutlet weak var nameTextField: UITextField!
   @IBOutlet weak var degreesLabel: UILabel!
   @IBOutlet weak var cityNameLabel: UILabel!
   override func viewDidLoad() {
      super.viewDidLoad()
      //Binding the UI
      viewModel.cityName.bindTo(cityNameLabel.rx_text)
         .addDisposableTo(disposeBag)
      viewModel.degrees.bindTo(degreesLabel.rx_text)
         .addDisposableTo(disposeBag)
   }
}

Vergessen Sie nicht, dass wir auch sicherstellen müssen, dass unser ViewModel weiß, was der Benutzer in das Textfeld eingegeben hat! Dazu senden wir den Wert von nameTextField bei jeder Änderung an die Eigenschaft searchText des ViewModel. Also fügen wir dies einfach in die viewDidLoad () -Methode ein:

nameTextField.rx_text.subscribeNext { text in
   self.viewModel.searchText.onNext(text)
   }
   .addDisposableTo(disposeBag)

Bild

So! Jetzt empfängt die Anwendung Wetterdaten, während der Benutzer den Namen der Stadt eingibt, und unabhängig davon, was der Benutzer sieht, bleibt der wahre Status der Anwendung verborgen.

Wenn Sie an einer erweiterten Version dieser App interessiert sind, schauen Sie sich meine Beispielwetter-App auf Github an .

Jetzt auch beliebt: