OK. I'll be looking for the answer, and may find it myself. I have a nasty habit of answering my own questions.
In any case, I have an app that is designed to be "skinned" fairly easily. As part of that, I have sequestered methods in a static class that is specific to variants. These static methods feed the main app images, colors and settings specific to the variant. The .h file is common to the main app, but the .m file is specific to the variant.
I like to use the ability to send an image as a background (which is automagically tiled), so the interface file specifies the routine as returning a UIColor, like so:
+ (UIColor *)meetingDetailBackgroundColor;
But the implementation file loads an image file, and returns that, like so:
+ (UIColor *)meetingDetailBackgroundColor
{
return [UIColor colorWithPatternImage:[UIImage imageNamed:#"DarkWeave.png"]];
}
Which is used in context, like so:
[[self view] setBackgroundColor:[BMLTVariantDefs meetingDetailBackgroundColor]];
NOTE: Edited to restore the original simple code I used.
Now, the issue is that I sometimes (not always) get a leak.
I'm sure that I'm doing something hinky here. I'm just not sure what.
Any ideas?
BTW: This is an ARC program, running on IOS 5. I'm new to ARC, but I think this is the way I'm supposed to do it.
UIColor colorWithPatternImage is buggy, do not use it. My experience is that it tends to greatly cripple performance on the device but not in the simulator. Anything like scrolling or animation on top of it tends to get slow. I'm not sure whether this really qualifies as a leak, I'm not seeing App being killed because RAM ran out. But if you profile the app, you will see that the app runs much slower with UIColor colorWithPatternImage enabled and drawing something.
Eventually I created a subclass of UIView, and did something like this:
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(c, kCGBlendModeCopy);
CGContextDrawTiledImage(c, CGRectMake(0, 0, bkgnd.size.width, bkgnd.size.height), bkgnd.CGImage);
}
This will tile the image. I then either use self.tableView.backgroundView or [self.view insertSubview:bkgnd atIndex:0] to make it a background. It runs much faster on the device, and causes zero memory leaks.
The best way to initilise your shared colour is like this:
+ (UIColor *)color
{
static UIColor *color;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
color = [[UIColor alloc] init...];
});
return color;
}
It is thread safe and only initialises the colour once. That way there is no way that you can leak the colour.
OK. I addressed this, but I did not fix it.
I punted.
It is, indeed, Instruments making wild guesses. There was nothing wrong with the way I specified the color (however, I liked one of the suggestions here, and redid my color spec that way).
It seems to be a very small leak, buried somewhere deep in the bowels (and I mean that literally) of MapKit. There seems to be absolutely nothing that I can do to resolve it.
After several hours of brick wall/neocortex interaction, I just gave up, and made the screen controller object reusable. I simply keep it hanging around, and change the contents to suit the meeting being inspected.
That puts off the leak until the app closes.
I am such a wimp.
Thanks.
Related
I have a UIViewController that I'm nesting inside another UIViewController (iOS 4.3+),
it is displayed just fine except one thing - a lot of excessive shadow.
I have tried removing it with setShadowRadius etc, but no luck..
This is the code I use to create it:
RDPreviewViewController* preview = [[[RDPreviewViewController alloc] initWithNibName:#"RDPreviewViewController" bundle:[NSBundle mainBundle]] autorelease];
[preview.view.layer setShadowOpacity:0.0];
[preview.view.layer setShadowRadius:0.0];
[preview.view.layer setColor:nil];
[preview.view setFrame:CGRectMake(0, 100, 320, 264)];
[self.mainView addSubview:preview.view];
And here's the result:
How do I remove it?
I suspect - and this is a theory - that given what you've said that somehow your PNG image with the subtle shadow is being loaded multiple times. This is why your shadow appears much darker than you're expecting - several identical PNGs are being overlaid on top of each other.
The reason I think this is the case is that judging from the code you've posted you're not programatically applying a shadow, and views do not have a shadow by default. Of course, perhaps you are adding a shadow in your code elsewhere, but based on my own experience I think it looks as if somehow the same view (your image view) is getting added multiple times.
It might be helpful if you shared more of your code, if possible.
I've been reading everything I can find on here about this topic but am still not sure the best way to proceed. I have a heavy UIImageView that uses an array of fat UIImages acting as an animated loop. This UIImageView is serving as the background for every screen in the app. (Client's request, not mine, so don't hate.) We've optimized the png files as small as they can go but it's still a pretty heavy load.
I've read several posts about how UIImage searches the cache for an existing image of that name (Ex. Shared UIImageView for background image throughout app - Singleton Property) but this doesn't seem to be happening. I've also been reading here about singletons and instantiating in the appdelegate but I'm not sure if either of these are the right way to proceed.
What's the best way to load the UIImageView once over the life of the app and use it in the background of every viewcontroller? Btw, because it takes several seconds to load, I'm going to be adding a "loading" page at app start that uses a single static image and an activity indicator.
Also, I'm not as familiar with testing and performance tools. Which one should I be using to test performance and make sure this is what is causing the hesitations throughout the app?
Apologies in advance for the noob questions - I generally avoid asking questions at all but sometimes, as in this case, I don't even know where to begin the research.
I'm rather pleased with myself. I went with the AppDelegate technique and it's working beautifully. I declared a UIImageView property in the AppDelegate, then add the image code to its getter:
- (UIImageView *)backgroundImageView {
if (!_backgroundImageView) {
NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:150];
for (int imageNum = 0; imageNum < 150; imageNum++) {
[tempArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"image_00%03i.png",imageNum]]];
}
_backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage animatedImageWithImages:tempArray duration:4.0]];
}
return _backgroundImageView;
}
This also allowed me to kill the object in applicationDidReceiveMemoryWarning.
Then I created a helper class that I can pop into all of my UIViewController classes:
+ (void)placeBackgroundImageUnderView:(UIView *)masterView {
myAppDelegate *appDelegate = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
//Correct for shrinkage
appDelegate.backgroundImageView.frame = masterView.frame;
[masterView addSubview:appDelegate.backgroundImageView];
[masterView sendSubviewToBack:appDelegate.backgroundImageView];
}
This allowed me to add a single line in each viewDidLoad method:
[HelperClass placeBackgroundImageUnderView:self.view];
The thing is, just using UIImage alone should cache the whole thing and speed up load time. But it seems that every now and then the images would have to reload - memory issues? So this allows me more control over it by instantiating the object once and using that same object, while also being to set that object to nil to free up memory if needed.
Since adding it, the whole app has been loading much faster. I added an additional load screen with an activity indicator for that initial load (also works beautifully) and everything after that is instant happy.
I am using the standard way of making shadows from a button programmatically, but I would like to shadow to no longer exist after I am done with the button. I could set opacity to 0, but would the shadow still exist and if so would it still tax the system. thanks
this gives an error
tempButton.superview.layer.shadowOffset = nil;
tempButton.superview.layer.shadowRadius = nil;
tempButton.superview.layer.shadowOpacity = nil;
I usually do the following to be safe.
[[tempButton layer] setShadowOpacity:0.0];
[[tempButton layer] setShadowRadius:0.0];
[[tempButton layer] setShadowColor:nil];
Quartz is highly optimized and will not waste any time rendering if it doesn't have to.
I would just remove the button, and replace it with an identical (but non-shadowed) button. Or keep both around and hide/unhide one of them. Sometimes it's easier to create a new UI object than munge around with an existing one.
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.
I've got a fairly simple app that has the following in the view the program is mostly in:
int currentPageIndex;
NSArray *images;
NSString *nextImage;
IBOutlet UIImageView *firstPage;
IBOutlet UIButton *bigButton;
In the implementation viewDidLoad, I load the array with a bunch of image file names:
images = [NSArray arrayWithObjects:#"image1.jpg", #"image2.jpg", etc, nil];
[images retain];
Each time the bigButton is tapped, the image changes:
- (IBAction)bigButtonTapped:(id)sender {
currentPageIndex++;
nextImage = [images objectAtIndex:currentPageIndex];
firstPage.image = [UIImage imageNamed:nextImage];
}
Everything works as I want it to, except that I am getting a "Received memory warning. Level=1" in the console with my device plugged in. This warning comes up after every 12 images or so, and eventually it crashes with "EXC_BAD_ACCESS"
I thought this would actually be a good way not to put anything in memory, as there is only one UIImageView on the screen and its image is changed as I need it to be.
It is a very simple app so I'm sure the fix is very simple... any ideas what I might be overlooking? Thanks so much!!
Since you get a memory warning, the problem must be that the images aren't released. However, in the code you show, you're handling the images correctly. So the problem is most likely in a part of the code you're not showing us.
The only minor problem is see, which has been mentioned before, is that the currentPageIndex will eventually point outside of the range of the array. But this will cause a different error.
To avoid going over the size of the Array,
currentPageIndex= 0;
for(currentPageIndex in images){
doStuff;
}
OH!!!!! I THINK I spotted the your problem. Whenever you use UIImage imageNamed to load images all the images stay in memmory even though release reference to it. use the other UIImage method:contentOfFile.
One other thing, make sure your images are optimize for iOS. Use .png when posibible.
dibu2z
I assume Image is a retained property.
Try to release it at the beginning of your bigButtonTapped.
Hope it helps.
Could be that you've reached the end of the array and you're trying to access past the end of the array. You could do a
currentPageIndex++;
if ( currentPageIndex < [images count]) {
nextImage = [images objectAtIndex:currentPageIndex];
firstPage.image = [UIImage imageNamed:nextImage];
}
Also could be that the image you listed doesn't exist in the bundle.
There isn't really enough information here to say for sure what your problem is. EXC_BAD_ACCESS generally happens when you try to access an objects that has already been deallocated.
The quickest way to track down the real cause of EXC_BAD_ACCESS is by using the NSZombieEnabled executable argument, and then setting a breakpoint on objc_exception_throw. This will get you a stack trace, and allow you to determine specifically which object you are trying to access.
http://www.cocoadev.com/index.pl?NSZombieEnabled
Using Malloc to debug