Проблема с памятью при использовании большого массива UIImage вызывает сбой (swift)

в моем приложении у меня есть массив изображений, который содержит все изображения, сделанные на моей камере. Я использую collectionView для отображения этих изображений. Однако, когда этот массив изображений достигает 20-го или около того изображения, он аварийно завершает работу. Я считаю, что это связано с проблемой памяти.. Как сохранить изображения в массиве изображений таким образом, чтобы память была эффективной?

Майкл Dauterman дала ответ, используя эскизы. Я надеялся, что есть решение, кроме этого. Возможно, хранение фотографий в NSData или CoreData?

камера.Свифт:

//What happens after the picture is chosen
func imagePickerController(picker:UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject:AnyObject]){
    //cast image as a string
    let mediaType = info[UIImagePickerControllerMediaType] as! NSString
    self.dismissViewControllerAnimated(true, completion: nil)
    //if the mediaType it actually is an image (jpeg)
    if mediaType.isEqualToString(kUTTypeImage as NSString as String){
        let image = info[UIImagePickerControllerOriginalImage] as! UIImage

        //Our outlet for imageview
        appraisalPic.image = image

        //Picture taken, to be added to imageArray
        globalPic = image

        //image:didFinish.. if we arent able to save, pass to contextInfo in Error Handling
        if (newMedia == true){
            UIImageWriteToSavedPhotosAlbum(image, self, "image:didFinishSavingWithError:contextInfo:", nil)

        }
    }
}

NewRecord.Свифт!--3-->

var imageArray:[UIImage] = [UIImage]()
viewDidLoad(){

    //OUR IMAGE ARRAY WHICH HOLDS OUR PHOTOS, CRASHES AROUND 20th PHOTO ADDED
    imageArray.append(globalPic)

//Rest of NewRecord.swift is code which adds images from imageArray to be presented on a collection view
}

7 ответов


я столкнулся с проблемами с низкой памятью в моих собственных приложениях, которые должны работать с рядом объектов UIImage с высоким разрешением.

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

здесь некоторый код, который позволяет создавать эскизы:

// image here is your original image
let size = CGSizeApplyAffineTransform(image.size, CGAffineTransformMakeScale(0.5, 0.5))
let hasAlpha = false
let scale: CGFloat = 0.0 // Automatically use scale factor of main screen

UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale)
image.drawInRect(CGRect(origin: CGPointZero, size: size))

let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
imageArray.append(scaledImage)

и более подробную информацию об этих методах можно найти в этой статье NSHipster.

Swift 4 -

// image here is your original image
let size = image.size.applying(CGAffineTransform(scaleX: 0.5, y: 0.5))
let hasAlpha = false
let scale: CGFloat = 0.0 // Automatically use scale factor of main screen

UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale)
image.draw(in: CGRect(origin: .zero, size: size))

let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

лучшая практика-держать imageArray коротким. Массив должен использоваться только для кэширования изображений, находящихся в текущем диапазоне прокрутки (и тех, которые будут отображаться для лучшего пользовательского интерфейса). Вы должны сохранить остальные в CoreData и загружать их динамически во время прокрутки. В противном случае приложение в конечном итоге рухнет даже с использованием миниатюр.


позвольте мне начать с простого ответа: вы не должны реализовывать вещи, которые испытали тысячи людей самостоятельно. Есть несколько отличных библиотек, которые заботятся об этой проблеме сами по себе, реализуя дисковый кэш, кэш памяти, буферы.. В основном все, что вам когда-либо понадобится, и даже больше.

две библиотеки, которые я могу рекомендовать, чтобы вы следующий:

оба они великолепны, поэтому это действительно вопрос предпочтения (мне больше нравится Haneke), но они позволяют загружать изображения в разных потоках, будь то из интернета или из вашего пакета, или из файловой системы. Они также имеют расширения для UIImageView, который позволяет использовать функцию 1-line для загрузки всех изображений легко и во время загрузки этих изображений, они заботятся о погрузка.

кэш

для вашей конкретной проблемы вы можете использовать кэш, который использует эти методы для решения проблемы, например (из документации):

[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];

теперь, когда у вас есть это в этом кэше, вы можете получить его легко

SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"];
[imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) {
    // image is not nil if image was found
}];

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

надеюсь, что это помогает!


когда вы получаете предупреждение памяти от вашего контроллера вида, вы можете удалить фотографии, которые вы не отображаете из своего массива, и сохранить их в виде файла, а затем загрузить их снова, когда они необходимы, и так далее. Или просто обнаружение, когда они исчезают с collectionView:didEndDisplayingCell:forItemAtIndexPath

сохранить их в массив, как это:

var cachedImages = [(section: Int, row: Int, imagePath: String)]()

использование:

func saveImage(indexPath: NSIndexPath, image: UIImage) {
    let imageData = UIImagePNGRepresentation(image)
    let documents = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
    let imagePath = (documents as NSString).stringByAppendingPathComponent("\(indexPath.section)-\(indexPath.row)-cached.png")

    if (imageData?.writeToFile(imagePath, atomically: true) == true) {
        print("saved!")
        cachedImages.append((indexPath.section, indexPath.row, imagePath))
    }
    else {
        print("not saved!")
    }
}

и получить их обратно с:

func getImage(indexPath indexPath: NSIndexPath) -> UIImage? {
    let filteredCachedImages = cachedImages.filter({ .section == indexPath.section && .row == indexPath.row })

    if filteredCachedImages.count > 0 {
        let firstItem = filteredCachedImages[0]
        return UIImage(contentsOfFile: firstItem.imagePath)!
    }
    else {
        return nil
    }
}

использовать что-то вроде ответ in чтобы избежать блокировки основного потока

Я привел пример:найти здесь


используйте следующий код, чтобы уменьшить размер изображения при его сохранении:

       var newImage : UIImage
       var size = CGSizeMake(400, 300)
       UIGraphicsBeginImageContext(size)
       image.drawInRect(CGRectMake(0,0,400,300))
       newImage = UIGraphicsGetImageFromCurrentImageContext()
       UIGraphicsEndImageContext()

Я бы предложил оптимизировать ваш код вместо создания массива фотографий, просто создайте массив URL-адресов (версия ios библиотека фотографий 8.1) и извлекайте изображения только при необходимости через эти URL-адреса. т. е. при отображении.

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

можно использовать autoreleasepool чтобы удалить ненужные ссылки, которые не могут быть выпущены ARC.

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


вы можете просто хранить необработанные данные изображения в массиве, а не все метаданные и лишние вещи. Я не знаю, нужны ли вам метаданные, но вы можете обойтись без них. Другой альтернативой было бы записать каждое изображение во временный файл, а затем получить его позже.


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

ViewDidLoad:

    //Check if the folder exists, if not, create it
    let fetchOptions = PHFetchOptions()
    fetchOptions.predicate = NSPredicate(format: "title = %@", albumName)
    let collection:PHFetchResult = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions)

    if let first_Obj:AnyObject = collection.firstObject{
        //found the album
        self.albumFound = true
        self.assetCollection = first_Obj as! PHAssetCollection
    }else{
        //Album placeholder for the asset collection, used to reference collection in completion handler
        var albumPlaceholder:PHObjectPlaceholder!
        //create the folder
        NSLog("\nFolder \"%@\" does not exist\nCreating now...", albumName)
        PHPhotoLibrary.sharedPhotoLibrary().performChanges({
            let request = PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(albumName)
            albumPlaceholder = request.placeholderForCreatedAssetCollection
            },
            completionHandler: {(success:Bool, error:NSError!)in
                if(success){
                    println("Successfully created folder")
                    self.albumFound = true
                    if let collection = PHAssetCollection.fetchAssetCollectionsWithLocalIdentifiers([albumPlaceholder.localIdentifier], options: nil){
                        self.assetCollection = collection.firstObject as! PHAssetCollection
                    }
                }else{
                    println("Error creating folder")
                    self.albumFound = false
                }
        })

    }



func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
    //what happens after the picture is chosen
    let mediaType = info[UIImagePickerControllerMediaType] as! NSString
    if mediaType.isEqualToString(kUTTypeImage as NSString as String){
        let image = info[UIImagePickerControllerOriginalImage] as! UIImage

        appraisalPic.image = image
        globalPic = appraisalPic.image!

        if(newMedia == true){
            UIImageWriteToSavedPhotosAlbum(image, self, "image:didFinishSavingWithError:contextInfo:", nil)
            self.dismissViewControllerAnimated(true, completion: nil)
            picTaken = true


            println(photosAsset)


        }
        }
 }