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.