2015-01-26 14 views
6

Wiele klas dotykowych Cocoa Touch wykorzystuje wzorzec współwystępowania zdarzeń. UIViews, na przykład, mają metodę setNeedsLayout, która powoduje, że layoutSubviews zostanie wywołana w najbliższej przyszłości. Jest to szczególnie przydatne w sytuacjach, w których wiele właściwości wpływa na układ. W ustawieniach dla każdej właściwości można wywołać [self setNeedsLayout], która zapewni aktualizację układu, ale zapobiegnie wielu (potencjalnie drogim) aktualizacjom układu, jeśli wiele właściwości zostanie zmienionych jednocześnie lub nawet jeśli pojedyncza właściwość została zmodyfikowana wiele razy w ciągu jednego iteracja pętli uruchamiania. Inne drogie operacje, takie jak para metod setNeedsDisplay i drawRect:, są zgodne z tym samym wzorcem.Zaimplementuj wzorzec rzucony/scalony w kakao Touch podobnie jak "layoutSubviews"

Jaki jest najlepszy sposób na wdrożenie takiego wzoru? W szczególności chciałbym powiązać wiele zależnych właściwości z kosztowną metodą, która musi zostać wywołana po zakończeniu iteracji pętli, jeśli właściwość uległa zmianie.


Możliwe rozwiązania:

Korzystanie z CADisplayLink lub NSTimer można dostać coś pracuje w ten sposób, ale oba wydają się bardziej zaangażowani, niż to konieczne i nie jestem implikacje wydajności dodając do tego pewności, co byłoby wiele obiektów (szczególnie timerów). W końcu wydajność jest jedynym powodem, aby zrobić coś takiego.

Użyłem coś takiego w niektórych przypadkach:

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

Działa to doskonale w sytuacjach, w których wejście użytkownik powinien wywołać tylko pewne zdarzenie, kiedy ciągłe działanie, czy takie rzeczy. Wydaje się, że przyziemne, gdy chcemy zapewnić, że nie ma opóźnienia w uruchomieniu zdarzenia, zamiast tego chcemy po prostu złączyć połączenia w ramach tej samej pętli uruchamiania.

+0

Jeśli jesteś zainteresowany podjęciem akcji dokładnie w pętli, myślę, że chcesz [obserwatora pętli] (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ Wielowątkowość/RunLoopZarządzanie/RunLoopManagement.html # // apple_ref/doc/uid/10000057i-CH16-SW22). Aby uzyskać szybki przykład, zobacz [Wybór selektora na początku/końcu pętli uruchamiania] (http://stackoverflow.com/q/16789342). –

Odpowiedz

3

NSNotificationQueue ma dokładnie to, czego szukasz. Zobacz dokumentację na Coalescing Notifications

tu prosty przykład w 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; 
} 

Można kasy docs i grać z postingStyle aby uzyskać zachowanie Ci życzenia. Korzystanie NSPostASAP, w tym przykładzie, da nam wynik:

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

co oznacza, że ​​back-to-back wywołania setNeedsReload zostały coalesced.

+0

Kakao jest niesamowite i ogromne. Nigdy bym tam nie spojrzał. Nie zwariowałem na temat dodanej składni niezbędnej do rejestrowania/wyrejestrowywania powiadomień lub używania 'NSNotificationCenter' w ogóle, ale jest to pierwsza odpowiedź na faktyczne interakcje z pętlą uruchamiania w sensowny sposób, a nie tylko opóźnianie wiadomości i nadzieja na Najlepiej. –

+0

Prawdopodobnie chcesz uniknąć 'NSNotificationQueue'. https://www.mikeash.com/pyblog/friday-qa-2010-01-08-nsnotificationqueue.html#comment-a463a9228074bd16e6b8c95c376b7993. Zasadniczo wydarzenia nie mogą być nigdy publikowane lub publikowane w odpowiednim czasie.W 2009 r. Opublikowano notatkę o wydaniu OS X, "Foundation i AppKit same nie używają NSNotificationQueue, częściowo z tych powodów." –

0

To graniczy z „głównie opinia opiera się”, ale dorzucę mój zwykły sposób postępowania to:

ustawić flagę, a następnie obróbkę z performSelector kolejce.

W swojej @interface umieścić:

@property (nonatomic, readonly) BOOL needsUpdate; 

a następnie umieścić w swojej @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 { 
} 

Podwójna kontrola _needsUpdate jest trochę zbędny, ale tanie. Prawdziwie paranoiści zawinęliby wszystkie istotne elementy w @synchronizowane, ale jest to naprawdę konieczne tylko wtedy, gdy można wywołać setNeedsUpdate z wątków innych niż główny wątek. Jeśli masz zamiar to zrobić, musisz również dokonać zmian w setNeedsUpdate, aby uzyskać dostęp do głównego wątku przed wywołaniem performSelector.

0

Rozumiem, że wywołanie performSelector:withObject:afterDelay: za pomocą wartości opóźnienia 0 powoduje, że metoda zostanie wywołana przy następnym przejściu przez pętlę zdarzeń.

Jeśli chcesz, aby twoje działania były ustawiane w kolejce do następnego przejścia przez pętlę zdarzeń, powinno to działać poprawnie.

Jeśli chcesz połączyć wiele różnych działań i chcesz tylko wykonać jedno "zrób wszystko, co zgromadziło się od ostatniego przejścia przez pętlę zdarzeń", możesz dodać pojedyncze połączenie do performSelector:withObject:afterDelay: w swoim delegacie aplikacji (lub innym obiekcie z pojedynczą instancją) przy uruchomieniu i ponownie wywołaj metodę na końcu każdego połączenia. Następnie można dodać NSMutableSet rzeczy do zrobienia i dodać wpis do zestawu za każdym razem, gdy uruchomisz działanie, które chcesz połączyć. Jeśli utworzyłeś niestandardowy obiekt akcji i przejąłeś metody isEqual (i hash) na obiekcie akcji, możesz go skonfigurować, aby w zestawie działań był tylko jeden obiekt akcji każdego typu. Dodanie tego samego typu akcji wielokrotnie podczas przejścia przez pętlę zdarzeń spowoduje dodanie jednej i tylko jednej akcji tego typu).

Twoja metoda może wyglądać tak:

- (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]; 
} 

Trudno dostać się do szczegółowych informacji na temat, jak to zrobić bez konkretnych szczegółów, co chcesz zrobić.

3

Zaimplementowałem coś takiego za pomocą niestandardowych źródeł wysyłki. Zasadniczo, można skonfigurować źródło wysyłki użyciem DISPATCH_SOURCE_TYPE_DATA_OR jako takie:

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); 

Po tym, za każdym razem, gdy chcesz powiadomić, że nadszedł czas, aby uaktualnić, zadzwonić:

dispatch_source_merge_data(__source, 1); 

obsługi zdarzenia blok nie jest -reentrant, więc aktualizacje, które występują podczas działania procedury obsługi zdarzeń, będą się łączyć.

To jest wzór, którego używam w mojej strukturze, Conche (https://github.com/djs-code/Conche). Jeśli szukasz innych przykładów, poke obejrzyj CNCHStateMachine.m i CNCHObjectFeed.m.

Powiązane problemy