2015-01-26 16 views
6

Un certo numero di classi Cocoa Touch sfruttano un modello di progettazione di eventi coalescenti. UIViews, ad esempio, ha un metodo setNeedsLayout che provoca la chiamata di layoutSubviews nel prossimo futuro. Ciò è particolarmente utile in situazioni in cui un numero di proprietà influenza il layout. Nel setter di ciascuna proprietà è possibile chiamare [self setNeedsLayout] che garantirà il layout verrà aggiornato, ma impedirà molti aggiornamenti (potenzialmente costosi) al layout se più proprietà vengono modificate contemporaneamente o anche se una singola proprietà è stata modificata più volte all'interno di una iterazione del ciclo di esecuzione. Altre costose operazioni come la coppia di metodi setNeedsDisplay e drawRect: seguono lo stesso schema.Implementare un pattern Debounced/Coalesced in Cocoa Touch come `layoutSubviews`

Qual è il modo migliore per implementare pattern come questo? In particolare vorrei collegare un numero di proprietà dipendenti a un metodo costoso che deve essere chiamato una volta per iterazione del ciclo di esecuzione se una proprietà è cambiata.


Possibili soluzioni:

Utilizzando un CADisplayLink o NSTimer si potrebbe ottenere qualcosa di lavoro come questo, ma entrambi sembrano più coinvolti di quanto necessario e non sono sicuro che cosa le implicazioni sulle prestazioni di aggiungere questo alla molti oggetti (specialmente i timer) sarebbero. Dopotutto, le prestazioni sono l'unica ragione per fare qualcosa del genere.

ho usato qualcosa di simile, in alcuni casi:

- (void)debounceSelector:(SEL)sel withDelay:(CGFloat)delay { 
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:sel object:nil]; 
    [self performSelector:sel withObject:nil afterDelay:delay]; 
} 

Questa grande opera in situazioni in cui un ingresso utente deve attivare solo alcuni eventi quando un'azione continua, o cose del genere. Sembra goffo quando vogliamo garantire che non vi sia alcun ritardo nell'innescare l'evento, ma vogliamo semplicemente effettuare chiamate in coalesce all'interno dello stesso ciclo di esecuzione.

+0

Se sei interessato ad agire in modo preciso per ciclo di esecuzione, penserei che tu voglia un [osservatore del ciclo di esecuzione] (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ multithreading/RunLoopManagement/RunLoopManagement.html # // apple_ref/doc/uid/10000057i-CH16-SW22). Per un breve esempio, vedere [Esecuzione selettore all'inizio/fine del ciclo di esecuzione] (http://stackoverflow.com/q/16789342). –

risposta

3

NSNotificationQueue ha proprio quello che stai cercando. Vedere la documentazione sul Coalescing Notifications

Ecco un semplice esempio in un UIViewController:

- (void)dealloc 
{ 
    [[NSNotificationCenter defaultCenter] removeObserver:self]; 
} 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    [[NSNotificationCenter defaultCenter] addObserver:self 
              selector:@selector(configureView:) 
               name:@"CoalescingNotificationName" 
               object:self]; 

    [self setNeedsReload:@"viewDidLoad1"]; 
    [self setNeedsReload:@"viewDidLoad2"]; 
} 

- (void)viewWillAppear:(BOOL)animated 
{ 
    [super viewWillAppear:animated]; 
    [self setNeedsReload:@"viewWillAppear1"]; 
    [self setNeedsReload:@"viewWillAppear2"]; 
} 

- (void)viewDidAppear:(BOOL)animated 
{ 
    [super viewDidAppear:animated]; 
    [self setNeedsReload:@"viewDidAppear1"]; 
    [self setNeedsReload:@"viewDidAppear2"]; 
} 

- (void)setNeedsReload:(NSString *)context 
{ 
    NSNotification *notification = [NSNotification notificationWithName:@"CoalescingNotificationName" 
                   object:self 
                   userInfo:@{@"context":context}]; 

    [[NSNotificationQueue defaultQueue] enqueueNotification:notification 
               postingStyle:NSPostASAP 
               coalesceMask:NSNotificationCoalescingOnName|NSNotificationCoalescingOnSender 
                forModes:nil]; 
} 

- (void)configureView:(NSNotification *)notification 
{ 
    NSString *text = [NSString stringWithFormat:@"configureView called: %@", notification.userInfo]; 
    NSLog(@"%@", text); 
    self.detailDescriptionLabel.text = text; 
} 

Puoi esaminare la documentazione e giocare con la postingStyle per ottenere il comportamento si desidera. Utilizzando NSPostASAP, in questo esempio, ci darà uscita:

configureView called: { 
    context = viewDidLoad1; 
} 
configureView called: { 
    context = viewDidAppear1; 
} 

il che significa che le chiamate back-to-back a setNeedsReload sono stati coalizzati.

+0

Il cacao è incredibile ed enorme. Non avrei mai guardato lì. Non mi fa impazzire la sintassi aggiunta necessaria per la registrazione/annullamento della registrazione delle notifiche o l'uso di 'NSNotificationCenter', ma questa è la prima risposta che interagisce effettivamente con il ciclo di esecuzione in modo significativo piuttosto che solo ritardare i messaggi e sperare che migliore. –

+0

Probabilmente si vuole evitare 'NSNotificationQueue'. https://www.mikeash.com/pyblog/friday-qa-2010-01-08-nsnotificationqueue.html#comment-a463a9228074bd16e6b8c95c376b7993. Fondamentalmente, gli eventi potrebbero non essere mai pubblicati o pubblicati in modo tempestivo.Una nota sulla versione di OS X del 2009 continua, "Foundation e AppKit non usano NSNotificationQueue da soli, in parte per questi motivi." –

0

Questa confina con "principalmente opinione based", ma mi butto il mio solito metodo di manipolazione di questo:

Impostare una bandiera e poi coda di elaborazione con performSelector.

Nel vostro @interface messo:

@property (nonatomic, readonly) BOOL needsUpdate; 

E poi nel mettere @implementation:

-(void)setNeedsUpdate { 
    if(!_needsUpdate) { 
     _needsUpdate = true; 
     [self performSelector:@selector(_performUpdate) withObject:nil afterDelay:0.0]; 
    } 
} 

-(void)_performUpdate { 
    if(_needsUpdate) { 
     _needsUpdate = false; 
     [self performUpdate]; 
    } 
} 

-(void)performUpdate { 
} 

Il doppio controllo di _needsUpdate è un po 'ridondante, ma a buon mercato. Il vero paranoico avvolgerebbe tutti i pezzi rilevanti in @synchronized, ma questo è veramente necessario solo se setNeedsUpdate può essere invocato da thread diversi dal thread principale. Se hai intenzione di farlo, devi anche apportare delle modifiche a setNeedsUpdate per ottenere il thread principale prima di chiamare performSelector.

0

È a mia conoscenza che chiamando lo performSelector:withObject:afterDelay: utilizzando un valore di ritardo pari a 0 il metodo viene chiamato al prossimo passaggio attraverso il ciclo degli eventi.

Se si desidera che le azioni vengano messe in coda fino al passaggio successivo nel ciclo degli eventi, dovrebbe funzionare correttamente.

Se si desidera riunire più azioni diverse e si desidera solo una chiamata "Esegui tutto ciò che è accumulato dall'ultimo passaggio attraverso il ciclo di eventi", è possibile aggiungere una singola chiamata a performSelector:withObject:afterDelay: nel delegato dell'app (o qualche altro oggetto di istanza singola) all'avvio e invoca nuovamente il metodo al termine di ogni chiamata. È quindi possibile aggiungere un NSMutableSet di cose da fare e aggiungere una voce al set ogni volta che si attiva un'azione che si desidera coalizzare. Se hai creato un oggetto azione personalizzato e hai eseguito l'override dei metodi isEqual (e hash) sul tuo oggetto azione, puoi impostarlo in modo che ci sia sempre un solo oggetto azione di ogni tipo nel tuo insieme di azioni. Aggiungere lo stesso tipo di azione più volte in un passaggio attraverso il ciclo degli eventi aggiungerebbe una sola azione di quel tipo).

tuo metodo potrebbe essere simile a questo:

- (void) doCoalescedActions; 
{ 
    for (CustomActionObject *aCustomAction in setOfActions) 
    { 
    //Do whatever it takes to handle coalesced actions 
    } 
    [setOfActions removeAllObjects]; 
    [self performSelector: @selector(doCoalescedActions) 
    withObject: nil 
    afterDelay: 0]; 
} 

E 'difficile entrare nei dettagli su come fare questo senza dettagli specifici di ciò che si vuole fare.

3

Ho implementato qualcosa di simile usando le fonti di spedizione personalizzate. In sostanza, si imposta una fonte di spedizione utilizzando DISPATCH_SOURCE_TYPE_DATA_OR come tale:

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, dispatch_get_main_queue()); 
dispatch_source_set_event_handler(source, ^{ 
    // UI update logic goes here 

}); 

dispatch_resume(source); 

Dopo di che, ogni volta che si desidera comunicare che è tempo di aggiornare, si chiama:

blocco
dispatch_source_merge_data(__source, 1); 

Il gestore di eventi è non -entrato, quindi gli aggiornamenti che si verificano mentre il gestore eventi è in esecuzione si coalizzeranno.

Questo è uno schema che uso un bel po 'nel mio framework, Conche (https://github.com/djs-code/Conche). Se stai cercando altri esempi, fai clic su CNCHStateMachine.m e CNCHObjectFeed.m.

Problemi correlati