Archiving an iPad only App

(I've submitted Combat Imp to Apple and while I wait for approval I'm doing some clean-up which includes verifying problems and writing up radars. There may be a handful of these posts coming up.)

Combat Imp is an iPad only project. Way back when I had just plunked some icons in manually (into the .plist I believe? It's been a while.) As I got ready to ship the damn thing I switched over to using an Asset Catalog for the icons and launch images. I only set up the iPad icons, because why include other sizes that wouldn't be used? And this was good.

At some point later in time I went to make an archive (inside Xcode - I was getting ready to make a beta build for testers.) The archive didn't have the nice icon, instead it had the generic grid/circles placeholder icon. Turns out that even in an iPad only app, the archive uses the iPhone iOS 7 icon and doesn't look for other icons if that slot is blank.

Not a huge deal but I was having some other icon problems at the time and I conflated two problems and went down a rabbit hole. So if your Xcode archive is lacking an icon, try adding an iPhone icon.

I wrote it up as radar 18252465 if somebody wants to dupe it. It happens on both Xcode 5.1.1 and Xcode 6 beta 7.

A Month of Kittens!

A month ago yesterday we picked up a couple of kittens from the local shelter. I've tweeted a few pictures but I thought it was time to introduce them here on the blog. I also pulled my DSLR memory card and got around to looking through the pictures I took the day they came home.

First off, here's Shenanigans:

IMG_3336

And here's Hijinks:

IMG_3370

If you like those images there's a bunch more on Flickr

I'll call out them meeting up on that first day:

IMG_0542

and wrap up my photo love-fest with a picture of them from just a few days ago:

IMG_0678

It's so good to have cats around the house again!

The Last (I Hope) Word on NSMutableCharacterSet

Edited to add 2014-09-05: Xcode 6 beta 7 emits a warning for assigning the NSCharacterSet to a NSMutableCharacterSet *. I've never received feedback from Apple on the matter but since it's caught now I marked the radar as resolved.

Edited to add 2014-03-07: Filed a Radar for this bug. I can't link to that, but I also filed it on [Open Radar](https://openradar.appspot.com/radar?id=5874203416854528).

Edited to add: Reading the code snippets in here is difficult. I can't find a quick & easy way to make them have horizontal scrollbars if I use a pre tag around it. Rather that have them be truncated I removed the pre tags for now so they word wrap. I'll bug Squarespace about it later.

This NSMutableCharacterSet bug! I keep poking at it, going "OK I guess that makes sense" and then a day later going "Wait. That can't be right." Quick review. The following line of code:

NSMutableCharacterSet* fakeMutableSet = [NSCharacterSet decimalDigitCharacterSet];

seems to work. But although the object you get will respond to NSMutableCharacterSet selectors it won't actually do anything. Specifically you can write this:

[fakeMutableSet formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];

formUnionWithCharacterSet returns a void so you can't tell if it worked or not, and in fact it will not work. Conceptually now I should have a set with the decimal digits and whitespace. What I have (as far as I can tell, you can't probe much into NSCharacterSets) is the exact same set I had before this call. If you want a mutable copy you have to call mutableCopy like so:

NSMutableCharacterSet* mutableSet = [[NSCharacterSet decimalDigitCharacterSet] mutableCopy];

So far so good. I understand why I need mutableCopy, but at first I didn't understand why A ) fakeMutableSet wasn't mutable and B ) given that it wasn't mutable calling formUnionWithCharacterSet didn't cause an unrecognized selector assert. Seems like either it should be mutable or it shouldn't respond to the selector.

I finally got around to writing some test code and I've found enough that I'm convinced there is a real bug here and it's not at all what I suspected at first. I'll write this up in a radar after I finish this post. I've placed the source on GitHub if you want to follow along.

The full test code does a bunch of introspection on a variety of NSCharacterSet and NSMutableCharacterSet objects. But here's the smoking gun part:

NSCharacterSet* decimalSet = [NSCharacterSet decimalDigitCharacterSet];
NSMutableCharacterSet* fakeMutableSet = [NSCharacterSet decimalDigitCharacterSet];
NSMutableCharacterSet* mutableSet = [[NSCharacterSet decimalDigitCharacterSet] mutableCopy];

All three of these end up pointing at an instance of an internal class called _NSCFCharacterSet. So yeah. The docs say NSMutableCharacterSet is derived from NSCharacterSet but not really in the sense I think of it. And guess what: _NSCFCharacterSet responds to the NSMutableCharacterSet methods! See:

// So here's the catch right? _NSCFCHaracterSet can respond to formUnionWithCharacterSet but

// *DOESN'T WORK* if the underlying data is not mutable.

if ([[decimalSet class] instancesRespondToSelector:@selector(formUnionWithCharacterSet:)]) {

NSLog(@"Attempting union with decimal set");

// Have to cast this call to compile, but note no cast is needed for fakeMutableSet

// Also note this is NOT an unrecognizedSelector: we respond to this method

[(id)decimalSet formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];

}

Admittedly, yes I have cast decimalSet so the compiler does not complain but I expected the runtime to skip the call because the instances shouldn't respond to the selector. Somewhere up inside formUnionWithCharacterSet there is code that says "if I'm not mutable just sort of fail silently." It would have saved me a ton of time if that had at least logged a line that says "this call didn't work".

So yeah, if NSMutableCharacterSet objects were really a different class than NSCharacterSet objects I would have had a compiler error when I wrote the offending line without the mutableCopy. If NSCharacterSet objects didn't have methods to responds to selectors from NSMutableCharacterSet then I would have had a runtime assert. The NSCharacterSet and NSMutableCharacterSet API's sort of falsely represent what the data does. I don't see anyway to catch this happening from my code. So if you're using NSMutableCharacterSet be careful.

Does this extend to other NSMutable* classes? I don't know. These are the only ones I use where there are a lot of "return a pre-populated instances" method so I'd have more trouble making a real world example using NSMutableArray or NSMutableDictionary.

More iOS Utility Categories!

The more I think about that whole NSMutableCharacterSet thing I'm not happy with my current understanding. I may dig into that more in the near future. But at the moment I'm on my tear of pulling some common/utility code that I use in both Road Trip and Combat Imp into a common repository and putting a Creative Commons license on it. Today's code is adjacent to the NSMutableCharacterSet issue, but not directly related so I can dodge the "Why didn't this crash before I fixed it?" question for now. Quick summary of the problem:

I have many UITextFields where I want "numeric" values entered. I say "numeric" in quotes because it's quite a bit more complex than that. In Road Trip I need to accept strings like "$ 12,345.67". In Combat Imp I have some fields that are "just" numeric (round counter) and some that can take + and - signs (hit points, initiative modifier). All the fields need to take control characters or you can't backspace (and with an external keyboard you can hit things like Shift+Left Arrow to do selection.)

Shameful confession time: my original algorithm for this built a big list of all the characters I would ever want and then inverted the set. The reason for this is that I could call string rangeOfCharacterFromSet and find the first character in a string that was from a set. If the range returned anything other than NSNotFound I knew an illegal character was in the string and I could chuck it. This always seemed super backwards to me. I finally got around to doing what I wanted to do: I wrote a category for NSString that returns YES if all of the characters in the string are members of a set. Now instead of writing:

NSRange foundRange = [string rangeOfCharacterFromSet:nonlegalCharacters]];
if (foundRange.location != NSNotFound) {
    NSLog(@"String is %@, location is %d", string, foundRange.location);
    return NO;
} else {
    return YES;
}

Instead I build the character set of legal characters, cache that, and simply write:

return [string isContainedInCharacterSet:initiativeAndHPLegalCharacterSet];

(Both of these samples live inside textField:shouldChangeCharactersInRange:replacementString methods. Idea is when the user enters text I immediately validate the characters. If it's a 'g' and I only want numbers I reject it immediately. Doesn't even show up on screen.)

Much like the last stupid little helper category I plunked this in its own GitHub. Feel free to grab it if you need. You may notice that there are obvious other tests I could write it this category. I may in the future but for now, this is the only one I need. I'm not pretending to write some sort of comprehensive set of tests here, I'm just providing little snippets of code that I've found useful enough to isolate out and share in projects.

iOS Programming PSA

If you've read and used my post about converting currency strings on iOS you'll want to check the update I just posted. I had a nasty bug in RoadTrip under iOS 7 where it wouldn't accept the correct characters in the various cost fields. It turned out that when I wrote the code originally there was a line that says something like:

NSMutableCharacterSet* mutableSet = [NSCharacterSet decimalDigitCharacterSet];

At the time, this would give you y'know a NSMutableCharacterSet. Under iOS 7 it gives you a NSCharacterSet and calling a mutate method on it will just fail. Since they all return void you can't tell why it didn't work. (Or indeed, even tell that it didn't work. The symptom in my specific code was that not all of the characters I thought were in the set showed up there. There was no error or print statement or crash or anything.) Instead you need to write something like:

NSMutableCharacterSet* mutableSet = [[NSCharacterSet decimalDigitCharacterSet] mutableCopy];

I think (but can't confirm 100%) that what happened was in the early days of ARC that line invoked a copy operation and thus created a mutable set. But as time wore on somebody enhanced that and now it just references a prebuilt set. And the rest of the problem is just the way Objective-C works. I understand why it happens, but it wasn't something I expected, especially coming back into this code after doing C++ for a year.

Anyway, sorry if this bit anybody on the rear end!