Category: Programming


Displaying Line Numbers with NSTextView

October 5th, 2008 — 8:16pm

Yes, it’s free code time again. I’ve been neglecting the blog for some time so hopefully this will make up for it. Think of it as that conciliatory heart-shaped box of chocolates used as a sorry way to make up for forgetting about your birthday, after which, I go back to my old ways of sitting on the couch all day watching sports, ignoring you.

In version 2.2 of Hazel, I added mini AppleScript and shell script editors so that people could enter scripts inline without having to go to another program and saving it to an external file. I’ll admit, I didn’t set out to make an uber-editor since it was intended for small scripts. Nonetheless, a user recently pointed out that when a line wraps, it’s hard to tell if it’s a continuation of the previous line or a new one. One of his suggestions was putting line numbers in the left gutter. If you don’t know what I’m talking about, look at TextMate (the example he cited) or XCode (you need to turn it on in preferences). I thought it might be overkill for a script editor that will mostly be used for scripts less than ten lines long. I’m instead considering doing an indented margin for continuation lines. Less visual clutter and addresses the problem at hand.

Nonetheless, I was curious about implementing line numbers. Poking around, I found some tips on how to do it but it seemed like there were odd problems implying it wasn’t as straightforward as one would think. So, snatching some free time in between other things, I decided to tackle the problem.

I looked into subclassing NSRulerView. The problem is that NSRulerView assumes a linear and regular scale. Now, to make it clear, I am talking about numbering logical lines, not visual ones. If a line wraps, it still counts as one line even if it takes two or more visually. The scale is solely dependent on the layout of the text and can’t be computed from an equation. Despite these limitations, I went ahead and subclassed NSRulerView. If anything, NSScrollView knows how to tile it.

I had this notion that NSRulerView was a view that synced its dimensions with the document view of the scrollview. With a vertical ruler, I assumed it would be as tall as the document and the scroll view just scrolls it in tandem with the document. Not so. It’s only as tall as the scrollview. That means you have to translate the scale depending on the clipview’s bounds.

I added some marker support via an NSRulerMarker subclass that knows about line numbers. The line number view will draw the markers underneath the labels a la XCode (with the text inversed to white). The sample project uses another subclass which will toggle markers on mouse click. While NSRulerView usually delegates this to its client view it made more sense to just do it in a subclass of NSRulerView. You have to subclass something to get it to work and it made more sense to subclass the ruler view since the code to handle markers never interacts with anything in the client view anyways. Personally, I find it an odd design on Apple’s part and would have preferred a regular delegate.

The project is linked below. The main classes are NoodleLineNumberView and NoodleLineNumberMarker. Some notes:

  • To integrate: just create the line number view and set it as the vertical ruler. Make sure the document view of the scrollview is an NSTextView or subclass. Depending on the order of operations, you may have to set the client view of the ruler to the text view.
  • The view will expand it’s width to accommodate the widths of the labels as needed.
  • The included subclass (MarkerLineNumberView) shows how to deal with markers. It also shows how to use an NSCustomImageRep to do the drawing. This allows you to reset the size of the image and have the drawing adjust as needed (this happens if the line number view changes width because the line numbers gained an extra digit).
  • Note that markers are tied to numerical lines, not semantic ones. So, if you have a marker at line 50 and insert a new line at line 49, the marker will not shift to line 51 to point at the same line of text but will stay at line 50 pointing at whatever text is there now. Contrast with XCode where the markers move with insertions and deletions of lines (at least as best as it can). This is logic that you’ll have to supply yourself.

More details, including performance notes, can be found in the Read Me file included in the project.

I’m putting this out there because I’m probably not going to use it and it seems like a waste of some useful code. Also, my apologies to the user who asked for this feature. I feel like somewhat of a jerk going through the trouble of implementing the feature and not including it. It was more of a fun exercise on my part but I still feel it’s not suitable for Hazel. That said, I may consider adding it and having it available via a hidden default setting. Votes for or against are welcome.

In the meantime, you can use the code however you want. MIT license applies. Please send me any bug reports, suggestions and feedback.

Enjoy.

Download Line View Test.zip (version 0.4.1)

Update (Oct. 6, 2008): Uploaded version 0.3. Fixes bugs found by Jonathan Mitchell (see comments on this post). Also made line calculations lazy for better performance.

Update (Oct. 10, 2008): Uploaded version 0.4. Fixes bugs mentioned in the comments as well as adds methods to set different colors. There is a display bug that happens when linking against/running on 10.4. See the Read Me for details.

Update (Oct. 13, 2008): Uploaded version 0.4.1. Figured out the 10.4 display bug. Apparently, NSRulerView’s setRuleThickness: method doesn’t like non-integral values. Rounding up solves the problem. Thanks to this page for identifying the problem.

Update (Sep. 29, 2009): I have included this class in my NoodleKit repository so you should check there for future updates.

22 comments » | Cocoa, Downloads, OS X, Programming

New Tool On The Block: The LLVM/Clang Static Analyzer

July 7th, 2008 — 10:30pm

Over the weekend, Gus Mueller turned me on to the LLVM/Clang static analyzer. And just in time, too, as I was polishing up my 2.2 release (which went up earlier today).

It’s an offshoot of the LLVM and Clang projects (read the respective pages on what they are if you don’t know already). The static analyzer analyzes your code and looks for problems, focusing mainly on memory allocation patterns, in this case, including Objective-C/Cocoa (retain/release/autorelease) and CF (CFRetain/CFRelease) semantics.

Take this contrived example for instance:

  id foo()
  {
      NSArray       *array = [[NSArray alloc] init];

      if ([array count] > 0)
      {
        return nil;
      }
      return [array autorelease];
  }

The example above will get you a report like this (it generates html):

checker1.png

Drilling down you get this (still in html):


checker2.png
(click to enlarge)

Here you can see it pointing out [1] where the object was allocated [2] the branch it took and [3] the point where you leaked it. Pretty neat. It tries to follow every possible branch finding paths where you may have leaked an object. It also finds what it calls “dead stores” (when you assign a value to a variable but never use it again) and missing dealloc methods

As the project page says, it is very early in development. You’ll find that it does turn up a lot of false positives, especially with the missing deallocs. False positives for memory leaks seem to occur when you release something in a different scope than where you created it. For instance, I have this chunk of Apple code that wraps CFRelease() with it’s own function that checks for NULL first. The checker complained about this every time. Nonetheless, it did turn up some real leaks for me.

Aside from reducing the number of false positives, I’d also like to see the entries grouped by source file (it’s annoying jumping around between files) as well as some way to bring up the original source file by clicking on its name in the source code view. You will also see multiple entries for the same leak when the code traverses multiple paths that end up with the same leak which can be annoying.

In any case, I recommend downloading it and giving it a try. I’m not sure how thorough it is (i.e. whether it can supplant running your program through MallocDebug/Instruments/leaks) but it makes a great additional tool to add to your arsenal. Chances are it will look at some code path that you don’t test. Oh, and a couple tips:

  • Make sure you do a clean build on your project first. The checker only runs on files that would normally be compiled (it sits in as your compiler). If your project is already built, then no files will be compiled/analyzed.
  • Use the -V option, which will pop open a browser with the analysis page when done. Normally, it sticks the files somewhere under /tmp but only shows the actual path when you start the run. Needless to say, that bit of text scrolls off pretty quickly.
  • While the tool does come up with false positives, you’ll find that sometimes it finds something subtle that you may blow off as a false positive on first glance. Make sure you understand what it is flagging, even if it ends up being wrong.

I haven’t used it with a garbage collected program so I don’t know if it uses different techniques in such a case or is just plain unnecessary. Maybe the dead store detection becomes more important. Reports from anyone using this with GC are welcome.

3 comments » | Debugging, Programming, Software, Tools

Adventures in Debugging: Pref Pane Poppin’ Aplenty

April 23rd, 2008 — 3:34pm

As you may or may not know, Hazel is packaged as a preference pane. While you do all the configuration via the preference pane interface, the actual work of running your rules is done by background processes. The commandline tool that actually runs the rules is stored inside the pref pane bundle. I’ve kept it in the Resources part of the bundle for no good reason except that’s the catch all for all your bundle stuff. But then I thought, “why not stick it in the MacOS dir. That’s where executables are supposed to go, right?”

So, I went into XCode and created a new “Copy Files” phase to copy the built executables into the “executables” dir (which ends up being the MacOS dir in your bundle). I modified the code that launches the program to look in the right place (NSBundle’s -pathForAuxiliaryExecutable: for those of you keeping score at home). It seemed to work. My bundle was now a tad tidier with things in their proper place.

Later on, I was testing something else and noticed that when Hazel was running certain rules, the pref pane icon would show up in the dock and then disappear when the background program was done. Strange enough that a pref pane icon would appear in the dock; stranger that it was being triggered by a non-GUI program. More testing revealed that it happened when Hazel was executing AppleScripts. I tried isolating the AppleScript parts. I checked all sorts of paths. I couldn’t figure out what was causing it. The Pope was in town so maybe he was messing things up somehow.

I checked previous versions to isolate when the bug was introduced and discovered it was only in the last set of changes. Moving the executables back to Resources fixed it.

Problem solved but I had no idea why. After muttering about it in IRC, Mike Ash casually throws out the explanation: “the binary is hitting the window server, the window server notices that it’s in an executable directory of a bundle, sticks it in the Dock.”

Of course, it all sounds so simple when you hear the answer. Calling the AppleScript created the window server connection which then prompted it to throw the icon in the dock as if it were a regular app. Not sure how long it would have taken me to figure that out on my own.

The lesson here is that when faced with a crazy bug, ask Mike Ash. Really, though, don’t do that. He’ll crash his glider into me next chance he gets. The real lesson is that you should be careful about putting executables for background programs in the MacOS directory of your bundle.

Ok, so I can’t pin this one on the Pope. All I know is that I don’t get these types of problems when the Dalai Lama is in town.

2 comments » | Cocoa, Debugging, Hazel, Programming, Xcode

Ruby on Rails: First Impressions

March 31st, 2008 — 7:00pm

In getting Potion Store integrated with my site, I had to learn Ruby on Rails. RoR has always been one of those things I’ve been curious about so I saw it as an opportunity to dive in. I present here a first impression. Note that these are impressions. They represent how I perceived things coming to it for the first time. While chances are that these perceptions are inaccurate, there’s something to be said for seeing how things appear to fresh eyes.

Ruby is an interesting language. It appears to be very flexible and dynamic, allowing you to do a bunch of really neat things that you can’t in most other languages. Note that these features aren’t really unique to Ruby. My sense is that Ruby is not a distinctive language in itself so much as a mostly good mix of other languages. I say “mostly” because I’m not sold on the syntax. It reminds me too much of Perl. It’s like they created a great new flavor of ice cream and then mixed in glass and razor blades.

My advice to those picking up Rails for the first time, get the Rails book and read the first few chapters before diving in. I found the online tutorials kinda annoying and not as good at explaining the structure and conventions that Rails relies on. Rails’ strongpoint is streamlining common tasks and functionality but to do so, it expects things to be laid out in a certain way. The other thing that takes time to figure out is the “magic” that Rails performs at certain points. One of the great things about Rails is how certain things are automatic. Very little glue needs to be coded and I feel that’s a wonderful thing. On the downside, sometimes this magic makes it hard to figure out what’s going on. For instance, some classes had naming patterns for their methods that you had to find out about in the documentation. I found the quality of the documentation to be a bit uneven which didn’t help in some cases.

I feel most of the problems with Rails are less about the design and more about the implementation. For instance, Rails is a memory hog. And because Rails is single-threaded, you need to have at least two of these things sitting there to get any sort of concurrency. When I deployed my store, I had to upgrade my SliceHost slice as the 256M slice wasn’t cutting it (remember I’m also running mysql and PHP stuff). Also, I felt deployment was a bit clumsy, as I had to set up a cluster of mongrel instances to run my app with apache acting as a proxy. While I see that there is a mod_ruby for apache out there, no one I know recommended it. Nonetheless, I felt like I had to deal with more moving parts than was necessary.

In a nutshell, Rails: great, Ruby: enh. If you are going to buy a book on it, get the Rails book first. It has an appendix on Ruby that should be sufficient for most things. If you want to go deeper with the Ruby side of things, you can get the Ruby book as well but personally, I could have done without it.

Overall, I like the platform but at the same time, I can see that it’s relatively immature and has some ways to go. I’m pretty much tied to RoR for now since it’s what the store is written in but that’s not a bad thing. That said, I also am not so amazed that I am going to join the Ruby cult.

Nonetheless, RoR feels like how a web framework should and less like a bloated platform where you end up installing a hundred frameworks requiring just as many config files to be edited to solve non-existent problems that are justified because someone gave it a fancy technical name along with a contrived design pattern to fix it (if you need a hint as to what I’m referring to: “starts with a J”). RoR seems to be able to identify and address the actual problems people run into when creating web applications, all the while keeping it relatively simple, and that is refreshing to see.

6 comments » | Programming, Ruby on Rails, Web

Modal Glue

March 10th, 2008 — 9:51am

Recently, Quentin Carnicelli of Rogue Amoeba asked if there were NSResponder methods that you could hook your “OK” and “Cancel” buttons to to dismiss a modal panel (or sheet). As far as I knew there wasn’t but, gosh darnit, that would be a useful thing to have.

To clarify what I’m talking about here, when you run your own modal window or sheet with “OK” and “Cancel” buttons (or some equivalents), you end up hooking those up to methods that dismiss the window/sheet, stop the modal session and return some code (either one for confirmation or cancellation). Most of the time, you end up writing the exact same code. It’s glue code that shouldn’t have to be written.

Now, if you look at NSResponder, you’ll see all sorts of action methods in place. Sticking these glue methods into NSResponder will allow you to hook up your “OK” and “Cancel” buttons to the “First Responder” in IB. The idea here is that there is a default implementation that will close the current modal window or sheet and set the return code to either NSOKButton or NSCancelButton. With this, your code can act more like it’s using NSRunAlertPanel() or NSBeginAlertSheet() by just interpreting the return code.

I’ve created an NSResponder category with the methods -confirmModal: and -cancelModal: to which you can hook up your “OK” and “Cancel” buttons in IB. Note that you may have to manually add the methods to NSResponder in IB as it doesn’t know about them.

Now NSResponder’s versions of these methods don’t actually do anything besides pass it on to the next responder. The main part is in NSWindow, which overrides it. It will check to see if it’s the current modal window or sheet, order itself out, stop the modal session and send back the appropriate code. By using the responder chain, this mechanism will find the “nearest” modal window. Note that it is assumed here that the buttons to dismiss a modal window are in the same window. If you want to dismiss it from a different window, it may or may not work (depending on the responder chain and other subtleties such as the odd specification of NSWindow’s -isSheet method). I can’t think of a non-contrived case where you’d want this or care but if one comes up, let me know.

So here’s the download. Suggestions, questions, bug reports appreciated.

modalresponder.zip

2 comments » | Cocoa, Downloads, OS X, Programming

Happy Fun Leopard Bug Time: NSCalendar

January 25th, 2008 — 10:38am

Yes, folks, it’s Happy Fun Bug Time where I talk about what has been making me tear my hair out recently. In this installment, we talk about NSCalendar.

NSCalendar has a method called rangeOfUnit:inUnit:forDate:. What this method is supposed to do calculate how many of one time unit are in another. For instance, how many days are in a particular month. Since date calculations can get tricky, with daylight savings, leap years and all, this method is quite handy.

Or, it would be handy if it didn’t suffer from some pretty bad bugs on Leopard. At first, I tried something like the following to calculate the number of days in a given year:

  range = [[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit
                                             inUnit:NSYearCalendarUnit
                                            forDate:[NSDate date]];
                                  
  NSLog(@"%@", NSStringFromRange(range));

Given that this is a leap year, I’d expect “{1, 366}”. What do I get? “{1, 31}”. I suspect that wires got crossed with the days-in-a-month calculation. Oh, but the fun doesn’t stop there. Try this at home (kids: do not try this at home):

  NSCalendarDate          *date;
  int                     i;
  
  date = [NSCalendarDate dateWithYear:2007 month:1 day:1 hour:0
                               minute:0 second:0 timeZone:nil];
  
  for (i = 0; i < 365; i++)
  {
    range = [[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit
                                               inUnit:NSWeekCalendarUnit
                                              forDate:date];
    
    if (range.length != 7)
    {
      NSLog(@"Date %@ has week of length %d", date, range.length);
    }
    date = [date dateByAddingYears:0 months:0 days:1 hours:0 minutes:0 seconds:0];
  }

This cycles through each day of last year (2007) and calculates the number of days that week. Now, having just recently lived through that year, I think I can say with some certainty that no week had any more or less than 7 days in it. The above code prints out any oddballs. If you run it yourself, you find that quite a few days are in weeks exceeding 7 days. Upon closer inspection, you find that any day in a week straddling two months reports the number of days in the month, not the week. On weeks fully contained within a month, it reports 7 days. Again we are seeing a tendency towards the days-in-a-month calculation. Someone really likes computing that.

I've filed a bug report (rdar://5704940 for you Apple folks). Unless I'm doing something really wrong here or there were some time distortions last year that I was unaware of, you may want to avoid using NSCalendar's rangeOfUnit:inUnit:forDate: on Leopard for the time being. Thinking about it though, the time distortions would explain a couple weekends…

Update (Feb. 1, 2008):
I strongly recommend reading the comments. Thanks to Chris Suter for explaining what Apple's logic is in doing it this way. We all thought he was nuts at first but it appears that he was just explaining Apple's craziness.

In short, things probably are working as designed by Apple. I still think the design is flawed and that it is horrendous API. It seems that, in Apple's mind, Leopard is correcting a bug in Tiger (in other words, the intuitive and useful behavior in Tiger was a bug).

The original point stands that one needs to take care with -rangeOfUnit:inUnit:forDate:. It seems to be only useful for specific combinations of units and given the change in behavior between Tiger and Leopard, it becomes even more of a headache. If Apple is going to continue with this interpretation, then they should just treat these computations as undefined since the results are misleading.

17 comments » | Cocoa, Debugging, OS X, Programming

Idle Hands

January 8th, 2008 — 3:30pm

A common user annoyance is having alerts popping up at bad times. The worst is when the user is typing and focus gets stolen only to have subsequent key presses going to the new window, possibly resulting in the user inadvertently confirming something they didn’t want to. Peter Hosey discusses this particular case and outlines an NSAlert-based solution.

I recently submitted a patch for Sparkle+ to deal with a similar situation. It can be annoying to get an alert about a new version when you are working. A new version is not a “drop everything and deal with this now” type of alert. With this patch, when a new update is found, it will check the user’s idle time and hold off showing the panel until a certain amount of time has elapsed. This minimizes the chance of the user being in the middle of something when the alert comes up. Of course, this is only suitable for when the alert itself is not terribly critical or time-sensitive. It should not be used if it is in response to direct user action or if the alert is something that needs to be dealt with immediately.

Since this type of thing may be useful in other contexts, I’ve decided to generalize it and put it out there for your consumption. It’s a little NSObject category with two new methods: performSelector:withObject:afterSystemIdleTime: and performSelector:withObject:afterSystemIdleTime:withinTimeLimit:. The first will call the given method on the receiver when the user has been idle for the given period of time. The second method does the same but allows you to set a limit after which it will call the method regardless of idle time, thus preventing the method from being delayed indefinitely.

Beyond the use described above, you can use it in several other situations, such as doing some internal maintenance that may get in the way if the user is actively using the machine. You can reclaim/free memory, clear out caches, compact file stores, optimize data structures or whatever.

For the time being, I am using this in my Sparkle update alerts and my scheduled evaluation period expiration nags. What will be interesting is if this leads to some sort of “refrigerator light syndrome” where users notice that it only happens when they are not looking and are somehow bothered by it. Most likely, though, users probably won’t notice and, with any luck, they will be more receptive to the alerts when they do pop up. If that leads to an extra spring in their step, then I consider it a job well done.

The project is linked below. Make sure you read the Read Me file. If you end up using it, let me know.

Update (Jan 10, 2008): The original project had a couple files specified as absolute paths (so XCode wouldn’t find them). A new project has been put up with this error fixed. It is marked as version 0.6 in the package name and Read Me file.

performwhenidletest-0.6.zip

Update (Feb 5, 2008): It appears that the CGEventSourceSecondsSinceLastEventType() function hangs on Tiger systems. I have updated the code to check for the OS version and only do the idle delay on Leopard and later.

performwhenidletest-0.7.zip

I am also looking into patching this in Sparkle+. If you are using Sparkle+ with this feature enabled, drop me a line or wait for the patch which will hopefully happen soon.

12 comments » | Cocoa, OS X, Programming

Weak Linking

December 5th, 2007 — 6:05pm

It’s been a while since I posted. Since then, Leopard has been released and it’s been keeping me busy. Now that I’ve finally upgraded my dev machine to Leopard, I’ve started implementing Leopard-specific functionality. I still want to maintain Tiger-compatibility but there are cases where I need to reference new symbols in Leopard. These articles 1 2 tell you what you need to know, though I thought I’d provide a boiled down guide to what you need to do.

For the purposes of this article, we’ll assume we want a binary that will run on Tiger or later but have access to Leopard-only functionality. In this case, the deployment target is Tiger and the Target SDK (which API version you are using) is Leopard.

  • In Xcode, Project->Edit Project Settings->General. For Cross-Develop Using Target SDK, select the latest OS version whose features you want to use. In this case, Mac OS X 10.5
  • targetsdk.png

  • Click on the Build tab. Make sure the Configuration pop-up is set to All Configurations. Set Mac OS X Deployment Target to the minimum OS version you want to support (Mac OS X 10.4).
  • deploymenttarget.png

In most cases, this is all you need to do. Why’s that? It’s because Apple defines Availability macros that they use to set what symbols are available for different OS releases, based on the settings you just made above. If your target SDK is a later version than your deployment target, the symbols unique to the later target are weak-linked. This means that if the symbol does not exist, it will be set to NULL. As indicated in the articles linked above, you can compare these symbols to NULL before using them. Note that this mostly pertains to things like C functions. For Objective-C, you can check for the existence of classes using NSClassFromString(). For methods, just use -respondsToSelector:. What this all allows is for your program to dynamically use Leopard-specific functionality if it’s available. You can test for specific functionality without relying on doing a broader OS version check.

Of course, there are cases where things don’t work out of the box. For instance, it seems the headers for some lower level APIs aren’t set up with the Availability macros. In such a case, you can do your own prototype, this time asserting the weak linking:

extern void somefunction() __attribute__((weak_import));

If you don’t do this, somefunction() will end up being non-null even on older OS versions which will screw up your check for the function’s existence.

And then there are issues that aren’t based on API so much as behavior. For instance, if there was a bug in Tiger that was fixed in Leopard. It’s not the type of thing your program can detect so in such cases you’ll have to resort to version checking.

Nothing particularly new here but thought it might be helpful for people doing the OS version straddle for the first time.

1 comment » | Carbon, Cocoa, OS X, Programming, Xcode

You’re Doing It Wrong

October 25th, 2007 — 11:00am

In IRC, someone noted how they just learned about gdb’s print-object (or po) command. It was surprising since this was an experienced dev. It just goes to show that no matter how long you’ve been programming, there’s always some thing you should know, but don’t. These are those commands or features you just somehow missed. You bitch about what a pain in the ass something is only to realize that there was a simple solution all along.

So, in the interest of full disclosure, here are a couple of mine.

NSStringFromRect(), NSStringFromRange(), NSStringFromPoint(), et al.

Stupidly, whenever I needed to log an NSRect, NSRange, etc., I’d list out each of the components. For NSRect, it was always the worst, as it would look like this:

NSLog(@"%f %f %f %f", NSMinX(rect), NSMinY(rect), NSWidth(rect), NSHeight(rect));

I could have put this in a #define but for some twisted reason I would just keep copying it from other places or worse, typing it out. I finally learned about the above functions and now my teeth are whiter than ever!

Dragging header files into IB

For years, when I needed to have the header file for my class read into IB, I’d click on the “Classes” tab, select my class, do “Read Files…” (because of peculiarities in my project, the “Read Something.h” item is not enabled) and select my file in the open panel. The step of clicking on the “Classes” tab seemed particularly ridiculous to me if you had an instance of the class selected in the “Instances” pane.

At the last Cocoaheads meeting, Cathy Shive was giving a presentation on doing custom controls. While demonstrating how she put together the nib, I watched in amazement as she dragged the header file into the IB window to update her outlets and actions. I suppressed a sob as I reflected on all the time I wasted.

Command-double-click and option-double-click in XCode

In XCode’s editor, command-double-clicking on a symbol will bring up its definition, opening the appropriate header file as needed. Option-double-click brings up its documentation. You know. Me no know. Me ashamed.

• • •

Maybe these are news to you as well and if so, then I feel slightly less dumb. There are probably worse cases than this but I have erased all traces of them from my memory.

What are your embarrassing discoveries?

10 comments » | Cocoa, Debugging, OS X, Programming

Talking to my self

October 9th, 2007 — 10:21pm

I have code that takes in a string and uses key-value coding to extract a value from an object. It works fine if you want to get a property of an object, but what about getting the object itself? Using nil or an empty string with -valueForKey: or -valueForKeyPath: causes it to call -valueForUndefinedKey: which, by default, throws an exception.

After some diddling around, I found that you can use the key @"self". Doing valueForKey:@"self" on an object returns that object. There’s no magic here. NSObject has a -self method which returns, crazily enough, itself. This is peculiar because on the surface, this method is useless. Anywhere where you can type [anObject self] you can type anObject instead. Where this method comes in handy is in cases where you dynamically call a method/selector, as in KVC, and you want to specify a method that is a short-circuit of sorts.

So, in short, all of the following are equivalent:

someObject
[someObject self]
[someObject valueForKey:@"self"]
[[[someObject valueForKey:@"self"] self] valueForKey:@"self"]

I’m sure someone can use the last one in some Objective-C obfuscation contest.

5 comments » | Cocoa, OS X, Programming

Back to top