0

I'm using CGImageRef and noticed that it uses a lot of memory that doesn't get deallocated.
So I tried experimenting with the following code

- (void)photofromAsset:(ALAsset *)asset completion:(void(^)(NSError *error))completionHandler
    ALAssetRepresentation *representation = asset.defaultRepresentation;
    UIImageOrientation orientation = (UIImageOrientation)representation.orientation;
    CGImageRef fullResolutionImage = representation.fullResolutionImage;

    //UIImage *fullImage = [UIImage imageWithCGImage:fullResolutionImage scale:0.0 orientation:orientation];
    //[self startUpload:fullImage completion:completionHandler];
}

put some breakpoints and put the first three lines of code in an @autorelease pool.
Then tried removing the @autorelease and called

CGImageRelease(fullResolutionImage);

When I get to UIImageOrientation my app is using less than 30MB but as soon as I call CGImageRef it gets more than 80MB.
Both memory freeing methods only get me to 50MB, so there's an extra 20MB somewhere.
Those extra 20MB are freed only when the whole method gets completed.

Where are those extra 20MB from?
How can I free them before calling startUpload: ?

Thank you

halfblood17
  • 657
  • 1
  • 6
  • 21
  • 1
    "Those extra 20MB are freed only when the whole method gets completed" But if they are then freed, what do you care? – matt Apr 08 '15 at 18:32
  • I agree with Matt. That having been said, CoreGraphics calls can be tough to catch memory problems, but the static analyzer ("Analyze" on the Xcode "Product" menu) is pretty good at catching anything that slipped through. Make sure you have a clean bill of health from the analyzer. – Rob Apr 08 '15 at 18:36
  • @matt the problem is that before that method ends I'm calling the startUpload: method which is quite long. At the same time I'm calling photofromAsset: many times (even hundreds) so before the 20MB are freed another 10 images will be occupying 10x20 MB and that causes my app to crash. – halfblood17 Apr 08 '15 at 18:49
  • 1
    "I'm calling photofromAsset: many times" So what if you wrap _that_ call in an autorelease pool and drain it? – matt Apr 08 '15 at 18:58

2 Answers2

0

You don't need to release manually CGImageRef, as an old CF opaque type it follows the rules of ownership, you need to care about memory management only if in the function/method name there is Create or Copy.
About your problem is difficult to understand what -startupload does and where you call -photoFromAsset. My suggestion is if you -photoFromAsset is called inside a loop, wrap what's inside the loop in an @autorelasePool.
Second is probably your startUpload (I imagine that is uploading something to a server) that continues to keep a strong reference to the image itself by decompressing it and sending to the server.
To avoid that also to avoid opening a lot of connection my suggestion is to create a sort of a queue for network operations and send just 2 images "at once".

Andrea
  • 26,120
  • 10
  • 85
  • 131
  • "My suggestion is if you -photoFromAsset is called inside a loop, wrap what's inside the loop in an @autorelasePool" Yes, that was _my_ suggestion too. – matt Apr 08 '15 at 19:16
  • Then why is there a memory improvement when calling it? The problem is that the upload I'm doing needs to be able to continue working in background. That's why I'm starting all the uploads as soon as possible, so the OS knows about them and the user can exit the app without stopping the file uploading. `-startUpload:` sets up the request for that photo, starts the upload and returns only once the photo has been uploaded. `-photoFromAsset:` is already in a @autorelasepool – halfblood17 Apr 08 '15 at 19:16
  • @matt seeing that we agree is great honor for me, I'm a big fan, I have all your books, sorry for the OT, but I'm exited to have a comment from you. – Andrea Apr 08 '15 at 19:19
  • @halfblood17 Are you using any network library? because in my opinion without enqueuing the request you use a lot of resources in general and could also lead to networking problem. If you care about the user closing the app, you should use all the specific method given by the OS, such as the beginBackgroundTaskWithName or better backgroundSessionConfigurationWithIdentifier: for the NSURLSession. When I need to send images I usually collect them and save them in a temp dir, them I send them not more that once at time as chucked data. – Andrea Apr 08 '15 at 19:28
  • I'm using AFNetworking 2.0 but i just realized that probably the main problem are the completionHandlers. I need to get some stuff done when the upload completes but I can't wait for that to happen to free up the resources. Probably the best thing would be to use the basic NSURLSession so I can use delegates. What do you think? Could that be the problem? – halfblood17 Apr 08 '15 at 19:45
  • 1
    @halfblood17 Your problems all seem to stem from the fact that you are uploading an image (a UIImage) which is held _in memory_ (the variable `fullImage`, commented out in your quoted code). That seems a very silly thing to do: images are huge. Why don't you _write_ the UIImage to disk, and that way you can upload the _file_ (from disk), without holding _anything_ in memory? – matt Apr 08 '15 at 21:31
  • I write it in memory before uploading it. In my startUpload: method I'm creating the body of the request using that image and then writing that body on the disk. Then, using the disk copy, I start the upload with `uploadTaskWithRequest:fromFile:progress: completionHandler:` The problem is that only when the upload is complete the completion handler gets called, and it's here that I call the first completionHandler terminating `-photoFromAsset:` So my guess is that this whole design is flawed for the background upload since it works only with serialized uploading. – halfblood17 Apr 08 '15 at 21:37
  • But if you do what I am saying to do, you will have _released_ the memory you were using in your first four lines. So now it doesn't matter when the completion handler is called. The memory is no longer being held. – matt Apr 09 '15 at 00:30
  • I totally agree with matt, you should save your images to disk once you get your asset, and start the upload using the path to your file by creating a multipart POST request, in this way you the body of the requeste will be treated as a stream, with a very low impact on memory. Also better if you use the method provided in AFHTTPSessionManager to create the POST request directly. – Andrea Apr 09 '15 at 06:27
0

Your problems all seem to stem from the fact that you are uploading an image (a UIImage) which is held in memory (the variable fullImage, commented out in your quoted code). That seems a very silly thing to do: images are huge, and uploading takes time, so you are forcing your program to hold the entire image in memory for as long as it takes to upload it.

Instead, why don't you immediately write the UIImage as file to disk, and that way you can let go of the image from memory and instead upload the file (from disk), without holding anything in memory?

matt
  • 515,959
  • 87
  • 875
  • 1,141