2012-01-12 5 views
40

Co ludzie mają na myśli, gdy mówią, że to jest drogie? Tworzę instancje wielu obiektów przejściowych tylko do przechowywania pośredniego (NSString i NSDate są typowymi). Skąd mam wiedzieć, czy moje używanie programu NSDateFormatter jest przesadzone?Dlaczego alokowanie lub inicjowanie NSDateFormatter jest uważane za "kosztowne"?

Do tej pory miałem tendencję do tworzenia tego, co jest równe singletonowi, ale moją preferencją byłoby hermetyzowanie go do niektórych innych obiektów, z którymi jest on związany, aby móc używać własnych odniesień.

Z powodu braku testów wydajności, szukam lepszego "praktycznego" zrozumienia, dlaczego powinienem lub nie powinienem tego robić.

+0

profilowanie jest bezbolesny – justin

+0

Można profil swoją aplikację, aby określić, w jaki sposób lepiej odpowiada Twoim potrzebom. Sądzę, że programiści chcieli zwrócić uwagę, że coś tak nieszkodliwego brzmiącego jak "NSDateFormatter" może mieć dość skomplikowany kod inicjalizatora, więc oto podpowiedź w dokumentacji, aby jak najwięcej użyć zainicjowanego obiektu. –

Odpowiedz

33

Kiedy coś takiego nazywa się drogim, nie musi to oznaczać, że nigdy nie powinno się tego robić, oznacza to po prostu unikanie tego w sytuacjach, w których trzeba jak najszybciej wyjść z metody. Na przykład, kiedy iPhone 3G był najnowszym urządzeniem, pisałem aplikację z UITableView, która formatowała liczby do wyświetlenia w każdej komórce (mogę dodać, że to było z powrotem, gdy byłem początkującym w rozwoju iOS). Moja pierwsza próba była następująca:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 

    NSString *reuseIdentifier = @"cell"; 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath]; 

    MyManagedObject *managedObject = [self.managedObjects objectAtIndex:indexPath.row]; 
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; 
    [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; 

    [cell.textLabel setText:[managedObject title]]; 
    [cell.detailTextLabel setText:[numberFormatter stringFromNumber:[managedObject amount]]]; 

    return cell; 
} 

Przewijanie wydajność tego kodu był straszne. Szybkość klatek spadła do około 15 klatek na sekundę, ponieważ przydzielałem nowe NSNumberFormatter za każdym razem, gdy zostało uderzone tableView:cellForRowAtIndexPath:.

Naprawiłem go przez zmianę kodu do tego:

- (NSNumberFormatter *)numberFormatter { 

    if (_numberFormatter != nil) { 
     return _numberFormatter; 
    } 

    _numberFormatter = [[NSNumberFormatter alloc] init]; 
    [_numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; 

    return _numberFormatter; 
} 

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 

    NSString *reuseIdentifier = @"cell"; 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath]; 

    MyManagedObject *managedObject = [self.managedObjects objectAtIndex:indexPath.row]; 
    NSNumberFormatter *numberFormatter = [self numberFormatter]; 

    [cell.textLabel setText:[managedObject title]]; 
    [cell.detailTextLabel setText:[numberFormatter stringFromNumber:[managedObject amount]]]; 

    return cell; 
} 

Różnica polega na tym, że ja leniwie załadował NSNumberFormatter się z ivar, tak, że każdy bieg tableView:cellForRowAtIndexPath: nie przydziela nową instancję. Ta prosta zmiana przesunęła wydajność przewijania z powrotem do około 60 FPS.

Ten konkretny przykład nie jest już tak istotny, ponieważ nowsze układy są w stanie obsłużyć przydział bez wpływu na wydajność przewijania, ale zawsze lepiej być tak wydajnym, jak to tylko możliwe.

+1

Niebezpieczeństwo polega na tym, że musisz upewnić się, że twoja liczba nie jest wywoływana z więcej niż jednego wątku. Nie obsługuje współbieżności. –

+1

@GregMaletic jest to bardzo prosty przykład: '' 'tableView: cellForRowAtIndexPath:' '' 'nigdy nie zostanie wywołany z niczego poza głównym wątkiem. –

+0

@ Eli Prawda, ale jeśli twoja metoda numberFormatter jest wywoływana w innej metodzie w twoim kodzie, a ta metoda została wywołana w innym wątku, możesz się zepsuć. Musisz tylko pamiętać, aby wywołać metodę numberFormatter tylko z głównego wątku. –

7

Miałem to samo pytanie kiedyś. Pracowałem z Instruments nad aplikacją, z której pracowałem, i domyślam się, że poprzedni programiści tworzyli nowy NSDateFormatter dla każdego zrobionego dziennika niestandardowego. Ponieważ dla każdego ekranu używano do logowania około 3 linii. Aplikacja poświęcała około jednej sekundy na tworzenie tylko NSDateFormatters.

Najprostszym rozwiązaniem byłoby zachowanie instancji formatera daty w klasie jako atrybutu lub czegoś i ponowne użycie jej dla każdej linii dziennika.

Po pewnym trywialnym myśleniu przyjechałem z "fabryką", aby obsłużyć ponowne użycie NSDateFormatters w oparciu o wymagany format i lokalizację. Proszę o formatter daty o jakimś formacie i locale, a moja klasa daje mi już załadowany formatator. Dobre dostrajanie wydajności, powinieneś spróbować.

PS: Może ktoś chciałby go przetestować, więc zrobiłem to public: https://github.com/DougFischer/DFDateFormatterFactory/blob/master/README.md

+1

Czy otrzymałeś jakąś opinię? – Dejell

+1

Informacja zwrotna: działa jak czar – Laszlo

+1

Zgadzam się. Łatwy i wygodny. Moja obecna aplikacja ma wiele korzyści z jej używania. Wielkie dzięki – rmvz3