Search code examples
iosswiftamazon-web-servicesamazon-s3decoding

Can't download multiple AWS S3 images at once asynchronously


So essentially, I'm using a server to retrieve the URLs already stored. Once I retrieve the URL (in a for loop), I am calling a function that downloads them directly from AWS S3. The issue is that when I only have to download one image at a time, it works perfectly; this includes the case where I am already downloading one image, I upload another, then I go back to the loading home view, and the two images appear properly. When I have multiple images that need downloading (when I first start the app), the images array overlaps. I am using an array for the URL and another array for the UIImages.

Here is my ImageCache code:

class ImageCache {
    static var urlCache: Array<String> = []
    static var imageCache: Array<UIImage> = []
}

I used this instead of NSCache is because I need to be able to use imageCache as my data source and I wasn't sure how to specify the number of objects inside an NSCache as a NSCache object isn't iterable.

Anyways, here is the code where I'm calling the download function:

query.findObjectsInBackground { (results, error) in
    var imageURL: String = ""
    var count: Int = 1
    if error == nil {
        for result in results! {
            print(result)
            imageURL = result["imageURL"] as! String
            let imageKey = imageURL.components(separatedBy: "/uploads")[1]
            if !ImageCache.urlCache.contains(imageURL) {
                self.downloadImageFromS3(key: "uploads\(imageKey)", count: results!.count, current: count, url: imageURL)
            }
            count += 1
        }
    }
}

The reason for current and count is that when the current index equals the last index in the for loop, then we should update the UICollectionView. This way the view is reloaded only when the last image is downloaded (at least this was the goal).

And here is my downloadImageFromS3 function:

func downloadImageFromS3(key: String, count: Int, current: Int, url: String) {
    DispatchQueue.main.async {
        let transferManager = AWSS3TransferManager.default()
        let downloadingFileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("image.png")
        let downloadRequest = AWSS3TransferManagerDownloadRequest()
        downloadRequest!.bucket = "BUCKET_NAME"
        downloadRequest!.key = key
        downloadRequest!.downloadingFileURL = downloadingFileURL
        transferManager.download(downloadRequest!).continueWith(executor: AWSExecutor.mainThread()) { (task) -> Any? in
            if let error = task.error as NSError? {
                if error.domain == AWSS3TransferManagerErrorDomain, let code = AWSS3TransferManagerErrorType(rawValue: error.code) {
                    switch code {
                    case .cancelled, .paused:
                        break
                    default:
                        print("Error downloading: \(String(describing: downloadRequest!.key)) Error: \(error)")
                    }
                } else {
                    print("Error downloading: \(String(describing: downloadRequest!.key)) Error: \(error)")
                }
                return nil
            } else {
                let image = UIImage(contentsOfFile: downloadingFileURL.path)!
                ImageCache.urlCache.append(url)
                ImageCache.imageCache.append(image)
            }
            print("Download complete for: \(String(describing: downloadRequest!.key))")
            if current == count && ImageCache.imageCache.count == count && ImageCache.urlCache.count == count {
                self.collectionView?.reloadData()
                print(ImageCache.imageCache)
                print(ImageCache.urlCache)
            }
            return nil
        }
    }
}

EDIT: I thought I fixed the issue by putting the line

self.collectionView.reloadData()

outside that if statement I was explaining earlier. This causes the last two images to be messed with, meaning that the first and second image will look fine, but the third and fourth image are the same image. Does anyone have any solutions to make this work?

EDIT: I just found the real issue in why some of the final images are overlapping. I'm getting a decoding error when downloading the image:

2018-08-10 22:26:57.826759-0500 PROJECT_NAME[81557:49209786] mapData:754: *** ImageIO - mmapped file changed (old: 8112955  new: 6209998)
2018-08-10 22:26:57.871970-0500 PROJECT_NAME[81557:49209786] [0x7fcf610c5a00] Decoding failed with error code -1
2018-08-10 22:26:57.872127-0500 PROJECT_NAME[81557:49209786] [0x7fcf610c5a00] Decoding: C0 0x10C00B20 0x0218304A 0x11111100 0x00590011 8112955
2018-08-10 22:26:57.872225-0500 PROJECT_NAME[81557:49209786] [0x7fcf610c5a00] Options: 1x-1 [00000000,10C003B6] 0001D060
2018-08-10 22:26:57.872341-0500 PROJECT_NAME[81557:49209786] imageBlockSetCreate:829: *** buffer height mismatch: rect:{0,0,4288,950}  size:{4288,2848}
2018-08-10 22:26:57.873336-0500 PROJECT_NAME[81557:49209786] [Unknown process name] img_blocks_create: Null block
CGImageProviderCopyImageBlockSet(<CGImageProvider 0x6040002ed980> (component-type = Int8, pixel-size = 4, size = [4288 x 2848]));
<CGImageBlockSet 0x6000001357c0> (size = [4288 x 2848], rect = (0, 0) x [4288, 950], count = 3) [1]
2018-08-10 22:26:57.873474-0500 PROJECT_NAME[81557:49209786] [Unknown process name] img_blocks_create: Null block
CGImageProviderCopyImageBlockSet(<CGImageProvider 0x6040002ed980> (component-type = Int8, pixel-size = 4, size = [4288 x 2848]));
<CGImageBlockSet 0x6000001357c0> (size = [4288 x 2848], rect = (0, 0) x [4288, 950], count = 3)

I'm under the impression that the error is in this line: let image = UIImage(contentsOfFile: downloadingFileURL.path)!. The image, upon upload to S3, has Content-Encoding set to base64.


Solution

  • Solved my own question. Once I changed the downloadingFileURL path to a .jpeg file path and made it unique, I was able to see the issue. I was also invoking the wrong initializer for UIImage. It should've been UIImage(named: downloadingFileURL.path)! instead of UIImage(contentsOfFile: downloadingFileURL.path)!