2015-04-30 11 views
5

Sto costruendo un UISearchController in cui gli utenti digiteranno un nome utente e l'applicazione recupererà i risultati da un servizio web.UISearchResultsUpdating w/ReactiveCocoa

Desidero limitare le richieste per ridurre le chiamate di rete mentre l'utente sta digitando. Usando ReactiveCocoa come si dovrebbe implementare questo?

class SearchResultsUpdater: NSObject, UISearchResultsUpdating { 
    func updateSearchResultsForSearchController(searchController: UISearchController) { 
     let text = searchController.searchBar.text 
     let dataSource = searchResultsController.tableView.dataSource as! ...  
    } 
} 
+1

Hai guardato 'RACSignal' di metodo 'throttle'? –

+0

@ MichałCiuba è un metodo molto valido, dovresti fare una risposta. –

+0

Stai usando ReactiveCocoa 2? –

risposta

6

In realtà è molto buon approccio per risolvere questo problema con ReactiveCocoa.

Si desidera ottenere il testo mentre l'utente sta digitando o nella parola "Reattivo" che si desidera "Stream" del testo di input, Se l'utente è un veloce typer, si desidera eseguire la richiesta al server solo se il testo di ricerca è invariato per un breve periodo di tempo, puoi farlo da solo (usando Delegate, NSTimer), ma con ReactiveCocoa è davvero semplice e leggibile.

@property (weak, nonatomic) IBOutlet UISearchBar *searchBar; 

[[textSignal throttle:0.4]subscribeNext:^(NSString* searchText) { 
    [[SearchService sharedInstance]search:searchText completed:^(NSString *searchResult, NSError *error) { 
     NSLog(@"searchResult: %@",searchResult); 
    }]; 
}]; 

Supponiamo che la classe SearchService restituisca la ricerca e la lunghezza del testo di ricerca dopo 2,5 secondi.

@implementation SearchService 

typedef void(^CompletedResults)(NSString *searchResult, NSError *error); 

- (void)search:(NSString *)text completed:(CompletedResults)handler { 

    NSString *retVal = [NSString stringWithFormat:@"%@ = %@", text, @([text length])]; 

    // Here you should to do your network call and and return the response string 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
     [NSThread sleepForTimeInterval:2.5]; 
     if (handler){ 
      handler(retVal, nil); 
     } 
    }); 
} 

E con la sola riga di codice è possibile limitare il testo di input.

realtà ReactiveCocoa non prevede una categoria di UISearchBar, ma non è così complicato da implementare (È possibile trovare UISearchBar (RAC) categoria hire)

Cosa importante che si desidera porsi è, che cosa accadrà se hai già inviato la richiesta al server e prima di ottenere la risposta l'utente continua a digitare? Probabilmente si desidera annullare la richiesta precedente (e rilasciare tutte le risorse) e inviare una nuova richiesta al server con il nuovo testo di ricerca. di nuovo puoi farlo da solo ma con ReactiveCocoa è molto semplice, se inizi a pensare a cose come segnali.

È necessario avvolgere il servizio di ricerca che restituisce "flusso" di risultati dal server.

@implementation SearchService 

- (RACSignal *)search:(NSString *)text { 

    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 

     [self search:text completed:^(NSString *searchResult, NSError *error) { 
      [subscriber sendNext:searchResult]; 
      [subscriber sendCompleted]; 
     }]; 
     return nil; 
    }]; 
} 

Ora tutto ciò che dovete fare è quello di mappare ogni testo da ricercare per segnalare di conseguenza dal server e chiamare per switchToLatest.

[[[[textSignal throttle:0.4] 
map:^id(NSString* searchText) { 
    return [[SearchService sharedInstance]search:searchText]; 
}] 
switchToLatest] 
subscribeNext:^(NSString* searchResult) { 
    NSLog(@"searchResult: %@",searchResult); 
}]; 

E un'altra cosa, probabilmente quando si ottiene la risposta dal server che si desidera aggiornare l'interfaccia utente. E devi farlo sul thread principale. Anche qui con ReactiveCocoa è davvero semplice, basta aggiungere deliverOn: RACScheduler.mainThreadScheduler.

[[[[[textSignal throttle:0.4] 
map:^id(NSString* searchText) { 
    NSLog(@"Get Text after throttle"); 
    return [[SearchService sharedInstance]search:searchText]; 
}] 
switchToLatest] 
deliverOn:RACScheduler.mainThreadScheduler] 
subscribeNext:^(NSString* searchResult) { 

    if ([NSThread isMainThread]){ 
     NSLog(@"is MainThread"); 
    } 
    else{ 
     NSLog(@"is not MainThread"); 
    } 
    NSLog(@"searchResult: %@",searchResult); 
}]; 

Buona fortuna :)

Se si scrive il codice con Swift, dare un'occhiata a ReactiveSwift GitHub - estensioni reattivi per Swift, Ispirato da ReactiveCocoa

2

Mi dispiace non sono molto familiarità con l'API RAC Swift, ma questo è realizzabile nella versione Objective-C del RAC chiamando il metodo bufferWithTime:onScheduler: su un RACSignal, quindi farò senza dubbio avere una controparte Swift .

Esempio:

double sampleRate = 2.0; 
[[textField.rac_textSignal bufferWithTime:sampleRate onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(RACTuple * x) { 
    NSLog(@"%@", x.last); //Prints the latest string in the tuple. 
}]; 

Incorporando questo con UISearchController:

double sampleRate = 2.0; 
[[[self rac_signalForSelector:@selector(searchBar:textDidChange:) fromProtocol:@protocol(UISearchBarDelegate)] 
bufferWithTime:sampleRate onScheduler:[RACScheduler mainThreadScheduler]] 
subscribeNext:^id(RACTuple * x) { 
    NSLog(@"%@", x.last); 
}]; 

Here's un post del blog di dare un UISearchController un'opzione rac_textSignal, in modo che non c'è bisogno di implementare una funzione delegata te , mentre con il codice sopra, avrai ancora una funzione vuota searchBar:textDidChange: in SearchResultsUpdater.

+0

Come dovrei incorporare questo in un UISearchController? –

+0

Modificato per dare un'implementazione chiara. –

1

Questo potrebbe non essere quello che stai cercando, ma potrebbe aiutarti ad arrivarci. Ho usato l'estensione NSTimer in questo gist: https://gist.github.com/natecook1000/b0285b518576b22c4dc8

let (keySignal, keySink) = Signal<String, NoError>.pipe() 

func createIntervalSignal(interval: Double) -> Signal<(), NoError> { 
    return Signal { 
     sink in 
     NSTimer.schedule(repeatInterval: interval) { timer in 
      sendNext(sink,()) 
     } 
     return nil 
    } 
} 

func textFieldChanged(sender:UITextField) { 
    sendNext(keySink, sender.text) 
} 

let sendNetworkRequestSignal = keySignal |> sampleOn(createIntervalSignal(1.0)) 
let disposeThis = sendNetworkRequestSignal |> observe(next: { stringVal in }) //send requests in the closure