Cocoa error 1671

Last week, I came across quite a serious bug in one of our iOS apps: all application data was lost when the app was quit. This is what happened:

Our app used Core Data to store things to disk, making these persist across app runs. Realising no data was available when the app started for the second time, I had a look at our saving routine:

NSError *error = nil;
BOOL saved = [managedObjectContext save:&error];
if (!saved || error) {
    NSLog(@"Failed to save: %@", error);
}

Nothing out of the ordinary. A break point tells that this code is run, the console shows no error, all good.

Having read this post, I now know to poke a little deeper in a case like this, but I hadn’t. So I wandered though the app for a while, until I finally returned here and put a break point on the line:

NSLog(@"Failed to save: %@", error);

Surprise: it breaks. Turns out our trusty NSLog silently fails to log this error! From here on, things start to roll:

(lldb) po error
(NSError *) $1 = 0x0882eec0 [no Objective-C description available]

Hm, that might be related..

(lldb) p (int)[error code]
(int) $5 = 1671
(lldb) po [error domain]
(id) $6 = 0x00b7655c NSCocoaErrorDomain
(lldb) po [error localizedDescription]
(id) $7 = 0x0d628730 The operation couldn’t be completed. (Cocoa error 1671.)

Google (and Bing) tell me: No results found for “Cocoa error 1671″. While 1671 has been used as a number before, there is absolutely nothing to be found in relation to iOS, Cocoa, or Apple. But wait, let’s try:

(lldb) po [error userInfo]
(id) $8 = 0x0882f080 {
    NSLocalizedDescription = "The operation couldn\U2019t be completed. (Cocoa error 1671.)";
    NSValidationErrorKey = firstLetter;
    NSValidationErrorObject = "<NSManagedObject: 0x882c030> (entity: Person;
        id: 0x882c080 <x-coredata:///Person/t41AD32DB-D331-4A65-A964-FB0B4359BAAA2> ;
        data: {\n firstLetter = \"\\Ud83c\";\n})";
    NSValidationErrorValue = "\Ud83c";
}

Et voila. What all printers failed to show me, the NSDictionary description was able to escape for \U2019readability\U2019 purposes. With great success: “\Ud83c” is not a valid string. A quick search on www.unicode.org/charts tells me I’ve been bitten by something out of the High Surrogate Area.

Isolated surrogate code points have no interpretation; consequently, no character code charts or names lists are provided for this range.

My bad. To determine the fist letter of a person’s name, I naively used:

NSString *firstLetter = [fullName substringToIndex:1];

And NSString isn’t really strict when it comes to the more exotic characters of the Supplementary Multilingual Plane, like emojis. These characters are represented by two 16 bit characters, which makes using substring and character methods a lot less obvious. Apple does warn about this in its documentation:

If you need to access string objects character by character, you must understand the Unicode character encoding, specifically issues related to composed character sequences.

Yes Apple, I’ll read it tomorrow, promise. Anyway, the Characters and Grapheme Clusters article from the iOS String programming Guide provides a valuable introduction, leading me to the proper implementation:

NSString *firstLetter = [fullName substringWithRange:
    [fullName rangeOfComposedCharacterSequenceAtIndex:0]];

All of a sudden everything starts working. Core Data stores first letter “\Ud83c\Udf3″, NSLog logs again, lldb debugs again, ttgtb.

Unicode is great fun, don’t miss out on it! Read some on Wikipedia, for example Supplementary Multilingual Plane. Knowing just a few fundamentals will save you lots of time, especially when people start using the iPhone’s Emoji keyboard. And the second lesson learned: do not trust NSLog.

Who’s name did cause all this trouble in the first place? http://en.wikipedia.org/wiki/%F0%9F%8C%B5.

Leonard van Driel
iOS developer at Noodlewerk