PDA

View Full Version : NSMutableDict won't retain objects?




larswik
Feb 16, 2012, 07:01 AM
I need a second set of eyes here. I have done this many times before with out a problem. I am getting 4 text files from the internet and then using NSScanner to read in each line, from each txt file. At that point I add each line of text to an NSString and further add that string to an NSMutableArray. When it finished reading in the txt file it adds the NSMutableArray to a NSMutableDictionary.

I used NSLog to verify the all the data exists in the MutableArray but when I try to verify that it was saved to the MutableDictionary it just prints out the keys and the objects are blank.

Here is the log and I can see clientArray hold values

2012-02-16 13:45:00.994 SYV Traveler[5905:b903] ClientArray: (
"Olive House",
"Olive & olive oil tasting",
"River Course",
Golfing,
"Wine Country Mobile Massage",
"Mobile massage service"
)
2012-02-16 13:45:00.995 SYV Traveler[5905:b903] Client Dicts: {
activities = (
);
dining = (
);
shopping = (
);
wine = (
);
}
2012-02-16 13:45:00.995 SYV Traveler[5905:b903] items: (
)
kill
quit
Program ended with exit code: 0

Here is the Method so far. At the bottom is where I test it
@interface ViewController : UIViewController{
NSMutableDictionary *clientTableViewDict;
NSMutableArray *clientArray;

}


-(void)backgroundNameLoad{ //Load the tableView Lists
BOOL checkForFile;
int locationOfScanner = 0;

NSMutableString *tempStorageString = [[NSMutableString alloc] init];
clientArray = [[NSMutableArray alloc] init];
clientTableViewDict = [[NSMutableDictionary alloc] init];

NSFileManager *fileManager = [NSFileManager defaultManager]; //Create File manager object

for (int i = 0; i < 4; i++) {
NSString *verifyLocalFile = [self dataPathToAllFiles:i];
checkForFile = [fileManager fileExistsAtPath:verifyLocalFile]; // BOOL to see if file exists

NSLog(@"Local File Path %@", verifyLocalFile);

if (checkForFile) {
NSLog(@"The file is here");
}

else{
NSLog(@"No file exists");
NSURL *fileLocation = [self getNextURL:i];

NSString *currentClientList = [NSString stringWithContentsOfURL:fileLocation encoding:NSUTF8StringEncoding error:nil];
NSScanner *scanner = [NSScanner scannerWithString:currentClientList];

while (![scanner isAtEnd]) {
[scanner setScanLocation:locationOfScanner];
[scanner scanUpToString:@"\n" intoString:&tempStorageString];
locationOfScanner = [scanner scanLocation];
[clientArray addObject:tempStorageString];
}
[clientTableViewDict setValue: clientArray forKey:[self dictName:i]];
NSLog(@"ClientArray: %@", clientArray);
locationOfScanner = 0;
[clientArray removeAllObjects];
}
}
NSLog(@"Client Dicts: %@", clientTableViewDict);
NSLog(@"items: %@", [clientTableViewDict objectForKey:@"wine"]);
}


I have looked at it for a couple hours now and can't track down the problem.

Thanks



robbieduncan
Feb 16, 2012, 07:21 AM
It does retain it. What it does not do is create a new copy of it. There is none object with multiple pointers pointing to it. So when you say (I have removed the two lines inbetween which have no effect on this)


[clientTableViewDict setValue: clientArray forKey:[self dictName:i]];
[clientArray removeAllObjects];


You are telling the dict to have a pointer to the object pointed to by clientArray. This is the same object. There is one array. You then empty that array. As noted there is one array. So this empties the array that the dict has a pointer to.

Sydde
Feb 16, 2012, 06:18 PM
Not directly related to your issue, but I think I am seeing a small leak here, in orange. IIRC, NSScanner creates an autoreleased NSString object and places the pointer to it in the "intoString:" object, overwriting the variable. If you are using ARC, you are creating an orphan object that is released right before the scan call. If I am mistaken, then you are just repeatedly adding the same object into the array, you would need to be reallocating it before each scan call.

-(void)backgroundNameLoad{ //Load the tableView Lists
BOOL checkForFile;
int locationOfScanner = 0;

NSMutableString *tempStorageString = [[NSMutableString alloc] init];
clientArray = [[NSMutableArray alloc] init];
clientTableViewDict = [[NSMutableDictionary alloc] init];

NSFileManager *fileManager = [NSFileManager defaultManager]; //Create File manager object

for (int i = 0; i < 4; i++) {
NSString *verifyLocalFile = [self dataPathToAllFiles:i];
checkForFile = [fileManager fileExistsAtPath:verifyLocalFile]; // BOOL to see if file exists

NSLog(@"Local File Path %@", verifyLocalFile);

if (checkForFile) {
NSLog(@"The file is here");
}

else{
NSLog(@"No file exists");
NSURL *fileLocation = [self getNextURL:i];

NSString *currentClientList = [NSString stringWithContentsOfURL:fileLocation encoding:NSUTF8StringEncoding error:nil];
NSScanner *scanner = [NSScanner scannerWithString:currentClientList];

while (![scanner isAtEnd]) {
[scanner setScanLocation:locationOfScanner];
[scanner scanUpToString:@"\n" intoString:&tempStorageString];
locationOfScanner = [scanner scanLocation];
[clientArray addObject:tempStorageString];[/COLOR]
}
[clientTableViewDict setValue: clientArray forKey:[self dictName:i]];
NSLog(@"ClientArray: %@", clientArray);
locationOfScanner = 0;
[clientArray removeAllObjects];
}
}
NSLog(@"Client Dicts: %@", clientTableViewDict);
NSLog(@"items: %@", [clientTableViewDict objectForKey:@"wine"]);
}


You also do not need to use the scan location variable, scanners advance automatically on their own. In this code, you can remove those lines entirely.

Expect the text you get to have unknown line delimination format. Change the lines I highlighted in blue to

if ( [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] intoString:&tempStorageString] ) {
// only add a string if it has content
[clientArray addObject:tempStorageString];
[scanner scanCharactersFromSet:[NSCharacterSet newlineCharacterSet] intoString:nil]; // skip past any excess CR characters
}

larswik
Feb 17, 2012, 04:03 PM
Ahhhh. I always forget that I am just storing pointers! Dejo mentioned that a while back, I get it but I forget it often, but it makes sense. I store the pointer and then remove all objects so it points the object that has nothing in it. My original thinking was that I first store the array in the dict so the dict takes control of that object.

Now before I read this message I got it working by moving the [array removeAllObjects] to the top of the for loop.

for (int i = 0; i < 4; i++) {
[clientArray removeAllObjects];
NSString *verifyLocalFile = [self dataPathToAllFiles:i];
checkForFile = [fileManager fileExistsAtPath:verifyLocalFile]; // BOOL to see if file exists

It works but it should not since the Array is still storing the pointer to the same object. Technically I would think I wind up with the same result, but in this case it retains the items in the dict? Why would it work when the for loop executes again?

When you reminded me it stores the pointer it became clear. The for loop executes 4 times so it would add the same pointer that points to the same object 4 times. Since I remove the items in the array at the begining ive every pass I should only wind up with the last set of values I add to the array. But now when I NSLog the dict is shows all the data from each pass?

I would think this would not work, but it is and I don't know why?

Sydde - Thanks. I am using ARC and if by habit I try to release something I alloc'd Xcode lets me know right away. I will try out that code you recommended but I would love to know why my code is working when it should not be since I am removing all values from the array. Some how it's getting copied?

Thanks for all your help.

-Lars

Sydde
Feb 17, 2012, 07:59 PM
When you reminded me it stores the pointer it became clear. The for loop executes 4 times so it would add the same pointer that points to the same object 4 times. Since I remove the items in the array at the begining ive every pass I should only wind up with the last set of values I add to the array. But now when I NSLog the dict is shows all the data from each pass?

I would think this would not work, but it is and I don't know why?

I would ask how painful do you think it would be to allocate a new array inside the loop, at the beginning for each pass, where you currently have the -removeAllObjects (making that call unneeded)? You would remove the variable's initializer, where you first allocate it in the declaration. This would make your code logically tight and sensible.

larswik
Feb 18, 2012, 05:27 AM
Thanks, I will give that a shot instead. I will instantiate a new array in the for loop.

Thanks!