Quartz caching CGLayer - iphone

To quote from the CGLayer doc:
Quartz caches any objects that are reused, including CGLayer objects.
I am having problems with memory on large pages and am trying to implement a simple mechanism where views are released and recreated based on whether they are on/off screen.
Say for the sake of simplicity that I have a bunch of UIImages, created as the result of a network request, saved in an array somewhere. I create a UIImageView like so:
anImage = [anArray objectAtIndex:0];
UIImageView* imgView = [[UIImageView alloc] initWithImage:anImage];
[mainView addSubview:imgView]; // Quartz eats memory for view after first draw
[imgView release]; // owned by mainView now
[...] // wait a bit for draw cycle
[imgView removeFromSuperview]; // memory doesn't go down
When the imgView goes offscreen it is removedFromSuperview and released. Fine right? Nope- the CGLayer that exists in Quartz is not removed, because anImage still exists.
How can I get around this? The only way in this scenario is to create an image exactly the same behind Quartz's back with a different pointer address and delete the old image. And the only way to do this is to 'deep copy' the image (UIImage doesn't implement NSCoding) or to ask for it again over the network (slow).
What I am thinking is that I need to sqllite my images to a database and refetch them every time a view comes onscreen- but I would love to hear people's thoughts on this.

Here you increment imgView from 0 to 1.
UIImageView* imgView = [[UIImageView alloc] initWithImage:anImage];
In the next line, the mainView incrementes the reference count. (now it's 2)
[mainView addSubview:imgView]; // Quartz eats memory for view after first draw
Here, you release the imgView and the reference count goes back down to one.
[imgView release]; // owned by mainView now
I don't think your memory issues have anything to do with anImage. As long as imgView is a subview, it won't free that object, because it needs that object to draw to the screen.
What the following line means, is that if you programmatically draw to your CGLayer, Quartz with cache what you've drawn, so that you aren't constantly redrawing the same thing. It's not really related to adding subViews.
Quartz caches any objects that are reused, including CGLayer objects.

Related

Releasing images not created with init

Hi every one I have the next issue I'm creating various images like this
UIImage *image = [UIImage imageNamed:_imageName];
and I'm assigning to one UIImageView
[self.imgvImage setImage:image];
The user when tap a button one UIImageView is created and one UIImage is assigning, I have 4 UIImageViews created when new is created the last is removed sending release to the UIImageView like this
[_imgvImage release];
But around than 100 times creating and releasing one memory warring happen but the app dont crash I think this is because the UIImages created are not releasing and there are so much because all the code are clean and creating and releasing are all fine.
How can I remove all the UIIMageView with his UIImage completely from the memory.
Please help, sorry for my English in not good.
The image created using below statement is handled by iOS. this image is stored in a cache so that next time you call this it is simply get returned from the cache.
UIImage *image = [UIImage imageNamed:_imageName];
I think you should not worry about memory warning as long as you are releasing what you are allocating (UIImageView in this case). iOS should clear this cache in case of memory crunch.
If you do not want that caching (e.g. when you are loading one image once only) you can use imageWithContentsOfFile: method of UIImage.
Hi I found the solution the solution was call the
NSAutoreleasePool
in the method when I removed the objects of the view like this:
for(ProductVW *pVW in [_vwProductsContainer subviews]){
NSAutoreleasePool * autoreleasePool = [[NSAutoreleasePool alloc] init];
[pVW removeFromSuperview];
[autoreleasePool release];
}

Does UIImageView have a property to indicate setImage has completed and animations are ready?

Just getting started with Obj-C and iOS programming. I have some code that loads and imageView into a (HIDDEN: YES) UIView - simply
[bgImageView setImage:[UIImage imageNamed:#"Filename.jpg"];
The code then sets and commits animations on the fgImageView and the bgImageView.
Because some of the images can be large, occasionally the animation from the new bgImage does not render with the new background image in it and instead 'stalls and displays'. Note that the stall does not happen during setImage. It happens during the second animationCommit later in code. Because animationCommit forces the application to wait until the animation of this ImageView is done.
What I'm looking for is sort of a 'setImage commit' on UIImageView,
I'd much rather display a spinner until the image is loaded, then proceed with the animation, however there doesn't appear to be an isLoaded property for UIImageView class.
Is there a simple way to determine that the UIImageView is done with the setImage call?
My gut is that setImage is handled synchronously, especially since you're talking about how the app stalls. You'd want to run setImage in an NSOperation to allow that happen in the background.
http://developer.apple.com/library/ios/#documentation/cocoa/reference/NSOperation_class/Reference/Reference.html
NSInvocationOperation *myOp = [[NSInvocationOperation alloc] initWithTarget:myImageView selector#selector(setImage:) object:aUIImage];
[myOp addObserver:(NSObject *)self forKeyPath:(NSString *) #"isFinished" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:NULL];
[anOpQueue addOperation:myOp];
You'd then handle the KVO operation and handle removing the spinner there.
[UIImageView setImage:] is not asynchronous: It's done immediately before the message returns. If the view does not show the image instantly it's probably because of some lazy UIImage code. My guess is that [UIImage imageNamed:] just references the file in the bundle and loads it only after the image data is requested.
You could try to force the image to load by sending it a CGImage message:
[bgImageView.image CGImage];
If this still does not trigger the image to be loaded you have to draw it somewhere:
UIGraphicsBeginImageContext((CGSize){1, 1});
[bgImageView drawInRect:(CGRect){{0, 0}, {1, 1}}];
UIGraphicsEndImageContext();
There is a method in UIImageView - (BOOL)isAnimating, so you could potentially use:
if (![bgImageView isAnimating]) {
// continue to next image;
}
Would that suffice or is there other animation in progress that would give false responses?

ScrollView with large images scrolls too slowly

When I am scrolling images frequently in a UIScrollView then after some images, the next image takes time to load... it's not taking too much time but looks odd.
Suppose I have 27 images in a scrollView. When I start to scroll these images, for 1 or 2 images it scrolls smoothly but when I scroll again to see the 3rd image it takes time to load. Then when I start the images scrolling again from the 3rd image, it behaves like before.
I can't load all 27 images at a time or my app crashes.
When I slowly scroll the scrollview then I don't have this problem.
My code is below:
//Taking image view for 27 images;
int x=0;
for(int i = 1; i<=27; i++) {
imageView = [[UIImageView alloc] init];
imageView .frame = CGRectMake(x,0,768,1024);
imageView.tag=i;
imageView.image=nil;
imageView.userInteractionEnabled=YES;
[contentView addSubview:imageView];
x+=768;
}
//setContentOffset of the scrollView -->ContentView
[contentView setContentOffset: CGPointMake((imageNumber-1)*768, 0) animated: YES];
//desire image which i want to see from the start of the scrollview
pageNumber=imageNumber;
int pageBefore=pageNumber-1;
int pageAfter=pageNumber+1;
//Views for image
for( UIImageView * views in [contentView subviews]){
if(views.tag==pageNumber){
if(views.image==nil){
NSLog(#"entering");
views.image=[UIImage imageNamed:[ NSString stringWithFormat:#"%d.jpg",pageNumber]];
[views.image release];
}
}
if(views.tag==pageBefore){
if(views.image==nil){
views.image=[UIImage imageNamed:[ NSString stringWithFormat:#"%d.jpg",pageBefore]];
[views.image release];
}
}
if(views.tag==pageAfter){
if(views.image==nil){
views.image=[UIImage imageNamed:[ NSString stringWithFormat:#"%d.jpg",pageAfter]];
[views.image release];
}
}
My alarm bells rang when I saw this;
imageView .frame = CGRectMake(x,0,768,1024);
Apart from the space before .frame, are you saying that your images are 768x1024? That's HUGE and I suspect your problems are memory ones rather than code ones.
Be aware that in particular, using UIImage imageNamed: is likely to cause grief with such large images as that method caches the images in memory. You may wish to consider using alternative methods that load the image from a file each time.
You should try use the EGOImageView, it has caching build in which might help with your performance issues. You can implement a placeholder image to show the user that an image is being prepared for viewing. The image will load in another thread before being displayed, giving you smoother scrolling performance. The EGOImageView is part of the EGOImageLoading library.
https://github.com/tastefulworks/EGOImageLoading
As an alternative you could create your own lazy loading mechanism to increase scrolling performance. E.g. once a user stops scrolling for a second, start loading the image, otherwise display placeholder image if not yet the correct image is cached.
Edit: when thinking more about this issue, I realize caching won't help much (since you already load image from disk), but the asynchronous loading of images should help with the scroll performance, so make use of NSThread or NSOperation to load the image in a background thread, then notify the main thread that the image is loaded and ready for display.

setting image to button -> take up lots of memory ? what should i do then

basically im running my apps with instruments and found out that by just setting a background image to the UIButton, it takes up 6mb of data(which i do not want in case low-memory warnings). i read around and found out that since the button has been assigned the image, it retains it(and the memory).
How should i code it then?My current codes are as below. Btw im new to iPhone development so please tell me what to do.
btw this button would just bring me to another view. is there anyway to release the memory that was allocated to this image?
.m file
-(void)viewDidLoad{
UIColor *background = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"MainScreen.png"]];
selectionScreen.backgroundColor = background;
[background release]
}
You mentioned in comment above that your .png is under 300k. That's perhaps a touch big, but you're actually not looking at the right thing. A png gets expanded to a native CGImage object. I usually figure a 32-bit image with alpha takes up width * height * 4 bytes of memory. That's pretty much guaranteed to be bigger than the PNG it gets expanded from, and in your case could be quite big indeed. Enough so that the docs recommend not instantiating UIImages bigger than 1024 x 1024.
Now, one solution could be that -initWithPatternImage can take a small piece of your background, and will tile it when it's drawn. So your first shot at solving this would be to provide that method as small an image as possible, and let it tile to bigger sizes.
Second thing, the retention. You're correctly releasing your UIColor object after setting it on the background. You WANT that object you set it on to retain it! In a world of infinite memory, you'd want that button to retain its background color until the viewcontroller it's on gets dealloc'ed. If it's still huge and you really have to get rid of it before backing out of the view controller (say when you push to a new UINavigationController view or something), you could try setting background to nil (or a system default color maybe) in -viewDidDisappear and re-building your background in -viewWillAppear.
Wheb viewWillDisappear, you can set backgroundColor as another color, and release the background color you made.
-(void)viewWillDisappear:(BOOL)animated
{
// release original backgroundColor
// default backgroundColor is nil by UIView class reference.
selectionScreen.backgroundColor = nil;
}
Hope this can help you.
Did you try using something like:
button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setFrame:/frameOfChoice/];
[button setBackgroundImage:[UIImage imageNamed:#"MainScreen.png"] forControlState:UIControlStateNormal];
I'm not sure how this effects the memory usage tho.

Adding UIImage with the same image multiple times and removing it all in one code

Im having a bit of trouble right now adding the same image multiple times and removing it all at one time after the gamelooop.
I manage to add the image by creating 1 UIImageview for every image but i know thats its not the practical way memory wise since im allocating a lot of uiimageview with the same image.
I use this code to load the image:
UIImageView *imgView1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"correct.png"]];
UIImageView *imgViewn = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"correct.png"]];
CGRect imgRect = CGRectMake((touch.x-20), (touch.y-20), 40, 40);
[imgView1 drawInRect:imgRect];
[imgViewn drawInRect:imgRect];
[imgView1 release]
[imgViewn release]
Basically i use this to add image base on the location of the usertouch made on the subview (with image). If the users set of touches are correct this is where the game loops and show another image for the user to touch.
But after the new image load the "correct.png" image which is added on the previous touch is still on view.
Can someone show me a correct way to do this coz im kinda new to programming but i know that im gonna have a memory problem later if i keep on allocating image every time a user touch the screen.
Thanks in advance.
If you're trying to merely move an image (or series of images) through each run of the gameloop, rather than creating and removing them all each time, perhaps create the series of N images only once.
When the touches or gameloop begins, draw the images in the rectangles you're calculating from the touch event, and then when the loop is over, set the hidden property on the object to YES or NO (not sure exactly what your objective is).
It's hard to give you code without more details on what you're doing, but the basic idea is this:
When you first start the program/view, create your N images, and set imageView.hidden = YES;
On each loop, call the [imageView drawInRect:imgRect] method for each image, and set imageView.hidden = NO;.
When the view/program is done, call the [imageView release] method on each object.