Jak to zarządzanie pamiecią w Objective-C może zrobić psikusa?

Ostatnio pisząc aplikację na iPhona złapałem się na pułapkę początkującego (aż wstyd). Mam tu na myśli zarządzanie pamięcią w języku Objective-C. Pokażę dziś studium przypadku, jak to łatwo się złapać oraz pokażę objawy.

Wszystko rozpoczęło się od tego, że jakiś czas temu stworzyłem klasę będącą modelem danych (później nie przyglądałem się jej specjalnie). Stworzyłem jej property pole:

@interface Model : NSObject {
    NSString *pole;
}
@property (nonatomic, assign) *pole;
@end

Następnie stworzyłem kontroler, w którym wykorzystywałem sobie model:

@implementation KontrolerView

@synthesize model;

- (id)init
{
    [self oblicz];
    return self;
}

- (NSString *)oblicz
{
     /*
      * Operacje na modelu
     */
     return model.pole = [[NSArray alloc] initWithString:@"100 metrów kwadratowych"];
}

- (IBAction)obliczPonownie
{
    model.pole = [NSArray stringWithFormat:@"200 metrow + %@", model.pole];
    NSLog(model.pole);
}
@end

Powyższe klasy są pewnym uproszczoniem. Nadmienić muszę, że metoda obliczPonownie wywoływana była po jakimś zdarzeniu w interfejsie.

Co się okazuję?

W tak prostym kodzie można popełnić błąd, który nie dosyć spowoduje błąd Bad access, to może powodować dziwne zachowanie, trudne do zidentyfikowania. Jako, że nie podejrzewałem modelu błędu szukałem w kontrolerze. W czasie debugowania okazało się, że w metodzie obliczPonownie wartość model.pole nie jest taka jaką bym oczekiwał:/ Pole miało wartość invalid lub przypadkową. Po długich poszukiwaniach przyjrzałem się modelowi.

Przyczyna

Problemem była ta linijka kodu:

@property (nonatomic, assign) *pole;

a dokładnie atrybut assign, który nie zapobiega wyczyszczeniu zmiennej. Dlatego pamiętaj, że jeżeli nie chcesz zezwolić na zwolnienie pamięci użyj atrybutu retain:

@property (nonatomic, retain) *pole;

Atrybut ten zapobiega uwalnianiu zmiennej w niewłaściwym momencie, ale nakłada na nas obowiązek jej uwolnienia ręcznie, gdy jej nie potrzebujemy. Możemy wykorzystać do tego metodę dealloc.

Uwagi

Warte tu nadmienić także by pilnować się, żeby obiekty alokować, a później je inicjalizować. Tak jak tutaj:

   model.pole = [[NSString alloc] initWithString:@"100 metrów kwadratowych"];

Inaczej możemy się spotkać z podobnym zachowaniem jak wyżej. Obiekt może być nie widoczny poza zakresem gdzie został stworzony.

Ostatnią wartą przypomnienia rzeczą jest by dla obiektów takich jak tablice czy słowniki, które chcemy modyfikować wybierać klasy z dopiskiem Mutable np:

  1. NSArray -> NSMutableArray
  2. NSDictionary -> NSMutableDictionary

Niestety pisząc programy w Objective-C trzeba dość dokładnie pilnować tego typu szczegółów, by nie spędzać zbyt dużo czasu na debugowaniu.

3 myśli nt. „Jak to zarządzanie pamiecią w Objective-C może zrobić psikusa?

  1. Paweł Sołyga

    Od kiedy to Garbage Collector jest obsługiwany w iPhone OS ? Z tego co mi wiadomo to jest póki co obsługiwany tylko w Mac OS X 10.5.

    Drugie pytanie, dlaczego używasz NSArray a nie NSString w powyższym przykładzie ? Trochę dziwnie wygląda wysłanie initWithString do NSArray.

  2. sparhawk Autor wpisu

    „Od kiedy to Garbage Collector jest obsługiwany w iPhone OS ? Z tego co mi wiadomo to jest póki co obsługiwany tylko w Mac OS X 10.5.”

    Fakt. Zmieniłem tytuł posta na zarządzanie pamięcią. Ogólnie to zarządzanie pamięcią sprawia mi sporo trudności. A w początkowych dokumentach nie ma tego wyraźnie wskazanego. Dzięki wielkie.

    Co do:
    „Drugie pytanie, dlaczego używasz NSArray a nie NSString w powyższym przykładzie ? Trochę dziwnie wygląda wysłanie initWithString do NSArray.”
    Nie to mi się napisało, na szczęście chodzi tylko o idea ;)

  3. plusz.pl

    W dokumentacji Objective-C, w jednym z podstawowych dokumentów o tworzeniu obiektów i atrybutów jest wyraźnie napisane, że w przypadku przypisywania wartości atrybuotowi należy dodać retain.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *