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.
Related
I have successfully implemented an Image Picker in my app - it pulls pictures from the library and you can select specific images.
Now after the user selects the image, I want to store the image to a variable somehow in my code so that I can upload the image to a database.
Here's what I currently have in my code:
public class PhotoPickerService : IPhotoPickerService
{
TaskCompletionSource<Stream> taskCompletionSource;
UIImagePickerController imagePicker;
public Task<Stream> GetImageStreamAsync()
{
//Create and define UIImagePickerController
imagePicker = new UIImagePickerController
{
SourceType = UIImagePickerControllerSourceType.PhotoLibrary,
MediaTypes = UIImagePickerController.AvailableMediaTypes(UIImagePickerControllerSourceType.PhotoLibrary)
};
//Set Event Handlers
imagePicker.FinishedPickingMedia += OnImagePickerFinishedPickingMedia;
imagePicker.Canceled += OnImagePickerCancelled;
//Present UIImagePickerController
UIWindow window = UIApplication.SharedApplication.KeyWindow;
var viewController = window.RootViewController;
viewController.PresentViewController(imagePicker, true, null);
//Return Task Object
taskCompletionSource = new TaskCompletionSource<Stream>();
return taskCompletionSource.Task;
}
void OnImagePickerFinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs args)
{
//assigns var image to the edited image if there is one, otherwise it'll assign it to the original image
UIImage image = args.EditedImage ?? args.OriginalImage;
if (image != null)
{
//Convert UIImage to .NET stream object
NSData data;
if(args.ReferenceUrl.PathExtension.Equals("PNG") || args.ReferenceUrl.PathExtension.Equals("png"))
{
data = image.AsPNG();
//Console.WriteLine(data);
}
else
{
data = image.AsJPEG(1);
Console.WriteLine(data);
}
Stream stream = data.AsStream();
UnregisterEventHandlers();
taskCompletionSource.SetResult(stream);
}
else
{
UnregisterEventHandlers();
taskCompletionSource.SetResult(null);
}
imagePicker.DismissModalViewController(true);
}
void OnImagePickerCancelled(object sender, EventArgs args)
{
UnregisterEventHandlers();
taskCompletionSource.SetResult(null);
imagePicker.DismissModalViewController(true);
}
void UnregisterEventHandlers()
{
imagePicker.FinishedPickingMedia -= OnImagePickerFinishedPickingMedia;
imagePicker.Canceled -= OnImagePickerCancelled;
}
Here is what I'm wondering: I need to upload this image to a database with the datatype varbinary. The image currently has the datatype NSData which as I've researched is used to store items such as pictures with raw binary data. Am I storing this correctly as of right now to upload to a database? Do I need to do some sort of conversion?
You should implement UIImagePickerControllerDelegate to your ViewController
import UIKit
import AVFoundation
import MobileCoreServices
class ViewController: UIViewController {
var choosenImageData: Data?
fileprivate lazy var imagePicker: UIImagePickerController = {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.mediaTypes = [(kUTTypeImage as String)]
imagePicker.allowsEditing = false
return imagePicker
}()
#IBAction func cameraButtonPressed(_ sender: Any) {
self.imagePicker.sourceType = .camera // .photoLibrary
self.present(self.imagePicker, animated: true, completion: nil)
}
}
extension ViewController: UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
self.choosenImageData = image.jpeg(.lowest)
}
}
}
Use this extension to convert UIImage to Data
import Foundation
// MARK: - Compress UIImage Quality
extension UIImage {
enum JPEGQuality: CGFloat {
case lowest = 0
case low = 0.25
case medium = 0.5
case high = 0.75
case highest = 1
}
/// Returns the data for the specified image in JPEG format.
/// If the image object’s underlying image data has been purged, calling this function forces that data to be reloaded into memory.
/// - returns: A data object containing the JPEG data, or nil if there was a problem generating the data. This function may return nil if the image has no data or if the underlying CGImageRef contains data in an unsupported bitmap format.
func jpeg(_ quality: JPEGQuality) -> Data? {
return self.jpegData(compressionQuality: quality.rawValue)
}
}
After you have successfully retrieved selected image data, you can perform your tasks to save/upload to your online or local DB.
This answer is an alternative that you might consider instead of using UIImagePicker
Here this function will allow you to get the photos on the library on the phone, so you can use the result of this function which will be an away of [UIImage] and store it on a variable I understand you need to do. but to actually check what image what the user to upload you will need to build a CollectionView for example and give the user the option to select the photos to upload, will the user selecting the photos from the collection view you can save the index that you can use to define which pictures to upload.
Note: you can specify the request option: (the delivery mode defines the quality of the image you can use fastFormat , opportunistic or highQualityFormat)
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .opportunistic
This is the function just a copy past on your project will do the trick
private func getPhotos() -> [UIImage]? {
// Image Array declaration
var imageArray = [UIImage]()
// Image manager and specify the request option
let imageManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .opportunistic
// Sorted by creation date
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
// Perform the fetch and appand to Image array
if let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions) {
if fetchResult.count > 0 {
for i in 0..<fetchResult.count {
imageManager.requestImage(for: fetchResult.object(at: i), targetSize: CGSize(width: 280, height: 280), contentMode: .aspectFill, options: requestOptions) { image, error in
imageArray.append(image!)
}
}
return imageArray
} else {
print("You dont have any photos!")
}
}
return nil
}
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 use Kingfisher to load images into the Card View (UIView), as in the tinder, I set the Kingfisher to cache the images to disk, but during the paging of the cards, the memory consumption increases dramatically from 39 megabytes to 247 and after a while again becomes 39, But the problem is that when the memory is freed, in the application for a second UI slows down (I think this is blocked by the main thread). This problem is similar to this one. How can I fix it?
I set the settings for Kingfisher in AppDelegate in didFinishLaunchingWithOptions
fileprivate func setupKingfisherSettings() {
let megabytes: UInt = 300
ImageCache.default.maxDiskCacheSize = megabytes * 1024 * 1024
ImageCache.default.maxMemoryCost = 1
}
Snippet of code. When I removed this code, this problem did not occur.
private func downloadImages(_ card: CardModel) {
if let placeAvatarURLString = card.photoURLsProperties.placePhotoURLs.first {
if let placeAvatarURL = URL(string: placeAvatarURLString) {
venueImageView.kf.indicatorType = .activity
venueImageView.kf.setImage(with: placeAvatarURL)
} else {
venueImageView.image = UIImage(named: "CardDefaultImage")
}
} else if let eventLogoURLPath = card.photoURLsProperties.placeLogoURLs.first {
if let url = URL(string: eventLogoURLPath) {
venueImageView.kf.indicatorType = .activity
venueImageView.kf.setImage(with: url)
} else {
venueImageView.image = UIImage(named: "CardDefaultImage")
}
} else {
venueImageView.image = UIImage(named: "CardDefaultImage")
}
}
Updated:: I found a regularity when this happens. This jump in memory occurs when ImageCache extracts an image that is larger than 5 megabytes. I found it using debugPrint in this method diskImage, if the image is equal to or more than 5 megabytes, then there is a jump, if 4 megabytes, then everything is fine.
I'm testing on the iPhone 7, more than 74 gigabytes of free memory.
func diskImage(forComputedKey key: String, serializer: CacheSerializer, options: KingfisherOptionsInfo) -> Image? {
if let data = diskImageData(forComputedKey: key) {
debugPrint("ImageCache data.count", data.count / 1024 / 1024)
return serializer.image(with: data, options: options)
} else {
return nil
}
}
At the moment I solved my problem, I wrote my Custom CacheSerializer, and I compress the images up to 3 megabytes. But I'm interested in other answers, how to solve this problem.
import Kingfisher
struct AppNameKingfisherCacheSerializer: CacheSerializer {
func data(with image: Image, original: Data?) -> Data? {
return image.compressToData(3)
}
func image(with data: Data, options: KingfisherOptionsInfo?) -> Image? {
return UIImage(data: data)
}
}
I've been wrestling with this issue ad nauseam but still cant figure out what is causing the crash. I instantiate the camera from a button, thereafter when the user chooses the photo i add the photo to my images array and reload the collectionView. After approximately 20 times i get the output in the console "Connection to assets was interrupted or assets died" subsequently followed by a crash. When i remove the collectionView?.reloadData() method from within the delegate method "didFinishPickingMediaWithInfo" it doesnt crash. This has left me extremely obfuscated. See code below:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: Dictionary) {
if let image = info["UIImagePickerControllerOriginalImage"] as? UIImage {
images.append(image)
// when i remove this method the app doesnt crash!
collectionView.reloadData()
collectionView?layoutIfneeded()
dismiss(animated: true, completion: nil)
}
This is the solution, note the compression size is hardcoded, you may want to adapt the code to your needs or refactor and improve the method.
func imageCompression(_ imageDict: [String : UIImage]) -> [String : Data] {
var compressedImages = [String : Data]()
for (key, value) in imageDict {
var resizeAttempts = 5
var compressionRatio: CGFloat = 1
var imageData = UIImageJPEGRepresentation(value, compressionRatio)
if imageData?.count <= 90000 {
compressedImages[key] = imageData
} else {
while imageData?.count > 90000 && resizeAttempts > 0 {
resizeAttempts -= 1
compressionRatio = compressionRatio * 0.5
imageData = UIImageJPEGRepresentation(value, compressionRatio)
compressedImages[key] = imageData
print("image size now is \(imageData?.count)")
}
}
}
return compressedImages
}
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.