Alamofire .dowload takes a lot memory (ios, swift) - ios

i downloaded images from the server with alamofire framework. I worked with Alamofire.download . Each image has size above 1MB +-, but after each request memory increase a lot. After downloading 4 images the memory used is above 171MB and after that each image give more than 35MB.
Downloading code is:
Alamofire.download(mainReguest, to: self.destination)
.downloadProgress{ progress in
self.progressView.progress = Float(progress.fractionCompleted)
}
.response{ response in
if response.error == nil, let imagePath = response.destinationURL?.path {
let image = UIImage(contentsOfFile: imagePath)
self.addNewImageToTheScrollView(img: image)
}
}
}
Code with destination is:
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory,
in: .userDomainMask,
with: [DownloadRequest.DownloadOptions.removePreviousFile])

The problem is with this...
UIImage(contentsOfFile: imagePath)
Specifically with the fact that you are (presumably) downloading a compressed format of your image.
However, UIImage is uncompressed in memory. So each pixel of your image will take 4 bytes of information (red, green, blue, alpha).
So if you have an image that is (for example) 5 mega pixels.
Then 5,000,000 pixels * 4b = 20MB.
So, I imagine your images are around 10MP?
The best way around this is to optimise your image download. If you're displaying an image on an iPhone then there is no point downloading a 10MP version of it. You might as well resize it to be much much smaller.
Ideally, you should be resizing the images on the backend before the download happens.

Your Alamofire's code is ok. Most likely you have issues somewhere in UI part, for example because of the high image resolution. The first thing you have to do is to localise an issue. To check networking code please comment all UI related code and run your app one more time.
Alamofire.download(mainReguest, to: self.destination)
.downloadProgress{ progress in
// self.progressView.progress = Float(progress.fractionCompleted)
}
.response{ response in
if response.error == nil, let imagePath = response.destinationURL?.path {
let image = UIImage(contentsOfFile: imagePath)
print("Image is successfully downloaded!")
//self.addNewImageToTheScrollView(img: image)
}
}
}

Related

Swift cache base64 string UIImage data

I'm trying to cache UIImage that images come from service(I'm using Alamofire). Service sends me a base64 string and I'm converting base64 to data then print in tableviewcell with
cell.imageview.image = UIImage(data: imageDatas[indexPath.row])
I searched lots of libraries like Kingfisher , AlamofireImage but they are caching URL image can't find anyway to cache image with base64 string.So I find a similar example and try this :
private let cache = NSCache<NSNumber, UIImage>()
private let utilityQueue = DispatchQueue.global(qos: .utility)
private func loadImage(data : Data , completion: #escaping (UIImage?) -> ()) {
utilityQueue.async {
let image = imageDataDecodingClass.imageDataDecoding(imageData: data)
DispatchQueue.main.async {
completion(image)
}
}
}
in cell :
let itemNumber = indexPath.section * 2 + 1
let imageData = (showcaseDatas[indexPath.section * 2 + 1].Document?.Document!)!
if let cachedImage = self.cache.object(forKey: NSNumber(value: itemNumber)) {
cell.showcaseImage.image = cachedImage
} else {
cell.addSubview(progressHUDimage)
self.loadImage(data: imageData, completion: {ret in
cell.showcaseImage.image = ret
self.cache.setObject(cell.showcaseImage.image!, forKey: NSNumber(value: itemNumber))
progressHUDimage.removeFromSuperview()
})
}
Its caching image perfectly but when I scroll tableview ,CPU increased a lot (%70-90) thats why tableview is not smoothing.
So , my question is , How can I cache base64 string image in tableviewcell with smoothing and without CPU increased ? Thanks
If you are caching base64 String or Data there will be always a hit in decoding them as an image. The only way it would be caching the UIImage itself, but this will come with trade off of memory depending on the size of your images.
I can only give you few advices:
Use NSCache (it seems that you are already using it)
Resize your images on another thread to have a perfect fit on the size that you are rendering on screen and cache them only after they have been resized
Be sure that the performance hit you are seeing is not due to other reasons
Cache images by using their index path if you are using for cells
Create your NSCache with a memory limit and a number of element limit
You can also try to create 2 caches, one for ready to go already decode images and the other one as a fallback with Data object.
The fact that your Data objects are small it doesn't means that also images are, it really depends on the compression that has been used to save the image representation.
There are also more advance technique using memory mapping on physical memory.

Getting Memory waning in UIImage?(data:)

I am getting an Image as Data from server.To show it I am using UIImage?(data:) to show it.For a specific image application is getting crash because of memory pressure. What could be the reason, how to fix it.
if let imgData = Utils.fetchDataFromDocumentDirectory(imageName:"test.jpg"){
attachmentImgView.image = UIImage(data:imgData)
}
How big is the Image? and are you downloading only 1 image or an array?
This might be caused from the size of the image you are trying to download.
My advice if you are trying to show an image, only giving it the url like so:
let url = URL(string: "http://i.imgur.com/w5rkSIj.jpg")
let data = try? Data(contentsOf: url)
if let imageData = data {
let image = UIImage(data: data)
}
even better you can try to use AsyncImageView 3rd party library where you give it the url of the image and it shows it asynchronously without delays in the app.
Hope this helps!

UIImageJPEGRepresentation using large amount of memory (Swift 3.0)

I'm trying to compress and get the NSdata from between 20 and 30 UIImages with a "for-loop" like this:
for theImage in selectedUIImages {
let data = UIImageJPEGRepresentation(image,0.5)
// doing something with the data
}
Tried on an iPhone 7 with no issues besides my app using upto 700MB of memory when going through the loop, but on an older iPhone I get the message:
*Message from debugger: Terminated due to memory issue.*
The main objective is to get the NSData from the UIImage so I can put the image in a dir for uploading. Let me explain:
The Amazon S3 Transfer utility wants a path/url to the image and therefore I need to make a path/url for the UIImage and the only way i know is to get it by:
data.write(to: URL(fileURLWithPath: localPath), options: .atomic)
Try using an autorelease pool:
autoreleasepool {
for theImage in selectedUIImages {
let data = UIImageJPEGRepresentation(image,0.5)
// doing something with the data
}
}
and move it in a background thread.
Because your app run out of memory.
You can save it to Document directory after compress then upload it to server one by one. So it not make your memory issue.
You can decrease the image size by decreasing a ratio parameter. You can use 0.3 instead 0.5.
for theImage in selectedUIImages {
let data = UIImageJPEGRepresentation(image,0.3)
// doing something with the data
}

SDWebImage's sd_setImageWithURL updates the wrong cell with Image on scrolling!! Is that a expected behaviour?

OverView
I am downloading the images from web server using SDWebImage in my collectionView cell.
if floor.hasTitleImage != nil {
self.floorImageView.sd_setImageWithURL(NSURL(string:(floor.hasTitleImage?.imageURL)!), placeholderImage: UIImage(named: "Placeholder"), completed: { (imageDownloaded, error, cacheType, url) in
if imageDownloaded.size.width > 300 {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let resizedImage = imageDownloaded.resizePreservingAspectRatio(toSize: CGSize(width: 300, height: 300))
SDImageCache.sharedImageCache().removeImageForKey(NSURL(string:(floor.hasTitleImage?.imageURL)!)?.absoluteString, fromDisk: true, withCompletion: {
SDImageCache.sharedImageCache().storeImage(resizedImage, forKey: NSURL(string:(floor.hasTitleImage?.imageURL)!)?.absoluteString, toDisk: true)
})
})
}
})
}
else{
self.floorImageView.image = UIImage(named: "Placeholder")
}
Now what this code is doing is downloading the image using the url stored in core data element called floor. Now the images downloaded are way bigger then my ImageView so am resizing it to somewhere around 300px.
I know that SDWebImage does intense caching by default and uses url's absolute string as key for its cache. So Once resized! I replace the original image with my resized image.
Issue
Everything worked fine as expected! till I tested it with low internet speed which caused the image download to be delayed.
If the image downloading takes time, till then if I scroll the tableView and the cell gets reused, once the image downloaded SDWebImage loads the imageView of the cell but ends up resulting the wrong cell with wrong image.
Though this happens only once as for next time onwards image will be loaded from cache everything works fine. But Is this the expected behaviour or am I doing something wrong???
Solutions I could think of :
As sd_setImageWithURL by default sets the value of imageView with downloaded image rather than downloading the image using sd_setImageWithURL, if I can download it using,
if floor.hasTitleImage != nil {
if let downloadedImage = SDImageCache.sharedImageCache().imageFromMemoryCacheForKey(NSURL(string:(floor.hasTitleImage?.imageURL)!)?.absoluteString){
self.floorImageView.image = downloadedImage
}
else if let diskImage = SDImageCache.sharedImageCache().imageFromDiskCacheForKey(NSURL(string:(floor.hasTitleImage?.imageURL)!)?.absoluteString){
self.floorImageView.image = diskImage
}
else{
let downloadManager = SDWebImageManager.sharedManager();
downloadManager.downloadImageWithURL(NSURL(string:(floor.hasTitleImage?.imageURL)!), options: [], progress: nil, completed: { (downloadedImage, error, cacheType, finished, url) in
if downloadedImage.size.width > 300 {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let resizedImage = downloadedImage.resizePreservingAspectRatio(toSize: CGSize(width: 300, height: 300))
SDImageCache.sharedImageCache().removeImageForKey(NSURL(string:(floor.hasTitleImage?.imageURL)!)?.absoluteString, fromDisk: true, withCompletion: {
SDImageCache.sharedImageCache().storeImage(resizedImage, forKey: NSURL(string:(floor.hasTitleImage?.imageURL)!)?.absoluteString, toDisk: true)
})
if floor.hasTitleImage?.imageURL != nil && floor.hasTitleImage?.imageURL! == url{
dispatch_async(dispatch_get_main_queue(), {
self.floorImageView.image = SDImageCache.sharedImageCache().imageFromMemoryCacheForKey(NSURL(string:(floor.hasTitleImage?.imageURL)!)?.absoluteString)
self.reloadInputViews()
})
}
})
}
})
}
}
else{
self.floorImageView.image = UIImage(named: "Placeholder")
}
Though it works it will load the image only when I scroll the tableView :(
Questions :
Is what sd_setImageWithURL doing is expected behaviour ?? I mean every single one of the app using SDWebImage with tableView or CollectionView must have faced this correct ?? So if no body faced it I must be doing something wrong :( Am I doing anything wrong ?
Is what am doing using SDWebImageManager.sharedManager is correct ??? Is that long code required? Checking memory cache then disk cache and finally making call to download image isn't it too much :o
Why is that my second approach not loading the image once downloaded and expects the collection view to scroll and reload the cell to display the image ??
EDIT
I solved the 3rd question :) Sorry my mistake. I was comparing if floor.hasTitleImage?.imageURL != nil && floor.hasTitleImage?.imageURL! == url{ which is wrong because url above is NSURL where as floor.hasTitleImage?.imageURL! is NSString.
So Updated solution :
if floor.hasTitleImage?.imageURL != nil && floor.hasTitleImage!.imageURL! == url.absoluteString{
dispatch_async(dispatch_get_main_queue(), {
self.floorImageView.image = SDImageCache.sharedImageCache().imageFromDiskCacheForKey(NSURL(string:(floor.hasTitleImage?.imageURL)!)?.absoluteString)
self.setNeedsLayout()
})
}
Though my second approach downloads the image now and updates the cell properly but now image downloading is very slow compared to sd_setImageWithURL.
Is there any way I can use the method1 sd_setImageWithURL and get the image updated properly?
Please help me!! what am doing wrong !! Thanks in advance :)
Is what sd_setImageWithURL doing is expected behaviour ?? I mean every single one of the app using SDWebImage with tableView or CollectionView must have faced this correct ?? So if no body faced it I must be doing something wrong :( Am I doing anything wrong ?
No, it's not expected behaviour. I'm using SDWEbImage as well both tableView and collectionView and both are work fine.
Is what am doing using SDWebImageManager.sharedManager is correct ??? Is that long code required? Checking memory cache then disk cache and finally making call to download image isn't it too much :o
You don't have to cache image youself, SDWebImage take care caching process for you (in memory and disk).
If you need to resize image before cache and display, I think better do it by implement SDWebImageManagerDelegate which allows you to transform the image after it has been downloaded but before cache and display.
UPDATE:
If you use sd_setImageWithURL, there is another solution to prevent wrong assign image is by override prepareForReuse() method in UICollectionViewCell subclass and call sd_cancelCurrentImageLoad() on imageView and set image to nil.
hope this help.

iOS image loader effect

I am using "SVProgressHUD" for loader. When I want to load an image from url I am using async process to load the image in background. After completing the download I am updating the UI. And for that time span I am using a placeholder on the imageview and a loader with "Please wait..". But I want to use a loader like "Instagram". That means, when an image is loading from online it loads a blurry image of the original image and upon downloading the image , the blurry image show the original image.
Can any one please suggest me how can I do this?
The stuff you are talking about is Progressive JPEG.
Progressive JPEG (PJPEG) is an image format that stores multiple,
individual “scans” of a photo, each with an increasing level of
detail. When put together, the scans create a full-quality image. The
first scan gives a very low-quality representation of the image, and
each following scan further increases the level of detail and quality.
When images are downloaded using PJPEG, we can render the image as
soon as we have the first scan. As later scans come through, we update
the image and re-render it at higher and higher quality.
So, you have to make images loadable in this modern progressive format.You can follow a approach where every image before saving on server just convert it into appropriate progressive format.There are several tools avaiable for different type of server. E.g jpegtran
To check whether image is progressive or not you can use this tool or tool2.You can search for online tool a lot of tool is available.
Now for iOS
You can follow this tutorial
Some library
Concorde
DFImageManager
Assuming you are in control of the hosting of the images you can host lower resolution images and make 2 fetches, one for the lower resolution, and one for the higher. If you make the lower resolution image significantly smaller in file size the fetch for it will finish before the fetch for the full image, and you populate the UIImageView with this image until such a time as you can replace it.
Purely example code would look like this:
let imageView = UIImageView()
let lowResOperation = NSBlockOperation {
guard let imageData = NSData(contentsOfURL: NSURL(string: "https://myhost.org/low-res-image")!),
image = UIImage(data: imageData) else { return }
if imageView.image == nil {
imageView.image = image
}
}
let highResOperation = NSBlockOperation {
guard let imageData = NSData(contentsOfURL: NSURL(string: "https://myhost.org/high-res-image")!),
image = UIImage(data: imageData) else { return }
imageView.image = image
}
let backgroundQueue = NSOperationQueue()
backgroundQueue.addOperations([lowResOperation, highResOperation], waitUntilFinished: true)

Resources