“NSPropertyListWriteStreamError” – When No is Not Enough

July 25th, 2011

Sometimes, ‘NO’ is just not enough.  There are many great features about XCode, the LLVM compiler, Objective-C and the Cocoa Touch libraries but sometimes they just don’t give enough information.  One such case is NSDictionary’s “-(BOOL)writeToURL:atomically:” method.

I was working on a project, and I just wanted to store some information to the documents directory.

[cpp autolinks="false" classname="NSDictionary" firstline="1" gutter="true" tabsize="4" collapse="true"] NSDictionary *allData = [NSDictionary dictionaryWithObjectsAndKeys: imageArray, @"images", contactArray, @"contacts", profilesDict, @"profiles", nil]; [allData writeToURL:filename atomically:YES]; [/cpp]

and of course, writeToURL:, fails with a ‘NO’ value.

I scoured my data for invalid objects, or things that wouldn’t save correctly (i.e. not a valid plist value), but no luck.  All my data looked correct and of course there’s no error code, NSError, or NSException associated with this, so I switched to the more fancy writer hoping to get something more clear, but NSPropertyListSerialization just gave me “Error Code: 3851″ which is “NSPropertyListWriteStreamError“.

Not much more descriptive than ‘NO’…

So I wrote a simple category with a recursive method for NSObject that traverses the object graph and tells me what objects at runtime are compatible or not with a given NSPropertyListFormat (requires iOS 4 and Blocks). Pass ‘nil’ in the first time for ‘path’.

[cpp autolinks="false" firstline="1" gutter="true" tabsize="4" collapse="true"]
@implementation NSObject (DiscoverPLIST)
- (void) discoverPlist:(NSPropertyListFormat)format andPath:(NSString *)path
{
if (![NSPropertyListSerialization propertyList:self isValidForFormat:format]) {
NSLog(@"%@ – not valid for format: %d", path, format);
}
if ([self isEqual:[NSNull null]]) {
NSLog(@"%@ = (NSNull*)%@", path, self);
} else if ([self isKindOfClass:[NSArray class]]) {
[(NSArray*)self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *newPath = [path stringByAppendingFormat:@"[%i]",idx];
[obj discoverPlist:format andPath:newPath];
}];
} else if ([self isKindOfClass:[NSDictionary class]]) {
[(NSDictionary*)self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSString *newPath = [path stringByAppendingFormat:@".%@",key];
[obj discoverPlist:format andPath:newPath];
}];
} else if ([self isKindOfClass:[NSString class]]) {
NSLog(@"%@ = (NSString*)%@", path, self);
} else if ([self isKindOfClass:[NSNumber class]]) {
NSLog(@"%@ = (NSNumber*)%@", path, self);
} else if ([self isKindOfClass:[NSDate class]]) {
NSLog(@"%@ = (NSDate*)%@", path, self);
} else if ([self isKindOfClass:[NSData class]]) {
NSLog(@"%@ = (NSData*)%@", path, [self description]);
} else {
NSLog(@"%@ = Invalid Class: %@", path, [self class]);
}
}
@end
[/cpp]

And that told me the data objects in my tree where all correct!  Except the root and “profilesDict” object – which confused me.  So, I took things out of that dictionary and and put them into an array, and voila!  Saved correctly.

So what was wrong?  I was using NSNumber as a Key for values in “profilesDict” (customer IDs or something similar)  Lesson Learned:  Only NSStrings are valid Keys for saving to a property list, even though you can use NSNumber and even other custom objects as keys for NSDictionaries.

Stephen Furlani

Stephen has worked as a Biomedical Software Engineer on real-time and medical imaging applications on both Windows and Mac OS platforms respectively. As a Mobile Developer he leverages that experience on current projects.

Leave a Reply

Categories

Tags