iOS array not being filled completely - iphone

I am using these two recursive methods to find the paths of files and directories in a certain folder
- (NSMutableArray *)getFilePathsFromDirectory:(NSString *)directory{
NSMutableArray *contents = [[NSMutableArray alloc] init];
NSArray *arr = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directory error:nil];
for (NSString *file in arr) {
BOOL isDir;
[[NSFileManager defaultManager] fileExistsAtPath:[directory stringByAppendingPathComponent:file] isDirectory:&isDir];
if (!isDir) {
[contents addObject:[directory stringByAppendingPathComponent:file]];
}
else{
[contents addObject:[directory stringByAppendingPathComponent:file]];
[contents addObject:[self getFilePathsFromDirectory:[directory stringByAppendingPathComponent:file]]];
}
}
return contents;
}
- (NSString *)getPathForItemNamed:(NSString *)name array:(NSMutableArray *)arr{
NSString *str;
if (name) {
for (NSString *s in arr) {
if ([s isKindOfClass:[NSString class]]) {
if ([[s lastPathComponent] isEqualToString:name]) {
return s;
}
}
}
for (NSMutableArray *aq in arr) {
if ([aq isKindOfClass:[NSMutableArray class]]) {
str = [self getPathForItemNamed:name array:aq];
return str;
}
}
}
return str;
}
but the problem is, after going through a certain amount of subdirectories (3-5), this stops returning any path and returns (null). I feel like this has to do with the array not being filled with all the directories before it returns for some reason. Heres how I call these
NSMutableArray *paths = [self getContentsOfPaths:[self downloadsDir]];
path = [self getPathForItemNamed:[tableView cellForRowAtIndexPath:indexPath].textLabel.text array:paths];
NSLog(#"%#", path);

There are two problems with your getPathForItemNamed: method:
When it cannot find a file by name, it returns a value of an uninitialized variable str. This is undefined behavior - you need to set str to nil upon initialization. In fact, you do not need str at all (see the fix below).
When it discovers its first subdirectory, it assumes that the file that it is looking for must be inside that subdirectory, even if it is not. Whatever the first-level recursive invocation of getPathForItemNamed: returns, becomes the return result of the top-level invocation. This is bad: if the file that you are looking for is in the subtree of the second subdirectory, you are never going to find it!
Here is how you can fix your method:
- (NSString *)getPathForItemNamed:(NSString *)name array:(NSMutableArray *)arr{
if (!name) return nil;
for (NSString *s in arr) {
if ([s isKindOfClass:[NSString class]]) {
if ([[s lastPathComponent] isEqualToString:name]) {
return s;
}
}
}
for (NSMutableArray *aq in arr) {
if ([aq isKindOfClass:[NSMutableArray class]]) {
str = [self getPathForItemNamed:name array:aq];
// Return something only when you find something
if (str) return str;
}
}
return nil; // You do not need str at all.
}

Related

How to parse JSON and have 2 final arrays of data

I am parsing an itunes rss feed with JSON but I have run into a problem. The following code is running properly for one the movieName output but I still don't get the movieSummary output.
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
allDataDictionary = [NSJSONSerialization JSONObjectWithData:webData options:0 error:nil];
feed = [allDataDictionary objectForKey:#"feed"];
arrayOfEntry = [feed objectForKey:#"entry"];
for (NSDictionary *dictionTitle in arrayOfEntry) {
NSDictionary *title = [dictionTitle objectForKey:#"title"];
NSString *labelTitle = [title objectForKey:#"label"];
[arrayLable addObject:labelTitle];
NSDictionary *summary = [dictionTitle objectForKey:#"summary"];
NSString *labelSummary = [summary objectForKey:#"label"];
[arraySummary addObject:labelSummary];
}
movieName.text = [arrayLable objectAtIndex:0];
movieSummary.text = [arraySummary objectAtIndex:0]; //This is not displaying
}
Here is the link that I am parsing: http://itunes.apple.com/us/rss/topmovies/limit=300/json
I run into this situation a lot. I use something like this. Replace your code
NSString *labelTitle = [title objectForKey:#"label"];
[arrayLable addObject:labelTitle];
with
NSString * labelTitle = [ [ title objectForKey:#"label" ] ifNullThenNil ] ;
[ arrayLabel addObject:labelTitle ? labelTitle : #"" ] ; // you could also use #"<unknown>" or similar instead of #""
where -ifNullThenNil is provided via category:
#implementation NSObject (IfNullThenNil)
-(id)ifNullThenNil { return self ; }
#end
#implementation NSNull (IfNullThenNil)
-(id)ifNullThenNil { return nil ; }
#end
The problem was that when I was adding the strings to the Array that it sometimes contained NULL's thus the following code helped me out
if ([[arrayName objectAtIndex:0] isKindOfClass:[NSNull class]]) {
labelName.text = #"This is NULL";
} else {
[arrayName addObject:labelName];
}
if ([[arraySummary objectAtIndex:0] isKindOfClass:[NSNull class]]) {
labelSummary.text = #"This is NULL";
} else {
[arraySummary addObject:labelSummary];
}

Memory Leak in NSObject+JSONSerializableSupport

while removing the runtime memory leaks in my iPad application , I came across this strange memory leak in NSObject+JSONSerializableSupport class in the following method
+ (id) deserializeJSON:(id)jsonObject {
id result = nil;
if ([jsonObject isKindOfClass:[NSArray class]]) {
//JSON array
result = [NSMutableArray array];
for (id childObject in jsonObject) {
[result addObject:[self deserializeJSON:childObject]];
}
}
else if ([jsonObject isKindOfClass:[NSDictionary class]]) {
//JSON object
//this assumes we are dealing with JSON in the form rails provides:
// {className : { property1 : value, property2 : {class2Name : {property 3 : value }}}}
NSString *objectName = [[(NSDictionary *)jsonObject allKeys] objectAtIndex:0];
Class objectClass = NSClassFromString([objectName toClassName]);
if (objectClass != nil) {
//classname matches, instantiate a new instance of the class and set it as the current parent object
result = [[[objectClass alloc] init] autorelease];
}
NSDictionary *properties = (NSDictionary *)[[(NSDictionary *)jsonObject allValues] objectAtIndex:0];
NSDictionary *objectPropertyNames = [objectClass propertyNamesAndTypes];
for (NSString *property in [properties allKeys]) {
NSString *propertyCamalized = [[self convertProperty:property andClassName:objectName] camelize];
if ([[objectPropertyNames allKeys]containsObject:propertyCamalized]) {
Class propertyClass = [self propertyClass:[objectPropertyNames objectForKey:propertyCamalized]];
[result setValue:[self deserializeJSON:[propertyClass deserialize:[properties objectForKey:property]]] forKey:propertyCamalized];
}
}
}
else {
//JSON value
result = jsonObject;
}
return result;
}
I am getting the memory leak on this line
[result setValue:[self deserializeJSON:[propertyClass deserialize:[properties objectForKey:property]]] forKey:propertyCamalized];
Please suggest a solution or tell me where i am going wrong.

Creating Files And Saving Objects In Them

Why when you call this method (saveObject: (id)object forKey: (NSString *) key), will a file not get created???
When I call it filePath is equal to nil so the (-(void) setFilePath: (NSString *)fileName) method will get called...
-(int) saveObject: (id)object forKey: (NSString *) key {
//save object into file
if(filePath == nil) {
[self setFilePath:nil];
}
mainDict = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
if(mainDict == nil) {
mainDict = [[NSMutableDictionary alloc] init];
}
[mainDict setObject:object forKey:key];
if(![mainDict writeToFile:filePath atomically:YES]) {
if(object == nil) {
return 3;
}
if(key == nil) {
return 4;
}
return 2; //ERROR could not write object to file
} else {
if(shouldUseCache == YES) {
cacheStatus = 2; //New Caches need to be updated
}
return 1; //SUCCESS object saved to file
}
//RETURN KEY's
// *1 SUCCESS object saved to file
// *2 ERROR could not write object to file
// *3 ERROR object variable = nil
// *4 ERROR key variable = nil
}
-(void) setFilePath: (NSString *)fileName {
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentsDirectory = [paths objectAtIndex:0];
if(fileName == nil) {
fileName = #"savedObjects";
}
filePath = [NSString stringWithFormat:#"%#/%#.plist",documentsDirectory, fileName];
}
I think the problem is with the contents of the dictionary you are intending to write.
You are adding object to your dictionary.
That object must only contain members of types:NSArray , NSDictionary , NSString , NSDate, NSData and NSNumber. If your object contains a int or float , or anything other than these , it won't write.
You can read more here

NSArray of many NSDictionary. What is the best way to find a NSDictionary with necessary value for given key?

Now I'm trying the following and it works.
- (void)findDictionaryWithValueForKey:(NSString *)name {
for (NSDictionary * set in myArray) {
if ([[set objectForKey:#"title"] isEqualToString:name])
\\do something
}
}
EDIT:
I've added one extra argument to the post of bshirley. Now it looks more flexible.
- (NSDictionary *)findDictionaryWithValue:(NSString*)name forKey:(NSString *)key {
__block BOOL found = NO;
__block NSDictionary *dict = nil;
[self.cardSetsArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
dict = (NSDictionary *)obj;
NSString *title = [dict valueForKey:key];
if ([title isEqualToString:name]) {
found = YES;
*stop = YES;
}
}];
if (found) {
return dict;
} else {
return nil;
}
}
Here's one possible implementation using newer API. (I also modified the method to actually return the value). Provided mostly to demonstrate that API. The assumption is that the title is unique to one dictionary within your array.
- (NSDictionary *)findDictionaryWithValueForKey:(NSString *)name {
// ivar: NSArray *myArray;
__block BOOL found = NO;
__block NSDictionary *dict = nil;
[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
dict = (NSDictionary *)obj;
NSString *title = [dict valueForKey:#"title"];
if ([title isEqualToString:name]) {
found = YES;
*stop = YES;
}
}];
if (found) {
return dict;
} else {
return nil;
}
}
Use filteredArrayUsingPredicate: method of the array to get all the dictionaries that satisfy your requirement.
NSPredicate * predicate = [NSPredicate predicateWithFormat:#" title MATCHES[cd] %#", name];
NSArray * matches = [myArray filteredArrayUsingPredicate:predicate];
Now matches is the array of dictionaries that have the title key equal to name.
- (void)findDictionaryWithValueForKey:(NSString)name {
for (NSDictionary * set in myArray) {
NSString *s=[NSString stringWithFormat:#"%#",[set objectForKey:#"title"]];
if ([s isEqualToString:name])
\\do something
}
OR
if (s == name])
\\do something
}
}
I will also suggest this way,it would be better if you use a break statement,
- (void)findDictionaryWithValueForKey:(NSString)name {
for (NSDictionary * set in myArray) {
if ([[set objectForKey:#"title"] isEqualToString:name])
\\do something
break;
}
}
As per the NSArray documentation,
valueForKey:
Returns an array containing the results of invoking valueForKey: using key on each of the array's objects.
- (id)valueForKey:(NSString *)key
Parameters
key
The key to retrieve.
Return Value
The value of the retrieved key.
Discussion
The returned array contains NSNull elements for each object that returns nil.
Availability
* Available in Mac OS X v10.3 and later.
EDIT:
try this,
[myArray valueForKey:#"name"];
//this will return array of values, but this actually differ from what to want

unarchiveObjectWithFile retain / autorelease needed?

Just a quick memory management question if I may ... Is the code below ok, or should I be doing a retain and autorelease, I get the feeling I should. But as per the rules unarchiveObjectWithFile does not contain new, copy or alloc.
-(NSMutableArray *)loadGame {
if([[NSFileManager defaultManager] fileExistsAtPath:[self pathForFile:#"gameData.plist"]]) {
NSMutableArray *loadedGame = [NSKeyedUnarchiver unarchiveObjectWithFile:[self pathForFile:#"gameData.plist"]];
return loadedGame;
} else return nil;
}
or
-(NSMutableArray *)loadGame {
if([[NSFileManager defaultManager] fileExistsAtPath:[self pathForFile:#"gameData.plist"]]) {
NSMutableArray *loadedGame = [[NSKeyedUnarchiver unarchiveObjectWithFile:[self pathForFile:#"gameData.plist"]] retain];
return [loadedGame autorelease];
} else return nil;
}
You are correct in that unarchiveObjectWithFile returns an autoreleased object, since it doesn't contain new, copy or alloc.
Here's a version that is slightly re-written to use common Objective-C formatting idioms:
- (NSMutableArray *)loadGame {
NSString *gameDataPath = [self pathForFile:#"gameData.plist"];
if([[NSFileManager defaultManager] fileExistsAtPath:gameDataPath]) {
return [NSKeyedUnarchiver unarchiveObjectWithFile:gameDataPath];
}
return nil;
}