I'm having a bit of an issue with holding a mixture of a custom class and UIImage views in an array. These are stored in the array and I'm using:
if ([[fixtures objectAtIndex:index] isKindOfClass:[Fixture class]])
to distinguish between if it's a UIIMage or Fixture object. My source code for this is:
- (void) moveActionGestureRecognizerStateChanged: (UIGestureRecognizer *) recognizer
{
switch ( recognizer.state )
{
case UIGestureRecognizerStateBegan:
{
NSUInteger index = [fixtureGrid indexForItemAtPoint: [recognizer locationInView: fixtureGrid]];
emptyCellIndex = index; // we'll put an empty cell here now
// find the cell at the current point and copy it into our main view, applying some transforms
AQGridViewCell * sourceCell = [fixtureGrid cellForItemAtIndex: index];
CGRect frame = [self.view convertRect: sourceCell.frame fromView: fixtureGrid];
dragCell = [[FixtureCell alloc] initWithFrame: frame reuseIdentifier: #""];
if ([[fixtures objectAtIndex:index] isKindOfClass:[Fixture class]]) {
Fixture *newFixture = [[Fixture alloc] init];
newFixture = [fixtures objectAtIndex:index];
dragCell.icon = [UIImage imageNamed:newFixture.fixtureStringPath];
[newFixture release];
} else {
dragCell.icon = [fixtures objectAtIndex: index];
}
[self.view addSubview: dragCell];
}
}
However, when dragging the cell that was an object of class Fixture, I would get errors such as EXC_BAD_ACCESS or unrecognized selector sent to instance (which makes sense as it was sending a CALayerArray a scale command.
I therefore set a breakpoint to see inside the fixtures array. Here I saw that the UIImages were all set to the right class type but there was also:
(CALayerArray *)
(Fixture *)
(NSObject *)
for the positions were the Fixture classes were being held in the array. Could anyone shed some light onto why it's doing this? If you need any more info to help please feel free to ask.
Denis
In your code here:
Fixture *newFixture = [[Fixture alloc] init];
newFixture = [fixtures objectAtIndex:index];
dragCell.icon = [UIImage imageNamed:newFixture.fixtureStringPath];
[newFixture release];
It looks like you're releasing an autorelease object (newFixture). When you get an object out of the array, it's autorelease.
You also have a memory leak, when you allocate the newFixture at the first line, that object is never released because you replace the pointer to it in your 2nd line.
Fixture *newFixture = [[Fixture alloc] init]; // THIS OBJECT IS NEVER RELEASED
newFixture = [fixtures objectAtIndex:index]; // YOU'RE REPLACING THE newFixture POINTER WITH AN OBJECT FROM THE ARRAY
dragCell.icon = [UIImage imageNamed:newFixture.fixtureStringPath];
[newFixture release]; // YOU'RE RELEASING AN AUTORELEASED OBJECT
So the code should be like
Fixture *newFixture = [fixtures objectAtIndex:index];
dragCell.icon = [UIImage imageNamed:newFixture.fixtureStringPath];
Then your property should retain the image correctly.
Related
In my viewDidLoad method of my iPhone app I have the following code:
zombie[i].animationImages = zombieImages;
zombie[i].animationDuration = 0.8/zombieSpeed[i];
zombie[i].animationRepeatCount = -1;
[zombie[i] startAnimating];
Later on in the app the following code is called:
[zombie[i] stopAnimating];
zombie[i] = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"zh.png"]];
zombie[i].animationImages = flyingZombieImages;
zombie[i].animationDuration = 0.8/zombieSpeed[i];
zombie[i].animationRepeatCount = -1;
[zombie[i] startAnimating];
This causes the app to crash, with EXC_BAD_ACCESS on the line zombie[i].animationImages = flyingZombieImages;
flyingZombieImages is initialized with the following code: (zombieImages is initialized the same way)
NSMutableArray *flyingZombieImages = [NSMutableArray array];
for (NSUInteger i=1; i <= 29; i++) {
NSString *imageName = [NSString stringWithFormat:#"flzom%d.png", i];
[flyingZombieImages addObject:[UIImage imageNamed:imageName]];
}
Why is this happening? Is there a workaround?
As Dima mentioned flyingZombieImages is likely not initialized properly, which is causing the crash. However, there is another problem as well when you create the new instances of UIImageView:
zombie[i] = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"zh.png"]];
At this point you already have a reference to the old UIImageView stored in this variable. You are losing the reference to it and most likely will leak its memory. You also would want to remove the old UIImageView from the view hierarchy and add the new one.
A better way is instead to use the original UIImageView and change its image by replacing this line with:
zombie[i].image = [UIImage imageNamed:#"zh.png"];
I am building an app for iOS 5 using ARC and I seem to be having some memory issues. Basically its taking screen-shots of a portion of the display, placing the UIImage in an MSMutableArray and then piecing the screen-shots together for one big image. Now the problem is that after doing this a couple of times the OS closes the application due to high memory usage.
Here is the snippet that pieces the UIImage's together:
UIImage* finalImage = nil;
//join the screenshot images together
UIGraphicsBeginImageContext(CGSizeMake(collage.width, collage.height));
{
int hc = 0;
for(UIImage *img in imageArr)
{
NSLog(#"drawing image at:: %i", hc);
[img drawAtPoint:CGPointMake(0, hc)];
hc+=img.size.height;
img = nil;
}
//NSLog(#"creating finalImage");
finalImage = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
//do something with the combined image
//remove all the objects
[imageArr removeAllObjects];
//reset class instance
[self setImageArr: [[NSMutableArray alloc] init]];
Are they any other alternatives that I could use so there isn't so much memory being used? Maybe storing a CGImageRef in the array? Are there any potential memory leaks with the above code?
Any tips, pointers would be greatly appreciated.
Thanks.
[imageArr removeAllObjects]; will remove the objects from array. No need to reset the array again with
[self setImageArr: [[NSMutableArray alloc] init]];
By doing this you are allocating a NSMutableArray object and not releasing it.
Try by removing the line [self setImageArr: [[NSMutableArray alloc] init]];
make sure you alloc and init setImageArr
if (setImageArr == nil){
setImageArr = [[NSMutableArray alloc]init];
}
else
{
[setImageArr removeAllObjects];
}
or use (if you want to init from an existing Array):
NSMutableArray *setImageArr = [[NSMtableArray]initWithArray:arrayOfImages];
Because you say it will have memory issue after doing this a couple of times. Then how about you use NSAutoreleasePool to force system release objects after your method, example below:
#autoreleasepool {
UIImage* finalImage = nil;
//join the screenshot images together
UIGraphicsBeginImageContext(CGSizeMake(collage.width, collage.height));
{
int hc = 0;
for(UIImage *img in imageArr)
{
NSLog(#"drawing image at:: %i", hc);
[img drawAtPoint:CGPointMake(0, hc)];
hc+=img.size.height;
img = nil;
}
finalImage = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
//do something with the combined image
//remove all the objects
[imageArr removeAllObjects];
//reset class instance
[self setImageArr: [[NSMutableArray alloc] init]];
}
And I also doubt there is any memory leak problem in your other codes.
Using ARC doesn't meaning without memory leak problem, maybe you store many useless objects in a global variable etc.
Maybe you should use Instruments to monitor the memory to figure out where does the memory go.
Turns out the imageArr is being cleared properly. There appears to be a memory issue somewhere else in the program.
Instruments is saying there is a memory leak in this code:
- (void)layoutImageMaskViewForImageAtPath:(NSString *)path withFillColor:(UIColor *)color indexPath:(NSIndexPath *)indexPath {
UIImage *image = [UIImage imageWithContentsOfFile:path];
[self layoutImageMaskViewForImage:image withFillColor:color indexPath:indexPath];
}
UIColor *anIconFillColor = [UIColor colorWithWhite:0.70 alpha:1.0];
NSIndexPath *anIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
NSString *aPlaceholderPath = [[NSBundle mainBundle] pathForResource:#"path" ofType:#"png"];
[self layoutImageMaskViewForImage:anImage withFillColor:anIconFillColor indexPath:anIndexPath];
and
NSDictionary *anAssignedData = [aReservationData objectForKey:kAssignedSectionKey];
NSMutableArray *anEmployeeTaskQueueList = [NSMutableArray array];
NSArray *anAssignedReservationData = [anAssignedData objectForKey:kEmployeesIdentifier];
for (NSDictionary *aJobQueueData in anAssignedReservationData) {
EmployeeReservationQueue *anAssignedTaskQueue = [[EmployeeReservationQueue alloc] initWithServerDictionary:aJobQueueData];
if (anAssignedTaskQueue.rows.count == 0) {
ReservationTrack *aTrack = [[ReservationTrack alloc] init];
aTrack.rowSortOrder = 0;
aTrack.reservations = [NSArray array];
anAssignedTaskQueue.rows = [NSArray arrayWithObject:aTrack];
[aTrack release];
}
[anEmployeeTaskQueueList addObject:anAssignedTaskQueue];
[anAssignedTaskQueue release];
}
Your second example leaks track. Your last line is releasing aTrack instead.
In second case here:
[aTrack release];
What is aTrack? May be you mean [track release];?
In first case probably that you pass to function non-autoreleased parameters or may be you are not releasing them after calling that method. Just post code where you call for that method and I will check.
Gold memory-management rule in Objective-C :
Each 'init', 'copy','mutableCopy','retain' must call then 'release' or 'autorelease'.
Instruments reports that your app is leaking a ReservationTrack object. By default it shows where the leaked object was allocated, which is the code you posted. The code you posted doesn't leak a ReservationTrack. It stores it in an EmployeeReservationQueue which is stored in an NSMutableArray. One possibility is that you later access the ReservationTrack object, send it retain, and don't send it release or autorelease. Another possibility is that you leak the EmployeeReservationQueue or the NSMutableArray.
If you use the simulator, you can see the full retain/release history of most objects. When a leaked object shows up, mouse over the address of the object and click the right arrow that appears next to the address. Instruments will show you every malloc, retain, release, and autorelease event for that object. If you choose View > Extended Detail from the menu bar, you can click on any of those events and see the stack trace of the event. This should help you track down the unbalanced retain.
I've been programming objective-C for a few months now and have done pretty well so far without having to post any questions. This would be my first. The problem is that I'm getting a memory leak warning from a data object in one of it's methods. I can see that the problem is that I'm sending an alloc to it without releasing it, but I don't know how else to get it to retain the object in memory. If I take the alloc out, the program crashes. If I leave it in, it leaks memory. Here is the method in question:
+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure {
Feature *newFeature = [[self alloc] init];
newFeature.featureID = fID;
newFeature.featureName = fName;
newFeature.featureSecure = fSecure;
return [newFeature autorelease];
}
This method is called by another method in my view controller. This method is as follows:
+ (NSMutableArray*) createFeatureArray {
NSString *sqlString = #"select id, name, secure from features";
NSString *file = [[NSBundle mainBundle] pathForResource:#"productname" ofType:#"db"];
sqlite3 *database = NULL;
NSMutableArray *returnArray = [NSMutableArray array];
if(sqlite3_open([file UTF8String], &database) == SQLITE_OK) {
const char *sqlStatement = [sqlString UTF8String];
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
Feature *myFeature = [Feature featureWithID:sqlite3_column_int(compiledStatement,0)
name:[NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)]
secure:sqlite3_column_int(compiledStatement,2)];
[returnArray addObject:myFeature];
}
}
// Release the compiled statement from memory
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
return returnArray;
}
I have tried several things, such as creating a featureWithFeature class method, which would allow me to alloc init the feature in the calling method, but that crashed the program also.
Please let me know if you need any clarification or any other parts of the code. Thank you in advance for your help.
UPDATE: 4/14/2011
After reading the first two responses I implemented the suggestion and found that the program is now crashing. I am at a complete loss as to how to track down the culprit. Hoping this helps, I am posting the calling method from the view controller as well:
- (void)setUpNavigationButtons {
// get array of features from feature data controller object
NSArray *featureArray = [FeatureController createFeatureArray];
int i = 0;
for (i = 0; i < [featureArray count]; i++) {
Feature *myFeature = [featureArray objectAtIndex:i];
CGRect buttonRect = [self makeFeatureButtonFrame:[featureArray count] withMember:i];
UIButton *aButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[aButton setFrame:buttonRect];
[aButton addTarget:self action:#selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
[aButton setTitle:[NSString stringWithFormat:#"%#",myFeature.featureName] forState:UIControlStateNormal];
aButton.tag = myFeature.featureID;
[self.view addSubview:aButton];
}
}
NOTE: These methods are posted in reverse of the order they are invoked. This last method calls the second method, which in turn, calls the first.
UPDATE: I've updated these functions to show what is in there now: Below, I will post the header files for the object - maybe that will help
#interface Feature : NSObject {
int featureID;
int featureSecure;
NSString *featureName;
}
#property (nonatomic, assign) int featureID;
#property (nonatomic, assign) int featureSecure;
#property (nonatomic, retain) NSString *featureName;
- (id) init;
- (void) dealloc;
+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure;
#end
#interface FeatureController : NSObject {
}
- (id) init;
- (void) dealloc;
+ (NSMutableArray*) createFeatureArray;
+ (Feature*) getFeatureWithID:(int)fetchID;
#end
Convenience methods should follow the convention of returning autoreleased objects. Change this:
+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure {
Feature *newFeature = [[self alloc] init];
...
return newFeature;
}
to:
+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure {
Feature *newFeature = [[self alloc] init];
...
return [newFeature autorelease];
}
The name of your method - +featureWithID:name:secure: - indicates that it returns an object that the caller does not own. Instead, it is returning an object that has been retained, that the caller therefore owns and must release. To fix this (and your leak), simply replace return newFeature with return [newFeature autorelease].
There's nothing more you need to do, because your own code doesn't need a long-lasting ownership claim, and the array to which you're adding the object will manage its own ownership claim over it.
In +createFeatureArray, you’re over releasing the array:
+ (NSMutableArray*) createFeatureArray {
…
NSMutableArray *returnArray = [[[NSMutableArray alloc] init] autorelease];
…
return [returnArray autorelease];
}
In the first line, you used +alloc, so you own the array. Then you used -autorelease, so you do not own the array any more. This means that you shouldn’t send -release or -autorelease to it, which you are doing in the return line.
You can fix that by changing those lines to:
+ (NSMutableArray*) createFeatureArray {
…
NSMutableArray *returnArray = [NSMutableArray array];
…
return returnArray;
}
Also, unless it is relevant to callers that the array is mutable, you should change that method to return NSArray instead of NSMutableArray. You could keep your code as is, i.e., return a mutable array even though the method declaration states that the return type is NSArray.
As for your convenience constructor, there are essentially two choices depending on whether you want to return an owned or a non-owned object:
if you want to return an owned object, allocate it with +alloc or +new and return it without autoreleasing it. Your method name should contain new, e.g. +newFeatureWithId:…
if you want to return an object that’s not owned by the caller, allocate it with +alloc or new and autorelease it before/upon returning it to the caller. Your method name should not contain new, alloc, or copy.
In -setUpNavigationButtons, you obtain a non-owned array via +createFeatureArray, allocate a mutable array based on it, and release the mutable array without adding or removing elements from it. A mutable array makes sense when you need to add/remove elements. If you don’t have this need, you could change your method to:
- (void)setUpNavigationButtons {
// get array of features from feature data controller object
NSArray *featureArray = [FeatureController createFeatureArray];
…
// [featureArray release];
You’d remove that [featureArray release] since you do not own featureArray inside that method.
Edit: In -setUpNavigationButtons, you’re retaining the button you create and soon after you’re releasing it. In that particular method, those are idempotent operations — they aren’t wrong per se but are not necessary at all. You could replace that code with
UIButton *aButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
…
[self.view addSubview:aButton];
// [aButton release];
i.e., do not retain it and do not release it.
This:
UILable *myLabel = [[UILabel alloc] init];
UILable *myLabel = [[UILabel alloc] init];
gives me a redefinition error.
But this:
for(i=0;i<5;i++)
{
UILable *myLabel = [[UILabel alloc] init];
// some label code here
[self.view addSubview:myLabel];
[myLabel release];
}
doesn't. So is the second one false? Should I define it before and just reuse it?
Is that right:
UIIMageView *Sign;
//Some Sign Stuff
Sign = [[UIImageView alloc]init];
Sign.image = [UIImage imageNamed:#"Minus.png"];
frame = CGRectMake(160 ,80, 64, 64);
Sign.frame = frame;
[scrollView addSubview:Sign];
Sign = nil;
[Sign release];
//Some other Sign stuff
Sign = [[UIImageView alloc]init];
Sign.image = [UIImage imageNamed:#"Plus.png"];
frame = CGRectMake(200 ,80, 64, 64);
Sign.frame = frame;
[scrollView addSubview:Sign];
Sign = nil;
[Sign release];
is that correct? That doesnt work without the Sign = nil. So it seems a little wobbly too.
You cannot have identical variable names used in the same block level scope. So in your first example you cannot have a variable definition with the same name, you have to name them differently.
- (void) method {
UIImageView* image1;
// here we define a new block scope. This can be a block of any kind (while, for, if)
{
// All reference in this block to this variable will see this definition.
UIImageView* image1;
// Using image1 here
}
// Here we see again the image1 defined at the beginning of the method.
}
In your loop example you are in a new scope that it's reinitialize after each iteration.
Your third example is correct in that it define the variable only one time. You reuse this variable after that to assign a new object. The third one is less elegant in that your variable name does not describe well for each case what are their purpose.
For your case of 'Sign = nil' this effectively make the line that follows useless since in Objective-C a message sent to a nil object is ignored.
I would suggest to define a method that you can call to create your images that look the same. Something like:
- (void) createImage:(NSString*) image frame:(CGRect) frame {
UIImageView *Sign;
Sign = [[UIImageView alloc]init];
Sign.image = [UIImage imageNamed:image];
Sign.frame = frame;
[self.scrollView addSubview:Sign];
[Sign release];
}
Your for-loop is perfectly fine. The scope of myLabel is limited to one run of your for-loop. So each run a new variable to hold the reference to your UILabel is created.
The second code you posted has leaks.
Sign = nil
[Sign release]
This will release the object at address nil and not the object you created. I can't see what else is wrong with your code, but your fix is definitely not fixing the root cause. Maybe it will help to post what error/warning you get when removing Sign = nil.
Also note that starting your variable names with a capital letter is not a good naming convention, because usually class names start with one.