2015-05-01 9 views
6

Utilizzo una GPUImage e molte istanze GPUImageView. Lo scopo è quello di visualizzare l'immagine originale, sovrapporre più sezioni di immagini filtrate e infine animare lentamente i filtri di sezione sull'immagine originale. Immagina un'immagine con alcune barre seppia che si muovono per mostrare l'immagine normale e l'immagine seppia in sezioni.GPUImage e GPUImageView: l'app è stata chiusa a causa di un errore di memoria

ho avvolto questa funzionalità in una sottoclasse di UIView come mostrato di seguito:

import Foundation 
import QuartzCore 

class FilteredImageMaskView : UIView { 

init(frame: CGRect, image: UIImage){ 
    super.init(frame: frame); 

    let imageViewFrame = CGRectMake(frame.origin.x, 0.0, frame.size.width, frame.size.height); 

    let origImage = GPUImagePicture(image: image); 
    origImage.forceProcessingAtSizeRespectingAspectRatio(imageViewFrame.size); 

    // Display the original image without a filter 
    let imageView = GPUImageView(frame: imageViewFrame); 
    origImage.addTarget(imageView); 
    origImage.processImageWithCompletionHandler(){ 
     origImage.removeAllTargets(); 

     var contentMode = UIViewContentMode.ScaleAspectFit; 
     imageView.contentMode = contentMode; 

     // Width of the unfiltered region 
     let regularWidth: CGFloat = 30.0; 
     // Width of filtered region 
     let filterWidth: CGFloat = 30.0; 

     // How much we are moving each bar 
     let totalXMovement = (regularWidth + filterWidth) * 2; 

     // The start X position 
     var currentXForFilter: CGFloat = -totalXMovement; 

     // The filter being applied to an image 
     let filter = GPUImageSepiaFilter(); 
     filter.intensity = 0.5; 
     // Add the filter to the originalImage 
     origImage.addTarget(filter); 

     let filteredViewCollection = FilteredViewCollection(filteredViews: [GPUImageView]()); 

     // Iterate over the X positions until the whole image is covered 
     while(currentXForFilter < imageView.frame.width + totalXMovement){ 
      let frame = CGRectMake(currentXForFilter, imageViewFrame.origin.y, imageViewFrame.width, imageViewFrame.height); 
      var filteredView = GPUImageView(frame: frame); 
      filteredView.clipsToBounds = true; 
      filteredView.layer.contentsGravity = kCAGravityTopLeft; 

      // This is the slice of the overall image that we are going to display as filtered 
      filteredView.layer.contentsRect = CGRectMake(currentXForFilter/imageViewFrame.width, 0.0, filterWidth/imageViewFrame.width, 1.0); 
      filteredView.fillMode = kGPUImageFillModePreserveAspectRatio; 

      filter.addTarget(filteredView); 

      // Add the filteredView to the super view 
      self.addSubview(filteredView); 

      // Add the filteredView to the collection so we can animate it later 
      filteredViewCollection.filteredViews.append(filteredView); 

      // Increment the X position   
      currentXForFilter += regularWidth + filterWidth; 
     } 

     origImage.processImageWithCompletionHandler(){ 
      filter.removeAllTargets(); 

      // Move to the UI thread 
      ThreadUtility.runOnMainThread(){ 
       // Add the unfiltered image 
       self.addSubview(imageView); 
       // And move it behind the filtered slices 
       self.sendSubviewToBack(imageView); 

       // Animate the slices slowly across the image 
       UIView.animateWithDuration(20.0, delay: 0.0, options: UIViewAnimationOptions.Repeat, animations: { [weak filteredViewCollection] in 
        if let strongfilteredViewCollection = filteredViewCollection { 
         if(strongfilteredViewCollection.filteredViews != nil){ 
          for(var i = 0; i < strongfilteredViewCollection.filteredViews.count; i++){ 
           strongfilteredViewCollection.filteredViews[i].frame.origin.x += totalXMovement; 
           strongfilteredViewCollection.filteredViews[i].layer.contentsRect.origin.x += (totalXMovement/imageView.frame.width); 
          } 
         } 
        } 
       }, completion: nil); 
      } 
     } 
    } 
} 

required init(coder aDecoder: NSCoder) { 
    super.init(coder: aDecoder); 
} 

} 

class FilteredViewCollection { 
    var filteredViews: [GPUImageView]! = [GPUImageView](); 

    init(filteredViews: [GPUImageView]!){ 
     self.filteredViews = filteredViews; 
    } 
} 

un'istanza di FilteredImageMaskView viene aggiunto programmazione per una vista in un viewController. Quando quella viewController viene chiusa, l'assunto è che le risorse verranno smaltite: sono stato attento a evitare i cicli di conservazione. Quando guardo il consumo di memoria nel debugger su un dispositivo reale, la memoria si abbassa in modo appropriato quando viene scaricato il viewController. Tuttavia, se caricherò ripetutamente quel viewController per guardare l'immagine, quindi lo spegni, quindi lo ricarico di nuovo, alla fine incontrerò "App terminata a causa di un errore di memoria"

Se attendo qualche istante dopo aver chiuso ViewController, il gli errori di memoria sembrano meno frequenti, il che mi porta a credere che la memoria sia ancora rilasciata dopo che la viewController è stata chiusa ...? Ma ho anche visto l'errore dopo solo un paio di volte di apertura e chiusura non così rapida del viewController.

Devo utilizzare GPUImage e/o GPUImageView in modo inefficiente e sto cercando indicazioni.

Grazie!

MODIFICA: Vedere sotto per l'implementazione del controller di visualizzazione.

import UIKit 

class ViewImageViewController: UIViewController, FetchImageDelegate { 

    var imageManager = ImageManager(); 

    @IBOutlet var mainView: UIView! 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     imageManager.fetchImageAsync(delegate: self); 
    } 

    // This callback is dispatched on the UI thread 
    func imageFetchCompleted(imageData: [UInt8]) { 
     let imageView = FilteredImageMaskView(frame: self.mainView.frame, image: UIImage(data: imageData)); 
     mainView.addSubview(imageView); 

     var timer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(10.0), target: self, selector: Selector("displayReminder"), userInfo: nil, repeats: false); 
    } 

    func displayReminder(){ 
     // Show an alert or message here 
    } 

} 

class ImageManager { 

    func fetchImageAsync(delegate: FetchImageDelegate) { 
     // This dispatches a high priority background thread 
     ThreadUtility.runOnHighPriorityBackgroundThread() { [weak delegate] in 
      // Get the image (This part could take a while in the real implementation) 
      var imageData = [UInt8](); 

      // Move to the UI thread 
      ThreadUtility.runOnMainThread({ 
       if let strongDelegate = delegate { 
        strongDelegate.imageFetchCompleted(imageData); 
       } 
      }); 
     } 
    } 
} 

Ora che sto cercando attraverso questa versione ridotta, non passando self alla ImageManager creare un ciclo di trattenere anche se mi riferisco è weak mente al thread in background? Posso passarlo come riferimento debole direttamente dallo ViewImageViewController? È certamente possibile che ViewImageViewController venga eliminato prima del completamento del metodo fetchImageAsync e che venga richiamata la richiamata.

MODIFICA: Penso di aver trovato il problema. Se si guarda lo ViewImageViewController nella richiamata, creo un NSTimer e passo il self. Il mio sospetto è che si stia creando un ciclo di conservazione se viewController viene eliminato prima dell'esecuzione del timer. Questo spiegherebbe perché, se aspetto qualche secondo in più, non ottengo l'errore di memoria, perché il timer si attiva e il viewController dispone correttamente. Ecco la correzione (credo).

// This is on the ViewImageViewController 
var timer: NSTimer!; 

// Then instead of creating a new variable, assign the timer to the class variable 
self.timer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(10.0), target: self, selector: Selector("displayReminder"), userInfo: nil, repeats: false); 

// And finally, on dismiss of the viewcontroller (viewWillDisappear or back button click event, or both) 
func cancelTimer() { 
    if(self.timer != nil){ 
     self.timer.invalidate(); 
     self.timer = nil; 
    } 
} 
+0

Puoi fornire maggiori informazioni sul controller di visualizzazione? E 'in effetti stato deallocato?Se hai una perdita di memoria, il problema non sarebbe il codice che hai mostrato. – matt

+0

Certamente. Aggiornerò l'OP. – a432511

+0

@matt aggiunto per tua richiesta ... e potresti avere ragione ... vedi sopra – a432511

risposta

2

Penso di aver trovato il problema. Se si guarda lo ViewImageViewController nella richiamata, creo un NSTimer e passo il self. Il mio sospetto è che si stia creando un ciclo di conservazione se viewController viene eliminato prima dell'esecuzione del timer. Questo spiegherebbe perché, se aspetto qualche secondo in più, non ottengo l'errore di memoria, perché il timer si attiva e il viewController dispone correttamente. Ecco la correzione (credo).

// This is on the ViewImageViewController 
var timer: NSTimer!; 

// Then instead of creating a new variable, assign the timer to the class variable 
self.timer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(10.0), target: self, selector: Selector("displayReminder"), userInfo: nil, repeats: false); 

// And finally, on dismiss of the viewcontroller (viewWillDisappear or back button click event, or both) 
func cancelTimer() { 
    if(self.timer != nil){ 
     self.timer.invalidate(); 
     self.timer = nil; 
    } 
} 
+1

Molto possibile. Vedi la mia discussione sui timer e sui cicli di conservazione - inizia circa i 2/3 di questa sezione: http://www.apeth.com/iOSBook/ch12.html#_unusual_memory_management_situations Come cito i documenti, "L'oggetto di destinazione viene mantenuto dal timer e rilasciato quando il timer è invalidato. " Il mio esempio è un timer ripetuto; l'esempio di un timer a intervallo singolo a scatto singolo che non ha mai la possibilità di sparare non mi è venuto in mente! Mi prenderò un appunto per includerlo in tutte le future edizioni del libro. – matt

+0

@matt in effetti sembra che questo era il problema. Ora tutto funziona correttamente dopo aver implementato il codice sopra. A volte ci vuole un pettine a denti fini e un po 'di pazienza per trovare questi fastidiosi fermi :-D – a432511

+0

Bene, ci vuole guardare nel posto giusto. Stavi guardando nel posto sbagliato; la tua roba di GPUImage era una vera e propria falsa pista, e io ero quello che ti diceva di averti fatto e di averti cercato nel posto giusto. Ma te l'ho detto gratuitamente, così alla fine hai buttato via 100 punti del tuo rappresentante, senza scopo! – matt

0

Il FilteredImageMaskView è fortemente fatto riferimento nel blocco di processImageWithCompletionHandler, che è probabile che si formi un ciclo conservare. Prova a usare un sé debole invece nel blocco

+0

Gli darò un colpo! – a432511

+0

Questo non ha funzionato. Grazie per aver dato un colpo però. – a432511

Problemi correlati