Zarządzanie pamięcią Obj-C

Zarządzanie pamięcią jest jednym z najważniejszych procesów w każdym języku programowania. Jest to proces, w którym pamięć obiektów jest przydzielana, gdy są wymagane, i zwalniana, gdy nie są już potrzebne.

Zarządzanie pamięcią obiektów to kwestia wydajności; jeśli aplikacja nie zwalnia niepotrzebnych obiektów, jej zużycie pamięci rośnie i spada wydajność.

Techniki zarządzania pamięcią typu Objective-C można ogólnie podzielić na dwa typy.

  • „Manual Retain-Release” lub MRR
  • „Automatyczne liczenie referencji” lub ARC

„Manual Retain-Release” lub MRR

W MRR jawnie zarządzamy pamięcią, samodzielnie śledząc obiekty. Jest to realizowane przy użyciu modelu zwanego zliczaniem odwołań, który zapewnia klasa Foundation NSObject w połączeniu ze środowiskiem wykonawczym.

Jedyna różnica między MRR i ARC polega na tym, że zatrzymanie i zwolnienie jest obsługiwane przez nas ręcznie w pierwszym przypadku, podczas gdy w drugim przypadku jest to obsługiwane automatycznie.

Poniższy rysunek przedstawia przykład działania zarządzania pamięcią w Objective-C.

Cykl życia pamięci obiektu klasy A pokazano na powyższym rysunku. Jak widać, licznik zachowań jest pokazany poniżej obiektu, gdy licznik zachowań obiektu osiąga wartość 0, obiekt jest całkowicie zwalniany, a jego pamięć jest zwalniana do użycia przez inne obiekty.

Klasa Obiekt jest najpierw tworzony przy użyciu metody przydzielania / inicjowania dostępnej w NSObject. Teraz liczba zachowań wynosi 1.

Teraz klasa B zachowuje obiekt klasy A, a liczba zachowań obiektu klasy A wynosi 2.

Następnie klasa C tworzy kopię obiektu. Teraz jest tworzony jako kolejna instancja klasy A z takimi samymi wartościami dla zmiennych instancji. Tutaj liczba zachowań wynosi 1, a nie liczba zachowań oryginalnego obiektu. Przedstawia to przerywana linia na rysunku.

Skopiowany obiekt jest zwalniany przez klasę C przy użyciu metody Release, a licznik przechowywania wynosi 0, a zatem obiekt jest niszczony.

W przypadku początkowego obiektu klasy A licznik zatrzymania wynosi 2 i należy go dwukrotnie zwolnić, aby został zniszczony. Odbywa się to poprzez oświadczenia o dopuszczeniu klasy A i klasy B, które zmniejszają liczbę zatrzymań odpowiednio do 1 i 0. Ostatecznie obiekt zostaje zniszczony.

Podstawowe zasady MRR

  • Jesteśmy właścicielami każdego utworzonego przez nas obiektu: tworzymy obiekt przy użyciu metody, której nazwa zaczyna się od „przydziel”, „nowy”, „kopiuj” lub „mutableCopy”

  • Możemy przejąć na własność obiekt używając retain: otrzymany obiekt ma zwykle gwarancję, że pozostanie ważny w metodzie, w której został odebrany, a metoda ta może również bezpiecznie zwrócić obiekt do jego wywołującego. Używamy retain w dwóch sytuacjach -

    • W implementacji metody akcesora lub metody init, aby przejąć na własność obiekt, który chcemy przechowywać jako wartość właściwości.

    • Aby zapobiec unieważnieniu obiektu jako efekt uboczny innej operacji.

  • Kiedy już go nie potrzebujemy, musimy zrzec się prawa własności do obiektu, który posiadamy: Zrzekamy się prawa własności do obiektu, wysyłając mu komunikat o zwolnieniu lub wiadomość o automatycznym zwolnieniu. W terminologii Cocoa zrzeczenie się własności obiektu jest zatem zwykle określane jako „zwolnienie” obiektu.

  • Nie możesz zrzec się prawa własności do obiektu, którego nie jesteś właścicielem: jest to po prostu konsekwencja wcześniejszych zasad polityki określonych wyraźnie.

#import <Foundation/Foundation.h>

@interface SampleClass:NSObject
- (void)sampleMethod;
@end

@implementation SampleClass
- (void)sampleMethod {
   NSLog(@"Hello, World! \n");
}

- (void)dealloc  {
  NSLog(@"Object deallocated");
  [super dealloc];
}

@end

int main() {
   
   /* my first program in Objective-C */
   SampleClass *sampleClass = [[SampleClass alloc]init];
   [sampleClass sampleMethod];
   
   NSLog(@"Retain Count after initial allocation: %d", 
   [sampleClass retainCount]);
   [sampleClass retain];
   
   NSLog(@"Retain Count after retain: %d", [sampleClass retainCount]);
   [sampleClass release];
   NSLog(@"Retain Count after release: %d", [sampleClass retainCount]);
   [sampleClass release];
   NSLog(@"SampleClass dealloc will be called before this");
   
   // Should set the object to nil
   sampleClass = nil;
   return 0;
}

Kiedy kompilujemy powyższy program, otrzymamy następujące dane wyjściowe.

2013-09-28 04:39:52.310 demo[8385] Hello, World!
2013-09-28 04:39:52.311 demo[8385] Retain Count after initial allocation: 1
2013-09-28 04:39:52.311 demo[8385] Retain Count after retain: 2
2013-09-28 04:39:52.311 demo[8385] Retain Count after release: 1
2013-09-28 04:39:52.311 demo[8385] Object deallocated
2013-09-28 04:39:52.311 demo[8385] SampleClass dealloc will be called before this

„Automatyczne liczenie referencji” lub ARC

W automatycznym zliczaniu referencji lub ARC system używa tego samego systemu zliczania referencji, co MRR, ale wstawia wywołania odpowiedniej metody zarządzania pamięcią w czasie kompilacji. Gorąco zachęcamy do korzystania z ARC przy nowych projektach. Jeśli używamy ARC, zwykle nie ma potrzeby rozumienia podstawowej implementacji opisanej w tym dokumencie, chociaż w niektórych sytuacjach może to być pomocne. Aby uzyskać więcej informacji na temat ARC, zobacz Przejście do informacji o wersji ARC.

Jak wspomniano powyżej, w ARC nie musimy dodawać metod zwalniania i zachowywania, ponieważ zajmie się tym kompilator. W rzeczywistości podstawowy proces Celu C jest nadal taki sam. Wykorzystuje wewnętrznie operacje zachowywania i zwalniania, ułatwiając programistę kodowanie bez martwienia się o te operacje, co zmniejszy zarówno ilość napisanego kodu, jak i możliwość wycieków pamięci.

Istniała inna zasada zwana garbage collection, która jest używana w Mac OS-X wraz z MRR, ale od czasu jej wycofania w OS-X Mountain Lion nie była omawiana razem z MRR. Ponadto obiekty iOS nigdy nie miały funkcji czyszczenia pamięci. A w przypadku ARC nie ma również zastosowania czyszczenia pamięci w OS-X.

Oto prosty przykład ARC. Zauważ, że to nie zadziała na kompilatorze online, ponieważ nie obsługuje ARC.

#import <Foundation/Foundation.h>

@interface SampleClass:NSObject
- (void)sampleMethod;
@end

@implementation SampleClass
- (void)sampleMethod {
   NSLog(@"Hello, World! \n");
}

- (void)dealloc  {
  NSLog(@"Object deallocated");
}

@end

int main() {
   /* my first program in Objective-C */
   @autoreleasepool {
      SampleClass *sampleClass = [[SampleClass alloc]init];
      [sampleClass sampleMethod];
      sampleClass = nil;
   }
   return 0;
}

Kiedy kompilujemy powyższy program, otrzymamy następujące dane wyjściowe.

2013-09-28 04:45:47.310 demo[8385] Hello, World!
2013-09-28 04:45:47.311 demo[8385] Object deallocated