I've recently created a new class for my iPhone application which will hold information read from a text file containing the street address and GPS points of points of interest.
The issue though is that whenever I add code to initialize the class my application loads up and the instantly quits with no errors in the console. When I remove it, everything is fine. I simply cannot see anything wrong with the code.
Here is the constructor:
#import "GPSCoordinate.h"
#implementation GPSCoordinate
-(GPSCoordinate*) initWithData:(NSString *)rawData size:(int)size
{
self = [super init];
location = [NSMutableArray arrayWithCapacity:size];
coordinates = [NSMutableArray arrayWithCapacity:(int)size];
NSArray *tokens = [rawData componentsSeparatedByString:#"#"];
for (int i = 0; i < size - 1; i++) {
//Sub tokens
NSString *line = [tokens objectAtIndex:i];
NSArray *lineTokens = [line componentsSeparatedByString:#":"];
//Store address
[location addObject:[lineTokens objectAtIndex:0]];
//Store GPS coords
NSString *coords = [lineTokens objectAtIndex:1];
coords = [[coords stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:#""]
stringByReplacingCharactersInRange:NSMakeRange([coords length]-2, 1) withString:#""];
NSArray *coordsTokens = [coords componentsSeparatedByString:#" "];
CLLocationCoordinate2D coord;
coord.latitude = [[coordsTokens objectAtIndex:0] doubleValue];
coord.longitude =[[coordsTokens objectAtIndex:1] doubleValue];
[coordinates addObject:coords];
[line release];
[lineTokens release];
[coords release];
[coordsTokens release];
}
return self;
}
#end
Here is the call I make to it in another class:
self.gps = [[GPSCoordinate alloc] initWithData:gpsRawData size:[[gpsRawData componentsSeparatedByString:#"#"] count]];
Where am I going wrong with this?
I see a number of problems.
You're not checking the return value of [super init].
You're storing autoreleased arrays in what are presumably ivars (location and coordinates).
You're passing a separate size parameter which is calculated from the rawData outside of the call, but -initWithData: makes the exact same calculation inside the method. The size: parameter seems completely superfluous here.
You're skipping the last token entirely. You should take that for loop and make the condition simply i < size. Alternately if you're targetting iOS 4.0 or above you can turn the entire loop into
[tokens enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
NSString *line = obj;
// rest of loop body
}];
Since you don't seem to need the index inside the loop, you could also just use a for-in loop (this will work on pre-4.0 iOS devices):
for (NSString *line in tokens) {
// body of loop
}
You're not checking that your data is valid. If a line contains "foo", your program will crash when it tries to access [lineTokens objectAtIndex:1]. Similarly it'll crash if you have the string "foo:" as it tries to remove the first character of the coordinates variable. In fact anything less than 2 characters after the colon will crash. It'll also crash if there's no spaces after the colon.
And finally, all those calls to -release at the end will crash. All 4 of those objects are autoreleased objects, so by calling -release on them now you're simply guaranteeing that the app will crash when the autorelease pool is drained.
You're also storing coords (e.g. the string) in your coordinates array. Presumably you meant to store coord, though you'll need to wrap it in an NSValue in order to store it in an NSArray.
I see several issues.
1) Most fundamentally, you are releasing a lot of objects that you didn't allocate. For example:
NSString *line = [tokens objectAtIndex:i];
....
[line release];
is incorrect. Review the Cocoa Memory Management Rules.
2) Why are you doing [[gpsRawData componentsSeparatedByString:#"#"] count to pass the size to
your initWithData:size: method, when you're just going to have to repeat the -componentsSeparatedByString: call inside your method. Passing a separate "size" doesn't gain you anything, involves a redundant parse of the input, and opens up more possible bugs (what if the caller passes in a "size" that doesn't match the number of "#"s in the input - you aren't handling that error condition).
3) I also see that you are assigning latitude/longitude to CLLocationCoordinate2D coord; but not doing anything with it. Is that deliberate?
Related
I am facing strange issue, which is very common and may float at many sites, but this is bit strange. I am allocating mutable array object use object and then release as i did and doing every time of allocating and releasing object. It works fine when i comment release line and just use nil. Below is my code please see and suggest me better way.
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
NSMutableArray * array = [[NSMutableArray alloc] init];
array= [[dbSingleton sharedInstance] getAll_Players];
NSMutableDictionary * dict = [array objectAtIndex:row];
NSString * autoID = [dict objectForKey:#"autoId"];
NSLog(#"%#",[NSString stringWithFormat:#"%# %#",[dict valueForKey:#"fName"],[dict valueForKey:#"lName"]]);
[array release];
}
Please do not decrease my point because this is very common and people can't like these question to b ask. Thanks in advance.
No need to alloc-init and release your array.
I guess you do not require a new array with the same content, whereas only reference of your sharedInstance array is required.
For that, remove those lines and only decalre your array:
NSMutableArray *array = [[dbSingleton sharedInstance] getAll_Players];
As you have not alloc-init any array here, no need to release the same. Hence, no memory concerns required.
For the max, to reduce reference count what you can do is, immediately when its done with the reference, nullify that one:
NSMutableArray * array= [[dbSingleton sharedInstance] getAll_Players];
NSMutableDictionary * dict = [array objectAtIndex:row];
NSString * autoID = [dict objectForKey:#"autoId"];
NSLog(#"%#",[NSString stringWithFormat:#"%# %#",[dict valueForKey:#"fName"],[dict valueForKey:#"lName"]]);
array = nil; // not mandatory, it will work without this line as well
This methodology will work for both, ARC or Non-ARC.
Hope this helps.
First you initialize the array variable
NSMutableArray * array = [[NSMutableArray alloc] init];
And correctly at this point, you should release this variable at some point.
But, when you write
array= [[dbSingleton sharedInstance] getAll_Players];
You overwrite the array variable you just allocated, and array is now not something you should release here, unless you also call retain on the array you get from getAll_Players.
To fix the issue you should do it like this:
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
NSMutableArray * array = [[dbSingleton sharedInstance] getAll_Players];
NSMutableDictionary * dict = [array objectAtIndex:row];
NSString * autoID = [dict objectForKey:#"autoId"];
NSLog(#"%#",[NSString stringWithFormat:#"%# %#",[dict valueForKey:#"fName"],[dict valueForKey:#"lName"]]);
}
EDIT: (As answer to the question in the comment)
NSMutableArray * array = [[dbSingleton sharedInstance] getAll_Players];
The above line simply sets the variable 'array' as a pointer to whatever '[[dbSingleton sharedInstance] getAll_Players]' returns. The reference count is not increased nor decreased in that line, and you should therefore not decrease it either. (And NEVER trust the retain count value of any object, as objects are not always released when you expect them too).
The reference count is only increased when calling alloc, new, copy and mutableCopy when creating an object and calling retain on and object, and only when you yourself have used these keywords, should you ever release or autorelease an object. Note that this keywords increase the RF count. Where release and autorelease decrease the RF count.
It is expected objective-c practice to make sure that any function that returns an object, returns an object with the retain count of 0, unless the function name has one of the above keywords in its name. (and you should of course call 'autorelease' and not 'release' on the object before returning it)
When you use one of those keywords, the receiver should expect an rf count of 1, and that the receiver will make sure to release the object when he or she is done with it.
This is also what you should expect from any built-in functions.
That is why you should not release the object 'array' from the above line.
I'm doing some work using the RedPark Serial Cable for iOS. This is a cable that allows serial communication between an iOS device and another device like a microcontroller.
There is a method that comes with the RedPark SDK that reads the bytes available on the Serial Line. This method is called anytime the cable receives informatino.
(void)readDataBytes(UInt32 bytes) {
NSString *s = [[NSString alloc] initWith...bytes];
NSString *editedString = [self extractSubStringMethod:s];
[myArray addObject:editedString];
}
The microcontroller is sending the information in the following format
<msg>xxxxxxxxxxxxx</msg>
I want to be able to extract the x's from the message (which is taken in as an NSString). At the moment I'm using NSRange to extract everything after position 4 (the first x) and before the final "< /msg>" I'm not convinced it works and was wondering is there any other ideas?
Finally, I have an NSThread which is running alongside this, I have the messages being added to an NSMutableArray. So what I want is, the NSThread to be manipulating/displaying the message information when there is a message received from the cable. I have something like the following
//Thread method,
while([myArray count] > 0) //Don't believe this is neccesary but its in anyway
{
for(int i = 0; i < [myArray count]; i++){
NSString *string = [myArray objectAt(i)];
[self displayString:string];
[myArray removeObjectAt(i);
}
}
I believe it's crashing around the above... [self displayString:string] just sets the value of a label, something like
-(void)displayString(NSString *string) {
label.text = [string charAt(1)];
}
The code above is just from memory as I've left my Mac at home and I'm in work.
Any suggestions/help would be appreciated
Instead of
while([myArray count] > 0) //Don't believe this is neccesary but its in anyway
{
for(int i = 0; i < [myArray count]; i++){
NSString *string = [myArray objectAt(i)];
[self displayString:string];
[myArray removeObjectAt(i);
}
}
try like this:
while ([myArray count] > 0)
{
[self displayString: myArray[0]];
[myArray removeObjectAtIndex: 0];
}
To address your first concern, [string substringWithRange:NSMakeRange(5, string.length - 6)] should be sufficient and should work without any issues. You could use an NSRegularExpression to match the content inside the tags, but that would be overkill for such a simple task (and would have a performance hit).
Secondly, UIKit methods must only be called from the main thread (see documentation):
Note: For the most part, UIKit classes should be used only from an application’s main thread. This is particularly true for classes derived from UIResponder or that involve manipulating your application’s user interface in any way.
You appear to be setting the text of a label in a different thread. This is what is causing the crash. You need to call your displayString: method like this:
[self performSelectorOnMainThread:#selector(displayString:) withObject:string waitUntilDone:YES];
This will execute the method on the main thread instead of on a background thread.
You must not update UI on secondary thread.
Mutable classes are not thread safe.
while([myArray count]){
NSString *string = [myArray objectAt(0)];
[self performSelectorOnMainThread:#selector(displayString:) withObject:string waitUntilDone:NO];
[myArray removeObjectAt(0);
}
I have these two buttons hooked up to these two methods (they're nearly identical)
-(void)moveOneImageNewer{
int num = [numberOfImage intValue];
num--;
numberOfImage = [[NSString stringWithFormat:#"%i",num] retain];
//Load the image
[self loadImage];
}
-(void)moveOneImageOlder{
int num = [numberOfImage intValue];
num++;
numberOfImage = [NSString stringWithFormat:#"%i",num];
//Load the image
[self loadImage];
}
If I hit either of them twice (or once each, basically if they get called a total of two times) I get an EXC_BAD_ACCESS. If I throw a retain on: numberOfImage = [[NSString stringWithFormat:#"%i",num]retain] it's fine though. Can someone explain why this is? I did an NSZombie on the instruments and traced it back to this stringWithFormat call. Thanks in advance!
+stringWithFormat: doesn't contain 'new', 'alloc', 'copy', or 'retain', so it should be expected that you have to retain the return value of it if you want the new NSString it creates to stick around.
Edited to include this handy link duskwuff kindly dug up: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-BAJHFBGH
If numberOfImage is a properly declared property, e.g.
#property (copy) NSString *numberOfImage;
and it was properly synthesized (in the #implementation section for the class):
#synthesize numberOfImage;
then you can do:
- (void) moveOneImageNewer
{
self.numberOfImage = [NSString stringWithFormat: #"%i", [self.numberOfImage intValue] - 1];
// Load the image
[self loadImage];
}
The property setter will take care of retaining the string and, if necessary, releasing the previous string.
FWIW, why on earth is numberOfImage a string? Why not a simple int?
numberOfImage is an instance variable or property of your class, right?
You are setting it to a stringWithFormat (which returns an auto-released NSString) without claiming ownership of that object (by calling retain).
If you do not retain it, it will get auto-released before the method is called again (and then the first line will fail, as it tries to access the previously set, now auto-released value).
Consider using properties, they have auto-generated memory management code (including releasing the old NSString when you set the new one).
You haven't retained the string object in "moveOneImageOlder", so that object gets autoreleased at the end of the event cycle and points to nothing. That's why you get the EXC_BAD_ACCESS next time you try to use it.
Use a retain to claim ownership of the NSString. Remember to release when you're done though (you can use properties to help you with this)
-(void)moveOneImageNewer{
int num = [numberOfImage intValue];
num--;
[numberOfImage release];
numberOfImage = [[NSString stringWithFormat:#"%i",num] retain];
//Load the image
[self loadImage];
}
-(void)moveOneImageOlder{
int num = [numberOfImage intValue];
num++;
[numberOfImage release];
numberOfImage = [[NSString stringWithFormat:#"%i",num] retain];
//Load the image
[self loadImage];
}
Add this in dealloc:
- (void)dealloc {
[numberOfImage release];
[super dealloc];
}
Well, the NSString class method "stringWithFormat" returns an autorelease NSString object if I'm right.
So the second call to your method would have numberOfImage pointing to nothing, as the autorelease NSString object it used to be pointing to has already been released and deallocated since you didn't retain it.
The part that is directly causing the crash is [numberOfImage intValue] when you call the method a second time, as you sending a message to an object (pointed to by numberOfImage) that no longer exist.
I was running Leaks tool and discovered a massive leak in my Dictionary mutableDeepCopy but I can't figure out what's wrong with the code. Any suggestions?
#interface RootViewController : UIViewController{
NSDictionary *immutableDictionary;
NSMutableDictionary *mutableDictionary;
}
Here is the line of code that's highlighted in Instruments
self.mutableDictionary = [self.immutableDictionary mutableDeepCopy];
Here is the method for creating a mutable copy of a Dictionary
#interface NSDictionary(MutableDeepCopy)
-(NSMutableDictionary *)mutableDeepCopy;
#end
Here is method implementation, I've highlighted the code that Leaks saids is leaking 100%
- (NSMutableDictionary *) mutableDeepCopy {
NSMutableDictionary *dictionaryToReturn = [NSMutableDictionary dictionaryWithCapacity:[self count]];
NSArray *keys = [self allKeys];
for(id key in keys) {
id value = [self valueForKey:key];
id copy = nil;
if ([value respondsToSelector:#selector(mutableDeepCopy)]) {
copy = [value mutableDeepCopy];
} else if ([value respondsToSelector:#selector(mutableCopy)]) {
copy = [value mutableCopy]; //This is the Leak
}
if (copy == nil) {
copy = [value copy];
}
[dictionaryToReturn setValue:copy forKey:key];
}
return dictionaryToReturn;
}
You need to analyse this in light of Apple's Memory Management Rules.
Starting with this line:
self.mutableDictionary = [self.immutableDictionary mutableDeepCopy];
I would expect mutableDeepCopy to return an object I own, so at some point I need to release or autorelease it. e.g.
NSMutableDeepCopy* temp = [self.immutableDictionary mutableDeepCopy];
self.mutableDictionary = temp;
[temp release];
or
self.mutableDictionary = [[self.immutableDictionary mutableDeepCopy] autorelease];
So now we need to look at mutableDeepCopy. Because it has 'copy' in the name it needs to returned an "owned" object which, in practice means "forgetting" to release the returned object. You have already failed to do that when you create the returned object in the first line, since dictionaryWithCapacity: gives you an object you do not own. Replace it with
NSMutableDictionary *dictionaryToReturn = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
Now you own it.
It is important that you make your mutableDeepCopy obey the rules because it means you can treat the objects returned from mutableDeepCopy, mutableCopy and copy in exactly the same way. In all three cases you own the object copy that you insert into the array. Because you own it, you must release it or it'll leak as you found out. So, at the end of the loop, you need
[copy release];
That'll stop the leak.
How is your property declared? If is is retain or copy, then this doesn't leak.
Your problem is that the name mutableDeepCopy suggests that it returns a retained object, and not an autoreleased one as it actually does.
Edit:
And at the mutableDeepCopy itself, you need to release the copy variable after adding to the dictionary.
mutableCopy increments the retain count of the object, as does setValue:forKey:. This means that when dictionaryToReturn is dealloc'ed, the object that had mutableCopy called still has a retain count of one.
Try doing this instead:
copy = [[value mutableCopy] autorelease];
i get the signal error EXC_BAD_ACCESS when trying to retrieve the return output from the randomBallPick method, i probably do it wrong.
NSString *temp = [self randomBallPick];
upBall1.image = [UIImage imageNamed:temp];
Array (containers) retain/release items that are added/removed.
The object will receive release when it's removed from container with removeObjectAtIndex: so you need to retain it before it is removed and possibly autorelease since you are returning it from your method.
NSString * chosenFilename =
[[[imageArray objectAtIndex:chosen] retain] autorelease];
[imageArray removeObjectAtIndex:chosen];
return chosenFilename;
OK, can you try this, please?
NSString *chosenFilename = [[imageArray objectAtIndex:chosen] retain];
[imageArray removeObjectAtIndex:chosen];
return [chosenFilename autorelease];
All you need to do is:
NSString *chosenFilename = [[imageArray objectAtIndex:chosen] retain];
Since the objectAtIndex method returns an autorelease object.
There are several things wrong with this piece of code.
You are initializing your array of names once, but then you keep removing stuff from it... I'm not sure you want to do this, otherwise you'll start returning nil exclusively (after the 38th call...). You may want to re-fill your array in that case. Here's a better version of your routine (I think):
static NSMutableArray *imageArray = nil;
if (!imageArray.count) {
if (imageArray==nil) imageArray = [[NSMutableArray alloc] init];
for (int c = 0; c < 37; c++)
{
NSString *imageName = [NSString stringWithFormat:#"ball_%i.png", c];
[imageArray addObject:imageName];
}
}
// Now we are basically sure that imageArray.count > 0
assert(imageArray.count>0);
NSUInteger chosen = arc4random() % imageArray.count;
NSString *chosenFilename = [[imageArray objectAtIndex:chosen] retain];
[imageArray removeObjectAtIndex:chosen];
return [chosenFilename autorelease];
As others have said, you have to retain then autorelease the strings you extract from the array (because the array releases them upon removal).
Note also that you should call 'retain' on the string before removing it from the array. The string is already released after the removeObjectAtIndex: call... so it's already too late to retain it then.
As soon as you remove the object from the array, its retain count is probably zero, and it will get dealloced. Try doing
return [[chosenFilename] retain] autorelease]