I'm currently having an issue with an iOS project I'm developing. It goes through a procedure where it has to download images from a server, round them and place them on a black background, and finally save them as a file. This loops through over 2.000 URLs of different images and has to process them each. The problem is that there seems to be a huge memory leak somewhere, I just can't figure out how to solve it. The memory warning is triggered four times before the app is terminated.
func getRoundedPNGDataFromData(imageData: NSData) -> NSData? {
let Image: UIImage? = UIImage(data: imageData)!
let ImageFrame: CGRect = CGRectMake(0, 0, Image!.size.width, Image!.size.height)
UIGraphicsBeginImageContextWithOptions(Image!.size, false, 1.0);
let CurrentContext: CGContextRef = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(CurrentContext, UIColor.blackColor().CGColor)
CGContextFillRect(CurrentContext, ImageFrame)
UIBezierPath(roundedRect: ImageFrame, cornerRadius: 10.0).addClip()
Image!.drawInRect(ImageFrame)
let NewImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImagePNGRepresentation(NewImage!)
}
...
var ProgressCounter: Int = 0
for OID: String in MissingThumbnails {
let CurrentProgress: CFloat = (CFloat(ProgressCounter) / CFloat(MissingThumbnails.count))
dispatch_sync(dispatch_get_main_queue(), {
progressView?.progress
progressView?.setProgress(CurrentProgress, animated: true)
})
let ThumbURL: NSURL = NSURL(string: "http://\(Host):\(Port)/\(self.WebAPIThumbnailPath)".stringByReplacingOccurrencesOfString("${OID}", withString: OID, options: [], range: nil).stringByReplacingOccurrencesOfString("${ThumbnailNumber}", withString: self.padID(5), options: [], range: nil))!
var ThumbData: NSData? = NSData(contentsOfURL: ThumbURL)
if (ThumbData != nil) {
if (ThumbData!.length > 0) {
var RoundedPNG: NSData? = self.getRoundedPNGDataFromData(ThumbData!)
if ((RoundedPNG!.writeToFile(FSTools.getDocumentSub("\(self.Structs[Paths.OBJ_THUMBNAILS])/\(OID).png"), atomically: false)) == false) {
dispatch_sync(dispatch_get_main_queue(), {() -> Void in
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
delegate?.thumbnailCacheCompleted?(false)
})
}
RoundedPNG = nil
ThumbData = nil
}
}
ProgressCounter++
}
You need to add an autoreleasepool inside the loop to allow the autoreleased memory to be deallocated.
for OID: String in MissingThumbnails {
autoreleasepool {
/* code */
}
}
Autoreleased memory is deallocated when the current autoreleasepool scope is left which generally happens in the runloop. But if the code is tight and the runloop does not run the memory will not be released in a timely manner. Adding an explicit autoreleasepool at each iteration will allowed this temporary autoreleased memory to be reclaimed on each iteration of the loop.
Related
I got this method that grabs the camera buffer image to do some image processing every few seconds.
Below method runs fine on all my testing, yet this is present in crash reports with a significant amount of crashes.
final func cameraBufferProcessing () {
DispatchQueue.global(qos: .background).sync { [unowned self] in
if let bufferImage = self.cameraBufferImage?.oriented(.downMirrored) {
let heightPropotion : CGFloat = bufferImage.extent.height * 0.5 //Crashes on this line
if let cgImg = self.context.createCGImage(bufferImage.clampedToExtent(), from: CGRect(x: 0, y: heightPropotion, width: bufferImage.extent.width, height: heightPropotion))
{
DispatchQueue.main.async {
// use with cgImg to do image processing
}
}
} else {
// do something else
}
}
}
Crash reports point to the third line above:
let heightPropotion : CGFloat = bufferImage.extent.height * 0.5
Crash seems to be related to extent.
What could be the cause here?
I want to show gif image in a UIImageView and with the code below (source: https://iosdevcenters.blogspot.com/2016/08/load-gif-image-in-swift_22.html, *I did not understand all the codes), I am able to display gif images. However, the memory consumption seems high (tested on real device). Is there any way to modify the code below to reduce the memory consumption?
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://cdn-images-1.medium.com/max/800/1*oDqXedYUMyhWzN48pUjHyw.gif"
let gifImage = UIImage.gifImageWithURL(url)
imageView.image = gifImage
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l < r
case (nil, _?):
return true
default:
return false
}
}
extension UIImage {
public class func gifImageWithData(_ data: Data) -> UIImage? {
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
print("image doesn't exist")
return nil
}
return UIImage.animatedImageWithSource(source)
}
public class func gifImageWithURL(_ gifUrl:String) -> UIImage? {
guard let bundleURL:URL? = URL(string: gifUrl) else {
return nil
}
guard let imageData = try? Data(contentsOf: bundleURL!) else {
return nil
}
return gifImageWithData(imageData)
}
public class func gifImageWithName(_ name: String) -> UIImage? {
guard let bundleURL = Bundle.main
.url(forResource: name, withExtension: "gif") else {
return nil
}
guard let imageData = try? Data(contentsOf: bundleURL) else {
return nil
}
return gifImageWithData(imageData)
}
class func delayForImageAtIndex(_ index: Int, source: CGImageSource!) -> Double {
var delay = 0.1
let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil)
let gifProperties: CFDictionary = unsafeBitCast(
CFDictionaryGetValue(cfProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque()),
to: CFDictionary.self)
var delayObject: AnyObject = unsafeBitCast(
CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()),
to: AnyObject.self)
if delayObject.doubleValue == 0 {
delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: AnyObject.self)
}
delay = delayObject as! Double
if delay < 0.1 {
delay = 0.1
}
return delay
}
class func gcdForPair(_ a: Int?, _ b: Int?) -> Int {
var a = a
var b = b
if b == nil || a == nil {
if b != nil {
return b!
} else if a != nil {
return a!
} else {
return 0
}
}
if a < b {
let c = a
a = b
b = c
}
var rest: Int
while true {
rest = a! % b!
if rest == 0 {
return b!
} else {
a = b
b = rest
}
}
}
class func gcdForArray(_ array: Array<Int>) -> Int {
if array.isEmpty {
return 1
}
var gcd = array[0]
for val in array {
gcd = UIImage.gcdForPair(val, gcd)
}
return gcd
}
class func animatedImageWithSource(_ source: CGImageSource) -> UIImage? {
let count = CGImageSourceGetCount(source)
var images = [CGImage]()
var delays = [Int]()
for i in 0..<count {
if let image = CGImageSourceCreateImageAtIndex(source, i, nil) {
images.append(image)
}
let delaySeconds = UIImage.delayForImageAtIndex(Int(i),
source: source)
delays.append(Int(delaySeconds * 1000.0)) // Seconds to ms
}
let duration: Int = {
var sum = 0
for val: Int in delays {
sum += val
}
return sum
}()
let gcd = gcdForArray(delays)
var frames = [UIImage]()
var frame: UIImage
var frameCount: Int
for i in 0..<count {
frame = UIImage(cgImage: images[Int(i)])
frameCount = Int(delays[Int(i)] / gcd)
for _ in 0..<frameCount {
frames.append(frame)
}
}
let animation = UIImage.animatedImage(with: frames,
duration: Double(duration) / 1000.0)
return animation
}
}
When I render the image as normal png image, the consumption is around 10MB.
The GIF in question has a resolution of 480×288 and contains 10 frames.
Considering that UIImageView stores frames as 4-byte RGBA, this GIF occupies 4 × 10 × 480 × 288 = 5 529 600 bytes in RAM, which is more than 5 megabytes.
There are numerous ways to mitigate that, but only one of them puts no additional strain on the CPU; the others are mere CPU-to-RAM trade-offs.
The method I`m talking about is subclassing UIImageView and loading your GIFs by hand, preserving their internal representation (indexed image + palette). It would allow you to cut the memory usage fourfold.
N.B.: even though GIFs may be stored as full images for each frame (which is the case for the GIF in question), many are not. On the contrary, most of the frames can only contain the pixels that have changed since the previous one. Thus, in general the internal GIF representation only allows to display frames in direct order.
Other methods of saving RAM include e.g. re-reading every frame from disk prior to displaying it, which is certainly not good for battery life.
To display GIFs with less memory consumption, try BBWebImage.
BBWebImage will decide how many image frames are decoded and cached depending on current memory usage. If free memory is not enough, only part of image frames are decoded and cached.
For Swift 4:
// BBAnimatedImageView (subclass UIImageView) displays animated image
imageView = BBAnimatedImageView(frame: frame)
// Load and display gif
imageView.bb_setImage(with: url,
placeholder: UIImage(named: "placeholder"))
{ (image: UIImage?, data: Data?, error: Error?, cacheType: BBImageCacheType) in
// Do something when finish loading
}
In one of my app. i am facing issue of app crash because of i am unable to clean memory of node material diffuse content. when i am trying load node at that time memory is keeping up so i want clear memory whenever remove node from parent. please suggest approbate solution.
Here is below my code:
let recomondationView = viewRecomodation as! THARRecomondationsView
planeGeoMetryP1.firstMaterial?.diffuse.contents = UIImage.imageWithView(view: recomondationView)
oldAnnotationNode.name = name
oldAnnotationNode.geometry = planeGeoMetryP1
let billboardConstraint = SCNBillboardConstraint()
billboardConstraint.freeAxes = SCNBillboardAxis.Y
self.constraints = [billboardConstraint]
self.addChildNode(oldAnnotationNode)
Here is method of convert UIView to UIImage
extension UIImage {
class func imageWithView(view: UIView) -> UIImage {
var image = UIImage()
UIGraphicsBeginImageContextWithOptions(view.frame.size, true, 1.0)
let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
image = renderer.image { ctx in
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
}
UIGraphicsEndImageContext()
return image
}
}
Here is the code that i am using to remove node from parent
if let index = self.sceneNode?.childNodes.index(of: locationNode) {
self.sceneNode?.childNodes[index].geometry = nil
self.sceneNode?.childNodes[index].removeFromParentNode()
}
I am making a custom view of camera, in which i'm taking photos from gallery and from camera, photo gallery works ok, but camera is not working fine, I'm using UIImagePickerController for it, after taking 3 4 pictures it causes memory leaks and shut down the app, i'm properly presentingviewcontroller and dissmissingviewcontroller but it creates memory leaks issues anyway, i Used leak instrument to track down the issue and i found that UIImagePickerController creats new instance every time it appears to take photo
Avfoundation -[AVCapturePhotoOutput init]
NSSmutableArray Avfoundation -[[AVCapturePhotoOutput init]
please guide me how can i resolve it? because I'm not good in managing memory leaks.
Edit:
this is didfinishdelegate method!
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]){
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage{
self.delegate?.didFinishTakingPhoto(image)
picker.dismissViewControllerAnimated(true, completion: { () -> Void in
self.popMe(false)
})
}
}
func didFinishTakingPhoto(image: UIImage)
{
self.imageView.image = image;
self.startActivity("", detailMsg: "")
self.view.userInteractionEnabled = false
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) { () -> Void in
if let chokusItem = self.item {
var size = CGSizeMake(600.0, CGFloat.max)
if Global.shared.highQualityPhotoEnables {
size.width = 900.0
}
let scaledImage = self.imageView.image!.resizedImageWithContentMode(UIViewContentMode.ScaleAspectFit, bounds: size, interpolationQuality: CGInterpolationQuality.High)
let thumbSize = CGSizeMake(80.0, CGFloat.max)
self.thumbImage = self.imageView.image!.resizedImageWithContentMode(UIViewContentMode.ScaleAspectFit, bounds: thumbSize, interpolationQuality: CGInterpolationQuality.High)
self.photo = PhotoViewModel(image: scaledImage, parent: chokusItem)
let delay = 0 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
self.imageView.image = scaledImage
self.stopActivity()
self.removeCommentTableViews()
self.removeCommentViews()
self.view.userInteractionEnabled = true
self.showPhotoLimitAlertIfRequired()
})
if Global.shared.shouldSavePhotoToGallery {
let assetsLibrary = ALAssetsLibrary()
assetsLibrary.saveImage(scaledImage, toAlbum: "Inspection Images", completion: { (url, error) -> Void in
print("success", terminator: "")
}, failure: { (error) -> Void in
print("failure", terminator: "")
})
}
}
}
}
This appears to be some kind of bug in iOS , I am not sure however, I got pointed to this thread after making my own question, since I didn't find this one.
However, I am initializing my code in a completely different way than you but the leaked object appears the same.
I have opened a radar to this on:
https://openradar.appspot.com/29495120
You can also see my question here:
https://stackoverflow.com/questions/41111899/avcapturephotooutput-memory-leak-ios
Hope this answer atleast gives you some less headache wasting time while I am awaiting a confirmation.
In the function below(didPressTakePhoto), I am trying to take a series of pictures(10 in this case), store them into an array and display them as an animation in the "gif". Yet the program keeps crashing and I have no idea why. This is all after one button click, hence the function name. Also, I tried taking the animation code outside the for loop, but the imageArray would then lose it's value for some reason.
func didPressTakePhoto(){
if let videoConnection = stillImageOutput?.connectionWithMediaType(AVMediaTypeVideo){
videoConnection.videoOrientation = AVCaptureVideoOrientation.Portrait
stillImageOutput?.captureStillImageAsynchronouslyFromConnection(videoConnection, completionHandler: {
(sampleBuffer, error) in
//var counter = 0
if sampleBuffer != nil {
for var index = 0; index < 10; ++index {
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer)
let dataProvider = CGDataProviderCreateWithCFData(imageData)
let cgImageRef = CGImageCreateWithJPEGDataProvider(dataProvider, nil, true, CGColorRenderingIntent.RenderingIntentDefault)
var imageArray: [UIImage] = []
let image = UIImage(CGImage: cgImageRef!, scale: 1.0, orientation: UIImageOrientation.Right)
imageArray.append(image)
imageArray.insert(image, atIndex: index++)
self.tempImageView.image = image
self.tempImageView.hidden = false
//UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
var gif: UIImageView!
gif.animationImages = imageArray
gif.animationRepeatCount = -1
gif.animationDuration = 1
gif.startAnimating()
}
}
})
}
}
Never try to make an array of images (i.e., a [UIImage] as you are doing). A UIImage is very big, so an array of many images is huge and you will run out of memory and crash.
Save your images to disk, maintaining only references to them (i.e. an array of their names).
Before using your images in the interface, reduce them to the actual physical size you will need for that interface. Using a full-size image for a mere screen-sized display (or smaller) is a huge waste of energy. You can use the ImageIO framework to get a "thumbnail" smaller version of the image from disk without wasting memory.
You are creating a new [UIImage] in every iteration of the loop, so in the last iteration there's only one image, you should take the imageArray creation out of the loop. Having said that, you should take into account what #matt answered