2015-09-11 18 views
6

Obserwowałem tylko to zachowanie na iOS 9; iOS 8 działa poprawnie.Dlaczego plik CIContext.createCGImage powoduje wyciek pamięci?

Podejrzewam, że to może być błąd w SDK i otworzyłem radar do Apple (22644754), ale uważam, że jest to tak dziwne, że mam wrażenie, że może brakować połączenia lub kroku do uniknij wycieku.

Zaobserwowałem, że za każdym razem, gdy wywoływany jest CIContext.createCGImage, wzrasta użycie pamięci. Trudne jest to, że wzrost pamięci występuje poza aplikacją.

Jeśli przyjrzeć się "Raportowi z pamięci" z Xcode, zwiększenie pamięci jest widoczne w sekcji "Inne procesy".

Zasadniczo, co zrobić, aby spowodować problem jest następujący (mam uproszczony kod ściśle elementy niezbędne do reprodukcji wyciek):

Najpierw tworzę CIContext poparte EAGLContext:

let glContext = EAGLContext(API: .OpenGLES2)! 
let ciContext = CIContext(EAGLContext: glContext, options: [kCIContextOutputColorSpace : NSNull()]) 

Potem renderowanie obrazu przy użyciu następujących:

let input = CIImage(image: UIImage(named: "DummyImage")!)! 
ciContext.createCGImage(input, fromRect: input.extent) 

DummyImage jest tylko przykładowy plik obrazu. Wyciek jest bezpośrednio związany z rozmiarem tego obrazu, więc najlepiej jest użyć dużego, aby problem był bardziej zauważalny.

Jak widać, nie używam żadnych CIFILterów (używanie ich powoduje taki sam wynik) i nie przechwytywam uzyskanego obrazu (nawet jeśli zrobiłem to, nie mogłem użyć CGImageRelease jako obiektów są automatycznie zarządzane).

Jeśli kod renderowania zostanie wykonany wystarczająco długo, pamięć będzie rosnąć tak bardzo, że uruchomione aplikacje zostaną zabite.

Jedną z interesujących obserwacji jest to, że niszczenie CIContekstu nie ma znaczenia, ale niszczenie EAGLContekstu powoduje zwrócenie pamięci. To sprawia, że ​​myślę, że wyciek ma miejsce po stronie OpenGL.

Czy brakuje mi kodu, który może spowodować wyciek? Czy jest jakaś rozmowa, którą mogę wykonać, aby zwolnić pamięć pobraną przez EAGLContext? (Odtwarzanie go przez cały czas nie jest opcją, ponieważ jest to kosztowna operacja).


Stworzyłem prosty projekt do odtworzenia problemu. Można go znaleźć pod adresem:

https://www.dropbox.com/s/zm19u8rmujv6jet/EAGLContextLeakDemo.zip?dl=0

kroki prowadzące do odtworzenia są:

  1. Otwórz i uruchomić załączony projekt na urządzeniu.
  2. Obserwuj ekran "Raport o pamięci" w Xcode. Wzrost będzie widoczny na wykresie kołowym "Inne procesy" na wykresie "Porównanie wykorzystania".
  3. Aplikacja zawiera trzy przyciski. Każdy z nich wykona komendę createCGImage określoną liczbę razy (pokazaną na etykietach przycisków).
  4. Naciśnięcie dowolnego przycisku spowoduje zwiększenie wykorzystania pamięci w "Inne procesy". Może to być bardziej zauważalne po wykonaniu kilku wywołań createCGImage.
  5. Stuknięcie na przyciskach 100 Rendry pokaże wyraźniejszy efekt.
  6. Gdy wzrost pamięci jest nadmierny, aplikacja ulegnie awarii.

Odpowiedz

0

Zostało to potwierdzone jako błąd na iOS 9. Problem został rozwiązany począwszy od iOS 9.1 beta 3.

Kod pisał w pierwotnym pytaniu jest poprawne. Nie trzeba wprowadzać żadnych modyfikacji, aby zapobiec wyciekowi wersji wcześniejszych niż iOS 9 lub wersji zaczynających się od iOS 9.1 Beta 3.

1

Sposób, w jaki rozwiązałem to renderowanie kontekstu do bufora, a następnie zapisywanie go do pliku jako JPG. Będę musiał dalej zobaczyć, jak zoptymalizować ten przepływ na starszych urządzeniach gen iOS, ale wydaje się, że działa dobrze w porównaniu do createCGImage. Kod ten jest również przydatny do przekształcania CIImage w JPEG lub bitmap NSData. Pełny kod przykładowy można zobaczyć tutaj:

https://github.com/mmackh/IPDFCameraViewController

static CIContext *ctx = nil; 

if (!ctx) 
{ 
    ctx = [CIContext contextWithOptions:@{kCIContextWorkingColorSpace:[NSNull null]}]; 
} 

CGSize bounds = enhancedImage.extent.size; 
int bytesPerPixel = 8; 
uint rowBytes = bytesPerPixel * bounds.width; 
uint totalBytes = rowBytes * bounds.height; 
uint8_t *byteBuffer = malloc(totalBytes); 

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 

[ctx render:enhancedImage toBitmap:byteBuffer rowBytes:rowBytes bounds:enhancedImage.extent format:kCIFormatRGBA8 colorSpace:colorSpace]; 

CGContextRef bitmapContext = CGBitmapContextCreate(byteBuffer,bounds.width,bounds.height,bytesPerPixel,rowBytes,colorSpace,kCGImageAlphaNoneSkipLast); 

CGImageRef imgRef = CGBitmapContextCreateImage(bitmapContext); 

CGColorSpaceRelease(colorSpace); 
CGContextRelease(bitmapContext); 
free(byteBuffer); 

if (imgRef == NULL) { goto release; } 


CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:filePath]; 
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypeJPEG, 1, NULL); 
CGImageDestinationAddImage(destination, imgRef, nil); 
CGImageDestinationFinalize(destination); 
CFRelease(destination); 

success = YES; 

goto release; 

release : 
{ 
    CFRelease(imgRef); 

    if (success) 
    { 
     //completionHandler(filePath); 
    } 

    dispatch_resume(_captureQueue); 
} 
+0

dlaczego 'bytesPerPixel = 8'? Czy format 'kCIFormatRGBA8' nie jest 4-bajtowy na piksel? –