2015-04-15 3 views
1

У меня проблема с iPad-приложением, в котором отображаются все изображения (все 1024x768), которые принимают 3Mo в ОЗУ, не освобождая их, когда они не нужны, что приведет к сбою через некоторое время.Память изображения не освобождается

Я загружаю изображения с использованием метода «contentOfFile», который должен (в моем понимании) освобождать память, когда память низкая, и приложение больше не нуждается в ней, но это, похоже, не так (даже когда i имитировать предупреждение памяти на симуляторе).

Я сделал действительно простой тестовый класс, чтобы проиллюстрировать проблему ниже (swift 1.2).

Когда приложение загружает его, создается представление, добавляются подслои, заполненные изображениями, а затем, когда я нажимаю на экран, я ожидаю, что 72Mo, сделанное несжатыми данными растровых изображений (используемыми Core Animation для визуализации слоя), будет освобождается (или, по крайней мере, освобождается, когда я имитирую предупреждение о памяти), чего он никогда не делает.

(Примечание: Я использую ARC как на мой основной проект и этот тестовый проект)

ViewController: UIViewController { 

    func tap(gesture : UITapGestureRecognizer) 
    { 
    if gesture.state == UIGestureRecognizerState.Ended { 
     var tmpV = self.view.viewWithTag(1000) 
     // tried everything to release memory 
     for subLayer : CALayer in tmpV?.layer.sublayers as! [CALayer!] { 
     subLayer.contents = nil 
     } 
     tmpV?.layer.sublayers.removeAll(keepCapacity: false) 
     tmpV?.removeFromSuperview() 
     tmpV = nil 
    } 
    } 

    override func viewDidAppear(animated: Bool) { 
    super.viewDidAppear(animated) 

    //add gesture to remove images from view 
    var gesture = UITapGestureRecognizer(target: self, action: Selector("tap:")) 
    self.view.addGestureRecognizer(gesture) 

    //loading images 
    var imagesArray : [UIImage!]! = self.fillImagesArray() 

    //adding those images to a view 
    let tmpV = UIView(frame: CGRect(x: 0, y: 0, width: 1024, height: 768)) 
    for img in imagesArray { 
     var layer = CALayer() 
     layer.frame = CGRect(x: 0, y: 0, width: 1024, height: 768) 
     layer.contents = img.CGImage 
     tmpV.layer.addSublayer(layer) 
    } 
    tmpV.tag = 1000 
    self.view.addSubview(tmpV)  
    } 

    func fillImagesArray() -> [UIImage!]{ 
    var array : [UIImage!] = [] 
    for i in 0 ... 23 { 
     var image : UIImage? 
     var filename = NSBundle.mainBundle().pathForResource("image \(i)", ofType: "png") 
     image = UIImage(contentsOfFile: filename!)  
     array.append(image) 
    } 
    return array 
    } 

    override func didReceiveMemoryWarning() { 
    super.didReceiveMemoryWarning() 
    } 
} 

EDIT:

Чтобы добавить еще несколько деталей, вот скриншот взят из профиля/Отчисления инструмента , так как я сказал, что догадываюсь, что память берется CA при рендеринге слоев.

Memory screenshot Когда изображения не отображаются на экране, память по-прежнему очень низкая, но после их отображения они не освобождаются даже после удаления.

Я попытался нарисовать слой tmpV, удалить подслои, а затем добавить обрамленное изображение в tmpV.layer.contents в viewDidAppear, и если я сделаю так, мое изображение будет правильным, а память останется низкой, но она будет стоить около 1,5 секунд для рисования слоя (мне нужно, чтобы мое приложение было быстрым, и я не могу позволить себе это время).

Единственный способ, которым я нашел освобождение памяти, заключался в том, чтобы добавить изображения в массив переменных класса и опустошить его на tap func. Но мне нужно будет снова загрузить изображения, если я захочу заново создать изображение, и я бы хотел этого избежать.

+0

Что заставляет вас думать: «загружать изображения с использованием метода contentOfFile», который должен (по моему мнению) выпускать память, когда память низкая, а приложение не нужно »? – zaph

+0

Просто угадайте, исходя из чтения ответов на разные сообщения SO, такие как: http://stackoverflow.com/questions/10494980/cant-release-unused-calayer-memory-when-using-multiple-layers http: // stackoverflow.com/questions/20588338/ios-uiimage-storage-formats-memory-usage-and-encoding-decoding – GawenBerger

ответ

1

Программист несет ответственность за все использование памяти, связанную с contentsOfFile. Если у вас есть сильная ссылка на объект изображения, скажем в массиве, он не будет удален из памяти, пока вы не удалите его из массива, и нет сильных ссылок на него.

Возможно, вы захотите использовать NSCache и связанный с ним NSDiscardableContent Протокол для управления изображениями.

+0

Поскольку в этом случае массив является локальной переменной, он не должен быть освобожден в конце 'viewDidAppear' функция? Если это так, то единственная сильная ссылка, которую я вижу, должна быть в другом CALayer, который я удаляю позже. Я никогда раньше не использовал NSCache, поэтому я не уверен, как его использовать, я попробовал, но я действительно не понял, как это может помочь освободить память. Примечание: я обновил свой вопрос, чтобы добавить более подробную информацию. – GawenBerger

+0

Все изображения добавляются в представление: 'self.view.addSubview (tmpV)', поэтому представление имеет сильную ссылку на них.Пока существует представление, добавленные изображения будут сильно сохранены. – zaph

1

Вы сказали:

загружают изображения с помощью метода «contentOfFile», который должен (в моем понимании) памяти релиза, когда память является низкой и приложением не нужно больше ...

(должно быть содержание сек OfFile, с "S")

Это не совсем правильно. Другой распространенный способ загрузки изображений, imageNamed, кэширует изображения, которые он загружает, если они вам понадобятся.

Метод contentOfFile для загрузки изображений не выполняет никакого кэширования. Он передает вам изображение, а затем вам нужно поддерживать сильную ссылку на изображение или он будет освобожден. Вам нужно отслеживать все сильные ссылки на изображение и следить за тем, чтобы все они были завершены, как только вы закончите с ними.

Давайте пройдемся по коду вы публикуемые и собственности изображений (и данные CGImage из этих изображений.)

Вы храните свои изображения в массиве, который определен в рамках вашего метода fillImagesArray. Этот метод возвращает массив, поэтому вызывающий абонент будет владеть массивом. Вы храните массив изображений в другой локальной переменной imagesArray в области viewDidAppear. Этот массив должен быть выпущен при возврате viewDidAppear.

Вы устанавливаете данные CGImage из изображений в набор слоев, которые вы устанавливаете как подслои вида tmpV, который вы создаете.

Оригинальные объекты UIImage должны быть освобождены и освобождены при возврате viewDidAppear. Однако данные изображения теперь хранятся в виде нескольких слоев.

Затем вы устанавливаете tmpV в качестве подзаголовка вашего представления содержимого контроллера вида.

Итак, когда все, что сделано, данные изображения принадлежат вашим слоям. Слои принадлежат слою вашего представления, который принадлежит вашему представлению tmpV. Этот вид принадлежит содержимому вашего представления контроллера. Я не вижу другого владения данными изображения. Целая цепочка сильных ссылок имеет одну точку, тот факт, что ваш tmpV-просмотр был добавлен как подзаголовок представления содержимого вашего контроллера представления.

Итак, вы должны быть в состоянии избавиться от всего комплекта и caboodle, просто удалив tmpV из его супервизора. Если это сработает, данные должны быть освобождены.

Другая работа, которую вы делаете для обнуления данных изображения из слоев и удаления слоев, не требуется. На самом деле это может вызвать проблемы. Я бы удалил весь этот код. В частности, вызов, который освобождает массив подслоев, кажется мне плохой идеей. Если вы собираетесь проходить через массив слоев, я бы предложил вам вызвать removeFromSuperlayer на каждом из них (хотя вам нужно будет скопировать массив слоев, прежде чем перечислять его, чтобы он не мутировал, пока он перечислили.)

Вы должны зарегистрировать код, который извлекает ваш вид tmpV через тег, чтобы убедиться, что он находит представление.

Как вы заключаете, что ваша память не освобождается ?, и где вы ее измеряете?

+0

Благодарим вас за анализ кода. Я уверен, что вид tmpV получен, потому что, когда я нажимаю на экран, все изображения удаляются из него. Я согласен, что обнуление слоя.contents и т. Д. Кажется странным, но это была просто попытка, поскольку ничего не работает. Я измеряю память с помощью панели в XCode, которая показывает использование памяти/диска/сети + CPU. Я также использовал «Profile» + «Allocations», и это дает мне тот же результат. Я обновлю вопрос с помощью скриншота и, возможно, более подробной информации. (Кстати, я исправил опечатку contentOfFile, спасибо) – GawenBerger