iOS: Create UIImage from the background thread? - ios

Apples documentation says:
Because image objects are immutable, you cannot change their properties after creation. Most image properties are set automatically using metadata in the accompanying image file or image data. The immutable nature of image objects also means that they are safe to create and use from any thread.
Link
Also if you look at the answers of this "Thread safety of UIImage" question, it is concluded that it is safe to use them from any thread (at least since iOS 9).
Yet, there are comments that complain about issues, especially about creating UIImages on a background thread.
In my case I'm sure that this leads an issue where animations stop working.
Does anybody have insights on this?

I can't see your code but I suspect this is a UIImage vs UIImageView issue if you're experiencing animation problems.
A UIImage manages image data off screen.
A UIImageView displays your image on to your user interface.
Handling image data off the main thread with UIImage is fine.
Displaying or animating an image with UIImageView is not.

Related

iOS app crashes when loading an image

Goals:
Programmatically load first image (approx 94kb) into an ImageView in the main storyboard from the Assets.xcassets folder (see code below) - works perfect
Then when you load a second image (same size) into the original UIImage it causes the iOS app to crash.
Here is my code:
mainImageView.image = UIImage(named:"FirstImage.png") // load first image, no issues
Then if you programmatically load a second image into the same UIImage it causes the device to throw a low memory warning and the iOS crashes the app:
mainImageView.image = UIImage(named:"SecondImage.png") // load second image
After reading a number of answers on SO and other articles (see below), the best way to manage memory when you are loading multiple images into an animation array is to use the contentsOfFile: imageName instead of the UIImage(named:"FirstImage.png")
See Article here:
http://www.alexcurylo.com/2009/01/13/imagenamed-is-evil/
And secondly Apple states the following:
If a matching image object is not
already in the cache, this method locates and loads the image data
from disk or from an available asset catalog, and then returns the
resulting object. The system may purge cached image data at any time
to free up memory. Purging occurs only for images that are in the
cache but are not currently being used. In iOS 9 and later, this
method is thread safe. Special Considerations If you have an image
file that will only be displayed once and wish to ensure that it does
not get added to the system’s cache, you should instead create your
image using imageWithContentsOfFile:. This will keep your single-use
image out of the system image cache, potentially improving the memory
use characteristics of your app.
https://developer.apple.com/documentation/uikit/uiimage/1624146-init
Lastly, upon receiving the Memory Warning you could also create the following function:
func applicationDidReceiveMemoryWarning(application: UIApplication) {
NSURLCache.sharedURLCache().removeAllCachedResponses()
}
Hope this helps someone else :)

Strange behaviour with iOS UIImageView and App going into background

I have a method, and in this method, there is this conditional:
if (self.sleepingCharacter.objectSprite.image == [UIImage imageNamed:#"sleepingRight.png"])
{....
This normally works fine. But I've noticed, in the iOS simulator, that when I put my App in the background by pressing command-H, and then i bring my app back, this conditional no longer works. Do you know why this would happen?
I tested to see if the code would work if I wrote this:
if (self.sleepingCharacter.objectSprite.image)
{....
And it did work, which means that there is still an image there. Now I am confused.
You are using the == operator to compare the two images. This will only be true of the two images are actually the same hunk of memory (the same pointer).
The UIImage imageNamed: method caches images. So in theory if you call it again and again for the same image name, you will keep getting the same pointer and your code appears to work.
But the image cache can get purged at times due to memory usage. Once the image gets purged, the next call to imageNamed: will return a new image pointer and your check will fail.
You need a better way to see if the two images are the same. One solution is to convert both images to NSData objects using UIImagePNGRepresentation then compare the two NSData objects using the isEqual: method.

Strange bug in iOS 7 UIImage (not showing or showing after delay)

Every time when we add UIImageView or simple UIView with some custom CALayer objects added to "layer" property to view hierarchy, there are some different scenarios:
1) all images (in uiimageviews and calayer) are drawn with random delay
2) some images are drawn, but some not, when we perform a simple touch event anywhere - they appear
3) some images are not drawn and they won't appear after simple touch events, the only way make them to appear is to minimize the app and to expand it again.
The last case appears much less than two others. That problem occurs only on iOS7, iOS 4.3-6.1 is totally OK. We have viewed a lot of possible solutions but they were pretty primitive and none of those helped.
Any help would be appreciated! Thanks in advance!
we solved the problem. For everyone, who has the same problem, - all you need is to create CALayer and UIImageView objects on main thread, while UIImage objects still could be created in background thread. All these fixes are needed only in iOS 7

UIImage being released but CGImage not being released

I appear to have a memory management issue when I create UIImage instances the CGImages are not being released.
I have a paging UIScrollView to scroll through a series of JPG images. Below is my entire class that is a page view in the paging scroll view.
The code is being run on the main thread. The code uses ARC. I have tried loading the images using imageWithContentsOfFile: (returns an autoreleased object) as well as initWithContentsOfFile:(returns a retained object). I have tried #autoreleasepool and using performSelectorOnMainThread to ensure that the code is being run on main thread as suggested in other posts.
When scrolling through the images memory usage just grows until the app gets terminated as illustrated in the screenshot from instruments. Note the high allocation to image io.
Screenshot showing virtual memory usage
In the following screenshot it can be seen that the GTGImageScrollerPageViews, UIImageViews and UIImages are being deallocated. Notice there are Transitory objects for these numbering in the high 300's. However the CGImages are not being released and the number of CGImages living is in the high 400's and 0 Transitory.
Screenshot showing allocations
EDIT: Previously I had been recycling and re-using GTGImageScrollerPageView instances in the ScrollView as is the common pattern for scrollviews like this. In order to simplify while trying to debug this problem I allow the entire GTGImageScrollerPageView to be deallocated after it has been displayed in the ScrollView. As you can see in the second image above, there are only 4 GTGImageScrollerPageView living and 377 transitory, there are also 388 UIImageViews and 389 UIIMages listed as transitory so it appears that the UIImageViews and UIImages are being deallocated fine.
If I manually release the CGImage with CGImageRelease (Commented out in the code below) the CGImages are released. I know that I should not do this because I do not own the CGImage but this is useful to verify that this is where the problem is occurring. The screenshots below show the same code tested in Instruments but with CGImageRelease uncommented.
Screenshot showing virtual memory usage with CGImageRelease used
Screenshot showing allocations with CGImageRelease used
In the profiling outputs where CGImageRelease is used, you can see that the correct number of CGImage objects are Living and Transitory and that memory does not grow unbounded. Furthermore the app does not crash during usage with CGImageRelease used.
If this were some system caching of CGImage then it should release the memory when a memory warning is raised, but it doesn't. Memory just continues to grow.
What here could be causing this unbounded growth in memory?
Here is the code for the page view
EDIT: In response to comments I have updated the code to simplify further thus eliminating distractions such as ivars and properties that are not needed to demonstrate the problem. The problem remains unchanged and the results are the same when profiling. I have also added in the NSLog which also outputs the thread. I do see the dealloc being called as expected on the GTGImageScrollerPageView and it is always thread #1 and the call to displayAspectThumbImage is always on thread #1.
I really dont believe that there is anything wrong with the code that is presented here and this is confirmed by the generous effort of Rob. Something else is causing this but all the code related to loading and displaying the image is here; there is no other code in effect here. The only other notable thing that I could think of raising is that the displayAspectThumbImage method is called from scrollViewDidScroll and scrollViewDidEndDecellerating methods of the scrollview delegate but the fact that the methods are called on the main thread should exclude autorelease issues due to being run on another thread.
I have checked and NSZombies is not enabled and there are no Zombies increasing my memory usage. Indeed when the CGImageRelease method is called the memory usage is pretty flat as can be seen in the screenshot above.
#implementation GTGImageScrollerPageView
- (void)dealloc {
NSLog(#"%s %#", __PRETTY_FUNCTION__, [NSThread currentThread]);
}
- (void)displayAspectThumbImage:(NSString *)path {
NSLog(#"%s %#", __PRETTY_FUNCTION__, [NSThread currentThread]);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];
[self addSubview:imageView];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
[imageView setImage:image];
//If uncommented CGImages are disposed correctly
//CGImageRelease([imageView.image CGImage]);
}
#end
Tested on:
iOS 6.1.4
iOS 5.0.1
Really sorry to be answering this question myself but thought it would be useful to share my experience.
It turned out to be a category on UIImage that was part of a third party library I was using. I wont name the library as I have tested with the latest version and the problem is not there so there is nothing to gain by naming the library.
It is particularly strange as the library was not being used anywhere near the code that was leaking. It was only being used in one place in the whole project, but any time I used a UIImage it seems that it was affecting the UIImage instance. The mere presence of this category in the project was enough. This came as a real surprise.
The way I solved this was to begin by simulating the scenario in a new project and found that the code was not leaking there. I then migrated the code a little at a time and when I moved the categories across the leak appeared.
Huge thanks to Rob for his generous efforts to help me with this and even though he did not provide the solution directly, talking to him was really helpful in solving the problem. Nice to know there are such cool people out there.
I did a simple infinite scroller using your code and after scrolling through nearly 100 images the memory usage was, as one would have expected, thoroughly uneventful:
Looking at your source, I would have recommended a few little things (e.g. I would have put aspectImageView into a private class extension rather than the .h, I assume you're setting pageIndex from the calling routine but I'd add it as a parameter of displayAspectThumbImage, I would have made aspectImageView a weak property and refactor the code accordingly (e.g. create image view, add it as subview, then set the weak imageview property to point to this object), etc.), but none of those have direct bearing on your issue here.
Bottom line, your problem does not rest in the code you've shared with us.
Try to use property instead of ivar.
#property (nonatomic, strong) UIImageView *aspectImageView;

Strange issue with loading an UIImage from core dara directly using KVC

I am in the middle of an app project with core data where I stricty use KVC only with regard to the NSManagedObjects. The purpose of consequentally doing that is simply to get an understanding of advantages and disadvanages. I may have discovered a disadvangate. However, I cannot explain why.
I came across this when I used some very well established principle (used that in other apps already) of saving a scetch that the user drew with his fingers (a signature to a document) in core data and fetch and display or print to PDF respectively it later.
It turned out that I was able to get the UIImage from the view, store it in core data and instanly receive it from core data again using KVC (no save/fetch in between) and display it in another view for testing purposes. But I was unable to actually save it, fetch the data from persistant storage and re-display it on the screen.
So I did a lot of debugging, analysed the sqlite file etc. Eventually I nailed it down to the following strange behaviour:
Situation:
The image was stored into core data successfully by doing this:
[self.detailItem setValue:UIImagePNGRepresentation(image) forKey:kSignatureImage];
with image being an UIImage object and self.detailItem is the NSManagedOBject. Keys and references and everything is fine.
Then it comes to displaying the view again. The following is independent from whether the context was saved in between or even the app was closed and restartet or not:
self.signatureCanvas.image = [UIImage imageWithData:[self.detailTextLabel valueForKey:kSignatureImage]];
with self.signatureCanvas beeing an UIImageView subclass object. The image is not shown on screen.
NSData *data = [self.detailItem valueForKey:kSignatureImage];
NSLog(#"UIImage a: %#", [UIImage imageWithData:[self.detailTextLabel valueForKey:kSignatureImage]]);
NSLog(#"UIImage b: %#", [UIImage imageWithData:data]);
self.testImage.image = [UIImage imageWithData:[self.detailTextLabel valueForKey:kSignatureImage]];
self.signatureCanvas.image = [UIImage imageWithData:data];
self.testImage is a second plain UIImageView withn the same view (but smaller). Now the image is visible in signatureCanvas but not in testImage. (could be vice versa, tried that)
The output give a hint on what was happening but no actual explanation:
2013-03-22 12:43:37.746 MyApp[1595:c07] UIImage a: (null)
2013-03-22 12:43:37.769 MyApp[1595:c07] UIImage b: <UIImage: 0x7c973a0>
Until now I would have thought that the lines of code were kinda equivalent and that the compiler may even optimize the data object away. But why is the image (null) in one case and not in the other.
So I found the problem root cause and a workaround. Therefore this is not urgent any more. But I do not understand it. Any clues?
Environment is: SDK 6.1, xcode 4.6, ARC, core data on sqlite, iPad only so far. All this happens in a UITableViewCell subclass (prototype cell within storyboard) naturally in a UITableViewController subclass.
I assume that self.detailTextLabel in
self.testImage.image = [UIImage imageWithData:[self.detailTextLabel valueForKey:kSignatureImage]];
should be self.detailItem.

Resources