使用大型UIImage数组时的内存问题导致崩溃(快速)

时间:2015-08-08 20:37:01

标签: ios swift camera uiimage

在我的应用程序中,我有一个图像阵列,可以保存我相机拍摄的所有图像。我正在使用collectionView来显示这些图像。但是,当此图像阵列达到大约20张图像时,它会崩溃。我相信这是由于内存问题..如何以一种内存效率的方式将图像存储在图像阵列中?

Michael Dauterman使用缩略图提供了答案。我希望除此之外还有一个解决方案。也许将图片存储到NSData或CoreData中?

Camera.swift:

//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.swift

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 个答案:

答案 0 :(得分:6)

我自己的应用程序中遇到了低内存问题,这些问题必须与许多高分辨率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)

more information about these techniques can be found in this NSHipster article

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()

答案 1 :(得分:4)

最佳做法是保持imageArray短。该阵列应仅用于缓存当前滚动范围内的图像(以及即将显示的图像以获得更好的用户体验)。您应该将其余部分保留在CoreData中并在滚动期间动态加载它们。否则,即使使用缩略图,该应用最终也会崩溃。

答案 2 :(得分:2)

让我从简单的答案开始:你不应该实现成千上万人自己经历的东西。有一些很好的库可以通过实现磁盘缓存,内存缓存,缓冲区来解决这个问题。基本上你需要的一切,等等。

我可以推荐给您的两个库:

它们都很棒,所以它确实是优先考虑的问题(我更喜欢Haneke)​​,但它们允许您在不同的线程上下载图像,无论是来自Web还是来自您的捆绑,或者来自文件系统。它们还具有UIImageView的扩展,允许您使用单行功能轻松加载所有图像,当您加载这些图像时,它们关心加载。

<强>缓存

对于您的特定问题,您可以使用使用这些方法来处理问题的缓存,例如(来自文档):

[[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
}];

所有内存处理和平衡都是由库本身完成的,因此您不必担心任何事情。您可以选择将其与调整大小方法结合使用,以存储较小的图像(如果这些图像很大),但这取决于您。

希望它有所帮助!

答案 3 :(得分:1)

当您从视图控制器收到内存警告时,您可以从阵列中删除未显示的照片并将其另存为文件,然后在需要时再次加载等等。或者只是通过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({ $0.section == indexPath.section && $0.row == indexPath.row })

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

同样使用类似this answer的内容以避免阻塞主线程

我做了一个例子:find it here

答案 4 :(得分:1)

使用以下代码在存储图像时缩小图像的大小:

def foo(string : str) -> str:
    if foo.__annotations__.get('string')==type(string):
        print('continue...')

我建议优化您的代码,而不是创建一个照片数组,只需创建一个URL的数组(ios版本&lt; 8.1 from AssetLibrary)/ localIdentifier(版本&gt; 8.1照片库),并仅在需要时获取图像这些网址。即显示时。

如果将图像存储在数组中,ARC有时无法正确处理内存管理,并且在许多地方也会导致内存泄漏。

您可以使用 autoreleasepool 删除ARC无法发布的不必要的引用。

要进一步添加,如果您通过相机捕获任何图像,那么存储在数组中的大小远远大于图像的大小(虽然我不知道为什么!)。

答案 5 :(得分:0)

您可以将原始图像数据存储在一个数组中,而不是存储所有元数据和多余内容。我不知道你是否需要元数据,但你可以在没有元数据的情况下解决问题。另一种方法是将每个图像写入临时文件,然后再检索它。

答案 6 :(得分:-2)

对我有用的最佳路线是以全尺寸存储一组图像是使用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)


        }
        }
 }