Social

Recent Games

Contact
This form does not yet contain any fields.
    Tuesday
    Mar042014

    The Last (I Hope) Word on NSMutableCharacterSet

    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.

    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.

    Thursday
    Feb202014

    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.

    Wednesday
    Feb192014

    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!

    Thursday
    Feb132014

    UISplitViewController+QuickAccess

    OK, let's switch gears and not talk about cats for a while shall we? Although fair warning, this post will only be interesting to somebody who does iOS programming. Hey sometimes that happens. Last week I sold my car so California could get it dismantled. If you don't want programming articles ask me about that sometime, and maybe I'll write a post about that.


    OK, if you're still around you have some interest in code. Let's dive in. I have a few bits of code that are shared between the two iOS projects I'm working on currently. One is a dirt simple category that lets you quickly access controllers in an iPad UISplitViewController. In theory accessing the left controller is a single line of code, but it's a complicated line:

    - (UIViewController*)leftController {
        return [[self.viewControllers objectAtIndex:0] topViewController];
    }
    

    And this is really the way I think of split views: there's a left controller and a right controller. I understand why that is represented as an array of UINavigationControllers but the whole point is that UISplitViewController should be an abstraction. When I think "OK, we need to notify the left ViewController that I just opened the map view so it can update the UI elements" I just want the left controller. I don't want to start thinking about stacks of controllers contained in an array and figure out the proper string of messages to get the left controller.

    And I do that all over the place. I kept copying that damn string of messages around and every time the "Keep it DRY" birdie would hoot shamefully in my ear. So I finally got fed up enough to make a category to make UISplitViewControlller do what I want. Of course, this wasn't that DRY because I had two projects and they each had these source files but it was better.

    Today I finally got around to sorting out how to put the category in GitHub as its own project, make a local repository, and then pull those files into both projects. This is actually the small code trial run for doing the same thing with some code that works around an iOS bug involving NSMutableCharacterSet. More on that later!

    Anyway, if this category sounds useful to you I cleaned up the source, figured a modern Creative Commons license for it and put it on GitHub. Enjoy and let me know if you have any issues!

    Friday
    Jan242014

    Farewell Schrödinger

    (sigh) There were days where I worried about posting too many cat pics, or too many cat stories and turning this blog into a "cat blog". Well here we are in 2014 and there is probably some question about why I have a blog at all, as much as I use it. There was only one post in 2013 and it was my post about Heisenberg dying. Now I have to write about Schrödinger dying and it's not just a cat blog it's a cat death blog, which is a hideous idea. But still, I feel I should write something about my best little girl.


    Two weeks ago I noticed she was having some trouble peeing and that she generally seems a little listless. I needed to schedule a checkup for her in January anyway so I went and scheduled her an appointment for the following Monday. She seemed better but not 100% on Saturday and Sunday and so I took her thinking she probably had some sort of low-grade illness. The doctor said she was constipated and so they gave her an enema. I had to leave her for most of the day but I picked up her up in the afternoon. She seemed upset, but understandably so and we spent a fairly stressful evening and night with her sleeping alternated with complaints and more … let's just say evacuation.

    Tuesday rolled around and she was very listless and I couldn't get her to eat anything. I talked to her doctor, and we confirmed that she had a bladder or urinary infection. Her doctor said the listlessness could be discomfort from the enema as well as pain from the infection so we started her on antibiotics and a painkiller. Wednesday she still wouldn't eat and I was planning on bringing her in to see the doctor in the afternoon but before that she had a seizure. After the seizure she was almost completely paralyzed so I rushed in for the doctor to see. She was dehydrated and her body temperature was low so they kept her overnight on an IV. Thursday she was still cold and they warmed her up but she never really regained consciousness. Best guess now is that she suffered brain damage during the seizure and by Thursday afternoon it was clear she wasn't coming back and we had her put to sleep.

    When Heisenberg died I was worried that Schrödinger was going to take it poorly and I thought she'd be very negative about any new kittens. I have to admit she stepped up to the plate and was more than willing to snuggle with Karin like Heisenberg had all his life and she generally did a bang-up job of being the only kitty in the household. We'll have cats again around here again, I'm not sure when but for now we'll get used to being a family of two. We need a bit of a break before having cats around won't be a painful reminder of the two we've lost.

    Seventeen years ago I saw this tiny little runt of a fuzzball bossing around all the other kittens and I fell in love immediately. Since then I was privileged to be bossed around the the mouthiest little cat I've ever known and to learn her increasingly intricate little rituals. I learned how to make a fort out of pillows, an armchair and a blanket that she could sleep in when she was cold. I learned when she needed a snuggle, when she needed to be chased up into one of her "safe bases", and when she just needed to sit on the back of an armchair while I read a book. Well, I've made her last fort, I've given out her last piece of crinkly crepe paper to tear up, I've taken my last instruction from my bossy girl, and I've snuggled my last snuggle with my mouthy, smart little sidekick and companion. She's followed her brother off on their last adventure.

    Farewell Schrödinger.