2015-05-14 7 views
10

Ultimamente sto leggendo su ReactiveCocoa v3 e sto facendo fatica con l'impostazione di elementi di base. Ho già letto il changelog, i test, le poche domande su SO e gli articoli di Colin Eberhardt sull'argomento. Tuttavia, mi mancano ancora esempi di associazioni di base.Come creare binding di base in ReactiveCocoa 3 e 4

Diciamo che ho un'app che presenta il menu del giorno. L'app utilizza RAC3 e il pattern MVVM.

Model (Menu)

Il modello ha un metodo semplice per il recupero odierno menu. Per ora, questo non fa alcuna richiesta di rete, fondamentalmente crea solo un oggetto modello. La proprietà mainCourse è una String.

class func fetchTodaysMenu() -> SignalProducer<Menu, NoError> { 
    return SignalProducer { 
     sink, dispoable in 
      let newMenu = Menu() 
      newMenu.mainCourse = "Some meat" 
      sendNext(sink, newMenu) 
      sendCompleted(sink) 
    } 
} 

ViewModel (MenuViewModel)

Il modello vista espone diversi String variabili per lasciare che il controller della vista mostrano il menu. Aggiungiamo solo una proprietà per mostrare il corso principale.

var mainCourse = MutableProperty("") 

E aggiungiamo un binding per questo alloggio:

self.mainCourse <~ Menu.fetchTodaysMenu() 
    |> map { menu in 
     return menu.mainCourse! 
    } 

ViewController (MenuViewController)

Ultimo ma non meno importante, voglio presentare questo corso principale di una vista. Aggiungerò un UILabel per questo.

var headline = UILabel() 

E infine voglio impostare la proprietà text di quel UILabel osservando mio punto di vista del modello. Qualcosa di simile :

self.headline.text <~ viewModel.headline.producer 

che purtroppo non funziona.

Domande

  1. Il metodo restituisce un fetchTodaysMenu()SignalProducer<Menu, NoError>, ma cosa succede se voglio questo metodo per restituire un SignalProducer<Menu, NSError> invece? Ciò renderebbe impossibile il binding nel modello di visualizzazione, in quanto il metodo ora potrebbe restituire un errore. Come gestisco questo?
  2. Come già detto, l'attuale associazione nel mio controller di visualizzazione non funziona. Ho giocato con la creazione di uno MutableProperty che rappresenta la proprietà text dello UILabel, ma non ho mai capito bene. Penso anche che sia goffo o verbale dover creare variabili extra per ogni proprietà che voglio legare. Questo non era necessario in RAC2. Ho anche cercato intenzionalmente di evitare l'uso di DynamicProperty, ma forse non dovrei? Fondamentalmente voglio solo trovare il modo giusto di fare RAC(self.headline, text) = RACObserve(self.viewModel, mainCourse);.

Qualsiasi altro feedback/guida su come eseguire questa configurazione di base è molto apprezzato.

+5

Potresti essere interessato a leggere questo articolo: http://nomothetis.svbtle.com/an-introduction-to-reactivecocoa – mustafa

+0

In effetti lo sono. Grazie @mustafa! –

risposta

13

Quindi, dopo aver scritto questa domanda, Colin Eberhardt ha realizzato una parte 3 della sua serie di post sul blog RAC3 che include un esempio interessante e molto pertinente dell'utilizzo di MVVM e RAC3. Il post può essere trovato here e il codice sorgente here.

basato sul suo lavoro, sono riuscito a rispondere alle mie domande:

  1. Adottando un approccio leggermente diverso, sono in grado di rendere il fetchTodaysMenu() ritorno un SignalProducer<Menu, NSError> come voleva. Ecco come quello che poi avrei fatto nel mio modello di vista:

    MenuService.fetchTodaysMenu() 
        |> observeOn(QueueScheduler.mainQueueScheduler) 
        |> start(next: { response in 
         self.mainCourse.put(response.mainCourse!) 
        }, error: { 
         println("Error \($0)") 
        }) 
    
  2. Sembra che ci sia ancora UIKit binding al RAC3 beta 4. Colin ha fatto alcuni UIKit estensioni lui per aiutarlo a rendere questi attacchi che cercavo anche. Questi possono essere trovati here. aggiungendoli al mio progetto, ha fatto essere in grado di fare esattamente quello che volevo:

    self.mainCourse.rac_text <~ self.viewModel.mainCourse 
    

aggiornamento 25 Maggio 2015

Dopo lavorato molto di più con ReactiveCocoa 3, mi sarebbe piace rispondere 1) ancora una volta. Usando catch, è possibile farlo in modo più dichiarativo. Ho finito per l'attuazione di una piccola funzione di supporto per questo:

public func ignoreError<T: Any, E: ErrorType>(signalProducer: SignalProducer<T, E>) -> SignalProducer<T, NoError> { 
    return signalProducer 
     |> catch { _ in 
      SignalProducer<T, NoError>.empty 
     } 
} 

La funzione trasforma qualsiasi NSError-NoError rendendo possibile legare come volevo facendo MenuService.fetchTodaysMenu() |> ignoreError.

ho open source il mio progetto come questo potrebbe essere un buon punto di partenza per altri che cercano in ReactiveCocoa 3.0: https://github.com/s0mmer/TodaysReactiveMenu

aggiornamento 5 Marzo 2016

Come evidenziato nei commenti, in quanto Swift 2 , la funzione ignoreError sarebbe ora simile:

public func ignoreError() -> SignalProducer<Value, NoError> { 
    return flatMapError { _ in 
     SignalProducer<Value, NoError>.empty 
    } 
} 

Inoltre, una biblioteca di estensione chiamatoAnche ilè stato realizzato, dove qualcosa di simile è stato aggiunto.

+2

Con Swift 2, dovrai usare 'flatMapError' invece di' catch' per evitare conflitti con 'catch' di Swift 2. – user2067021