Main

March 17, 2013

OpenCV on iOS working again

I just updated the OpenCV on iOS Xcode project to use the git repo as a submodule for OpenCV.  I've also fixed the SURF demo.  Take a look!

(The git mirror that I had created for OpenCV's svn is now gone.)

Note: If you've cloned a previous version, use git submodule init and git submodule update after pulling the latest rev to pull the correct version of OpenCV. If you get build errors, you probably need to do this.

May 02, 2011

Simplified building of OpenCV on iOS is here

Check this out. Instead of several steps, some ugly CMake voodoo, and some work in the shell, now all you have to do is pull down a git repo to make OpenCV work on iOS. A couple of people have tried it out and found it functional. Let me know at "my first name at this domain dot calm" whether it works for you. If anyone would like to use the example project to make instructions on how to make it work in a new project or even generate some project templates, get in touch or give it a go and submit a pull request on github.

April 19, 2011

NyARToolkit Augmented Reality on iOS

In preparation for the Augmented Reality class that Jonathan Blocksom and I taught at the recent iPhone / iPad devcon, Using AVFoundation video capture, we modernized and completed the example found at morethantechnical.com using NyARToolkit (GPL) on iOS. It's a great example of the kinds of things we can do now that we have fast devices with cameras to play with.

You can view a shaky video of the example running on an iPad 2 here.
You can get the code from github here.

Be aware that this code is GPL, which (among other things) means that you have to release any changes you make to the code under GPL and that this code cannot be used in a commercial project. There is also some speculation that the terms of using the App Store preclude GPL licensed projects as well. Caveat Coder.

March 14, 2011

Slides for the upcoming iPhone iPad Devcon Talks

I'm talking at the upcoming iPhone iPad Devcon in Boston from April 4 through 6 and today is the day we all get to share our slides! I'm honored to be talking with Noah Gift and Jonathan Blocksom again. This time, Jonathan and I are doing a full day on Augmented Reality!

The latest version of the slides for the Augmented Reality Full day class is here. (Last Mod: 20110314)
There will be a whole packet of code and other goodness given out on the day of the class.

The latest version of the Open Source UI talk is here. (Last Mod: 20110314)

The latest version of the Caching and performance talk is here. (Last Mod: 20110314)

The latest version of the touchengine talk is here. (Last Mod: 20110314)

January 03, 2011

The E46 (penultimate) BMW M3 just might be the perfect car for you, too.

I have always loved driving fast cars. I drove a blue '71 Mustang Mach 1 through most of college and a pretty red '93 Ford Escort wagon got me to and from teaching gigs and concerts. I have motor oil in my blood. I needed two cars to satisfy the "carry my stuff around without breaking the bank... or the car" and the "get me someplace fast and in style" requirements.


Fast forward (ahem... cough... already?) years and I've lately had the pleasure of driving a Silver 2003 BMW M3. This is a machine that laps the Nüburgring in 8:22, gets from "naught to sixty" in 4.6 seconds. You wouldn't expect it, but the back seat is actually usable for two non-American-sized people and they both fold down to reveal almost station-wagon-like cargo space, passing through to the trunk. No kidding. I brought a 6' wire shelf home the other night. In real - world driving, it gets just barely less than 30 miles / gallon on the highway, which is waaay better than the latest M3, which, while it has 414 HP and obviously flies, gets about 20 MPG on the highway. That's worse than my E46 city numbers of 23 MPG or so, even though I drive with occasional (ahem) spirit. The engine redlines at 8000 RPM and the peak horsepower and torque both live very near that (around 7000), which means you have to grow accustomed to keeping the car above about 3000 RPM if you want any kind of torque. Oddly, that's a good thing. When you want to drive conservatively, it's easy to do so as the car has relatively little low-end power. If you want to throw it around a bit, keep the revs up. Simple. Very girlfriend friendly driving down low, grins up high.


(Over the break for pics and more)

Continue reading "The E46 (penultimate) BMW M3 just might be the perfect car for you, too." »

October 06, 2010

iPhone iPad Devcon sample code and downloadable slides

Hi everybody. I'm hearing from the conference organizers that slideshare is requiring a login to download the slides. I've put them up on my server along with sample code.

The downloadable pdf version of the Augmented Reality talk is here. Sample code is here. Videos are here. The latest version of the code can always be found on Google Code here.

The downloadable pdf version of the Open Source UI talk is here. Sample code is here. Please see the presentation for various links to the open source project it references.

The downloadable pdf version of the Caching and performance talk is here. Sample code is here.

The downloadable pdf version of the touchengine talk is here. Sample code is here. The latest version can always be found on Google Code here.

October 04, 2010

Mercurial as überVCS

I remember a Q&A session during the most recent 360|idev where several of us took to discussing our favorite flavors of version control. During that discussion I asked whether anyone knew of a sort of über version control system that would allow me to pull code from each of the three most popular version control systems (git, mercurial, subversion). Given that there is a lot of fantastic code on bitbucket (mercurial), github (git), google code (subversion / mercurial), and elsewhere, we all agreed that it would likely prove very useful to be able to use some overarching version control system to pull up-to-date code from more or less anywhere in a manner similar to the way svn-externals handles pulling subversion-based subprojects into a subversion working copy.

I have a number of projects using Subversion for version control, a few using Mercurial, and I even maintain a private branch of Three20 using git. When an external project is using the same version control system as the project itself, life is generally simple. In the gogoDocs subversion repository, for example, the svn-externals mechanism happily keeps the GData API at a specific revision for me. Almost all of my projects use code from some other project that uses a "foreign" version control system. In gogoDocs, where I keep my copy of the GData API close to the bleeding edge, the version of Three20 I'm using is woefully out of date. The reason for this is pretty simple. It's difficult to update Three20 because it requires a lot of voodoo to pull the changes from the git repository. In order to keep Three20 up to date, I would have to keep the .git directory with the Three20 code inside of my working copy, making sure to svn-ignore that directory and never check it in to subversion (I did that once accidentally...not pretty). If I were to try to update my copy of Three20 with the canonical version on github, I would also have to remember to commit those changes into my own subversion repository. This always causes some problems on whichever of my other development machines is behind the bleeding edge. In order to update to the latest Three20 on another development machine, I have to remember to git pull before I svn up or things start to get scary; this means that I have to keep the .git directory in all of my working copies up to date. What a pain. Like many programmers, I'm almost inexcusably lazy, so I don't bother. I need an überVCS.

Here's what I think such an überVCS should do:
  • It should be distributed, allowing me to branch and commit locally. I have lately taken to making atomic local commits and using local branches to experiment and I really miss that workflow when I'm working in svn.
  • It should be ubiquitous and familiar. I don't want to learn another version control system, okay? I already have three version control systems competing for the mental space that used to hold one. I really just want one version control system that deals with everything.
  • It should be able to easily pull subprojects from git, mercurial, and subversion repositories in a manner similar to subversion's svn-externals.
  • It should allow me to work within those subprojects through plugins or natively so as to avoid the use of "foreign" version control command sets. In other words, I should be able to use the überVCS's command set inside of, for example, a subversion subproject. Ideally, I could work in one version control system and have subprojects from other version control systems just work somehow.


Using a few plugins, Mercurial can act as The überVCS I've been looking for. It fulfills the first two requirements out of the box. It's distributed and familiar. As far as I'm concerned, it acts a lot like a distributed version of Subversion, which is the old(ish) school version control system I'm most familiar with, whereas git feels like it is from someplace like Mars. Your mileage may vary.

The third requirement "to easily pull subprojects from git, mercurial, and subversion repositories" can be fulfilled by installing my fork of hgexternals from bitbucket. It's just a simple merge of the fork from adri (available here) with the latest code from the original author of hgexternals available (here). Once my little fork of hgexternals is installed, you can place an .hgexternals file in the root of your main project and Mercurial will pull code from pretty much anywhere when you run the hg externals command. If you wanted to pull the gdata objective-c client (google code / Subversion), the hgsubversion project (bit bucket / Mercurial), and mogenerator (github / git) into a contrib subdirectory of your project, you would put the following three lines into your .hgexternals file.
./contrib/gdata http://gdata-objectivec-client.googlecode.com/svn/trunk/ svn
./contrib/hgsubversion https://jonmarimba@bitbucket.org/durin42/hgsubversion hg
./contrib/mogenerator http://github.com/rentzsch/mogenerator.git git
If you then run hg externals in the project. You'll see that the subversion, mercurial, and git repositories are checked-out much like subversion handles svn-externals. Note: Be aware that the hgexternals python scrip will actually run svn or git binaries in a subprocess, so svn and git will have to be installed for this to work properly. If you wanted to hold the external project to a given revision, which is usually a good idea, you just append the external version control system's usual revision flag. For example, if you wanted the Gdata Objective-C code pegged at revision 533, the first line in the .hgexternals file would look like this.
  
./contrib/gdata http://gdata-objectivec-client.googlecode.com/svn/trunk/ svn --revision 533
In order to get the hg externals command to act a little more like subversion, you'll want to add the hgexternals.extstatushook to your .hgrc to get the status of the external projects when you type hg stat. To do this, you add the below to your .hgrc as suggested in the hgexternals installation instructions.
[hooks]
post-status = python:hgexternals.extstatushook
With a couple of extra plugins, mercurial can pull from subversion and git, in addition to the usual mercurial repositories. If you prefer to let hgexternals shell out to git or svn (thus requiring you to use git and svn tools within the subprojects themselves), you can stop here. If you would like to use mercurial commands within all subprojects, regardless of whether they are Mercurial repositories, read on. Either way, we can now pull subprojects from anywhere and we can also use hg stat as we normally would in the working copy of the project and each subproject status will also be shown.

Fulfilling the last requirement is actually pretty simple. Mercurial has third-party plugins that do a fine job of allowing you to use mercurial inside of working copies pulled from git or subversion repositories. For git, hg-git from here does the trick. While you can always use git commands within the mogenerator subproject from the example above, I personally find it easier to avoid switching between different version control systems on the fly. Hg-git lets you to interact with a git repository using mercurial. In order to do that, we install hg-git and then change the third line in our .hgexternals file to the following.
./contrib/mogenerator git://github.com/rentzsch/mogenerator.git hg
What we're doing here is telling the mercurial hgexternals extension to pull from the git repository using mercurial itself. When hg-git is installed, this works with a git-based subproject just as it would with a Mercurial-based project. Make this edit to .hgexternals, remove the mogenerator directory that was checked-out with git, and rerun hg external command. Mercurial will now pull mogenerator from github and we can now work within the mogenerator subproject using regular hg commands.

A similar plugin called hgsubversion, available here, allows you to use Mercurial to pull from Subversion repositories. Just like we did with hg-git, we can install hgsubversion and then change our .hgexternals file to look like this.
./contrib/gdata svn+http://gdata-objectivec-client.googlecode.com/svn/trunk/ hg -r 533
./contrib/hgsubversion https://jonmarimba@bitbucket.org/durin42/hgsubversion hg
./contrib/mogenerator git://github.com/rentzsch/mogenerator.git hg
If we remove the gdata subdirectory tha wall pulled using Subversion from contrib and rerun hg external so that Mercurial will now pull it using its hgsubversion plugin, we now have three projects in our contrib directory that Mercurial itself pulled from git, svn, and hg repositories. With Mercurial plus the addition of a few plugins, we now have an über distributed version control system that makes it much simpler to work with and to update external code from many different sources.

September 26, 2010

My 2 cents in (hopefully) 5 minutes

saggau_speakerbadge.png

I'm performing a 5 minute "lightning" talk at the iPad/iPhone DevCon tomorrow and I'm not planning to use slides. I don't want to burn half of the 5 minutes getting keynote working, but hopefully attendees would like to have some of the links and code I'll reference. So! Here are my notes, links, and other such fodder. There is a lot more than 5 minutes worth of material here as I've been making notes about my habits for the last week or so; this is all of them. I plan to talk until I run out of air or out of time...

Tiny Little Habits for the iOS Dev to Avoid Premature Baldness

A foolish consistency is the hobgoblin of little minds, adored by little statesmen and philosophers and divines.
-- R.W. Emerson (Essays. First Series. Self-Reliance.)

Here are some consistencies that I think are not foolish. These little habits have saved me a lot of stupid, often difficult to debug, mistakes.

When you alloc and init or otherwise instantiate an object that you now own, write the balancing release or autorelease immediately; this includes any IBOutlets and properties. If it's an IBOutlet, also write the [self setSomeIBOutletProperty:nil] in -viewDidUnload when you write the [someIBOutlet release] in -dealloc.

Similarly

When something that is a delegate of something else, in the dealloc I'll tend to perform [somethingElse setDelegate:nil], whether I expect somethingElse to live for a while longer or not, then [somethingElse release]; on the next line. When you write [somethingElse setDelegate:something], write this code right away just like the alloc+init discipline above. This is one of the reasons I don't like to set delegates in xib files. It's too easy to mess this up. Also, I refactor as I work...

Speaking of delegates and protocols

When you write a delegate protocol, make absolutely every method optional so you get into the habit of testing if the delegate responds to the selector you're about to call as a matter of course. You're a lot less likely to mess up the delegate protocol that way. When you conform to a protocol, copy all (required and optional) methods into your class and comment them out. Uncomment them as you need to use them. That way, you don't have to go back and forth to the other class's header file to see if there is a delegate method for some behavior you need to know about. You have it all at hand.

Do as little setup work as possible in xibs, they work best for simply putting together UI geometry

  • Avoid xib file dependencies (See this BNR blog post) Avoid making view controllers in MainWindow.xib
    • It's easy enough to make 'em in code. It's easier to know where things come from when you make them in code.
  • Avoid setting delegates in xib files. Do it in code.
I'm not saying not to use xibs, they're useful and they often save time; I am saying to keep the interface builder voodoo to a minimum.

Lines of code are (mostly) free

Self-documenting code is better than heavily commented code and Objc is verbose by tradition; I think this is a good thing. Debugging code that isn't too nested is much easier.
[self setFloozle:[floozaWhat whatWithString:[NSString stringWithFormat:@"jeeminy %@ Christmas, %@", frickin, virginiaName]];
Where you gonna put a breakpoint in that crap?

When you think you can draw something in code instead of using a static image, try to find the time to implement drawing it in code. Also, Buy Opacity.app.
  • When apple comes out with yet another screen size, you'll be (somewhat more) ready for it.
  • When you want to change the size of something for some other reason, you don't have to generate a new image.
  • You get to figure out how on the green Earth you designer does the crazy things they do in photoshop... only in Quartz.

Speed

If the profiler doesn't say it's slow, it's not slow. If the profiler says it's slow, it's slow. Use the profiler.
Note: Shark (sniffle) is dead. Long live Shark! They killt it good in iOS 4.0 (no longer runs on the device).

Others' little code bits that are very useful:
  • Red Sweater NSData thing that prints the hex for you
  • IsEmpty from Wil Shipley with a small addition
    static inline BOOL IsEmpty(id thing) {
        return thing == nil
        || ([thing isEqual:[NSNull null]]) //My addition for things like coredata
        || ([thing respondsToSelector:@selector(length)]
            && [(NSData *)thing length] == 0)
        || ([thing respondsToSelector:@selector(count)]
            && [(NSArray *)thing count] == 0);
    }
    
  • LogMethod() from Aaron Hillegass
    //Modified from Aaron hillegass's code
    #define LogMethod() NSLog(@"-[%@ %s]", self, _cmd)
    
  • CPU and Memory tools I use to see what's really going on that I know I stole from a skojillian places, but can't remember who to thank.
  • GTMHTTPFetcher to download stuff (and Google Toolbox for Mac in general)
  • Nathan Eror's Core Animation Toys.

Subclassin' -- You stay classy, San Diego

Subclass less, compose more. In subclasses, always call super's implementation of a method, even when you just *know* it does nothing. (I'm thinking of UIViewControllers here). That way, if you ever do decide to put in an intermediary class between your class and the former superclass, you don't get stuck wondering what happened when those methods no longer get called.

Minimize direct access to the internals of your objects

Yeah, yeah, message send blah blah. It's not that slow unless you abuse it.
  • Write or synthesize accessors for ivars you want to share. Also, buy Accessorizer.app
  • Remember, you can use readonly in a property when appropriate (and it is more often appropriate than you might think).
  • Mark ivars you don't want messed with as @private. For srsly.
  • Make good use of class continuations:
    • to document the internal workings of your objects (If the header is the public documentation, the continuation is the private, implementation documentation)
    • to suppress "may not respond to" compiler warnings
    • to expand readonly properties to readwrite status internally
    • to make a protected methods header file if you want subclasses, but not other users of a class, to use certain of its internal methods.
      • Import that file in subclasses to suppress compiler warnings.
      • Makes it easy to delineate private vs. protected vs. public methods

Know what you're really doing when you use dot syntax

I have a weird dot syntax habit, ymmv. There be message sends in them thar hills.

I rarely return nil from something that is expected to return a string, dictionary, an array, or a set

Instead, I return an empty object of the expected type like so:
    return @"";
    return [NSArray array];
    return [NSDictionary dictionary];
I often use Shipley's IsEmpty() to find out if some object is empty, rather than comparing its pointer to nil. That way, nil messaging returning nil doesn't bite you in the ass when you're expecting something that is not an NSObject and you don't get that ugly (null) string in your UI. I hate that.

Make a logging method you can turn off and one you really can't; both call through to NSLog

Also, see the expansion of LogMethod from Aaron Hillegass
#define LogMethod() SBLog(@"-[%@ %s]", self, _cmd)
#define WarnMethod() SBWarn(@"-[%@ %s]", self, _cmd)
#define SBWarn NSLog

#ifdef DEBUG
#define SBLog NSLog
#else
#define SBLog    
#endif

Run the static analyzer, but not all the time

I usually run it:
  • before a commit to source control
  • after I make a new class
  • any time I am alloc'ing a lot of stuff
However, I don't find it particularly useful to set the preference that will run it for every compile; For me, the time spent on that adds up quickly. I have a lot of fairly big projects,though. YMMV.

If your code won't work unless some particular condition is satisfied, NSAssert that condition!

(Hi Joe P.) You can turn off asserts in shipping code, if you're a panzy. I leave 'em on. I would rather get a crashing bug I can reproduce than one I can't and it's probably gonna crash soon enough...

NO WARNINGS (and turn on the warning set you can find on the blog the Rentzsch linked to that one time)

On External, third party, projects

  • READ THE CODE
  • UNDERSTAND THE CODE
  • DON'T USE IT UNTIL YOU KNOW HOW IT WORKS (make a little test project to learn it and drop lots of breakpoints)
  • If it's hard to read, it's probably broken.
  • I'm on the three20 mailing list; you wouldn't believe some of the questions asked there...
  • Add external files project relative outside of your project (makes updating the external project easier)
  • Freeze external projects at a given revision number; update early on between releases:
    • in svn this is done with gtmOauth -r 19 http://gtm-oauth.googlecode.com/svn/trunk in an svn:external
  • Avoid editing external projects directly (makes it hard to merge), instead subclass or make categories to change behavior
  • When possible, avoid static libraries
    • Too many places where compiler flags are set.
    • Instead, compile in only those parts you need and their dependencies
    • I usually add just the top level source file I need and pull in dependencies as the compiler pukes, rinse and repeat until no errors or warnings.

Make tiny little test projects for new features

  • As your codebase grows, new features can present a difficult integration, so split it into two steps
    • 1. New Feature
    • 2. Integrate new feature into codebase
    • 3. goto 1

If there is any chance that you've got a delayed perform of a selector scheduled, don't forget to cancel those when appropriate

+[NSObject cancelPreviousPerformRequestsWithTarget:]
+[NSObject cancelPreviousPerformRequestsWithTarget:selector:object:]
Incolsolata, because menlo makes left square brackets look indented.

If you have to pick just one: Read Mike Ash's blog.

August 04, 2010

Another iPhone dev looks at the Android

About a year ago, I used a G1 dev phone as my primary phone for a few weeks. It was okay, had a lot of potential, but didn't do it for me like the iPhone did at the time. I remain curious how the other half lives, so I will soon receive a Nexus One from a good friend of mine via UPS today. Thanks, dude! You know who you are.

I have to mention that I'm apprehensive. Playing with my neighbor's new Sprint Evo briefly was rather disappointing; Sprint bloatware aside, from a usability and UI standpoint the version of Android on the Evo (2.1, I believe) needs serious detail work to pull even with iOS 2.0.

I find myself flummoxed as to how anyone manages to use email on the thing. One can't forward just selected parts of an email (no kidding, all or nothing) and responding to an email inline is not supported. In both of the two (!) email apps that come on the phone (one for Gmail and one for, um, other email), once received, email text is not malleable. I love to read the comparisons of Android and iPhone, am glad that Apple has a solid competitor, and am excited to use Android again (and possibly develop software for it) on fast hardware with their latest OS, but right now I'm thinking "make email work, then we'll talk" or, as my neighbor puts it, "This is 2010, right? Doesn't Google know how to do email?"

Stay tuned for more Android / iOS comparisons in the coming weeks. I'm excited to dig in.

September 30, 2009

My Deck for 360idev

Thanks to all who attended my talk at 360idev. I think it went rather well and I am grateful to all who attended; the audience makes the talk. Here are the slides and the sample code as promised. Please read the readme file.

I'll also take some time on the plane home tomorrow night to make versions of this sample code without blocks for those who like to kick it old school. I'll post those here as soon as I can.

UPDATE: I've changed the last example to remove the blocks requirement for those who wish to avoid them. Same download location here as before.

Note: Be aware that the slides are on a pretty slow internet connection.

February 24, 2009

Useful bash foo with subversion

I've been a command-line user of subversion (svn) for some time and have long enjoyed these little bits of bash foo.

Stripping .svn directories

I often start one-off little test projects in my private slush svn repository and then move them into their own repository for further development as (or if) they grow up. Sometimes you'll want to get rid of svn's footprints in a working directory.
rm -rfv `find . -name *\.svn`
The find . -name *\.svn finds all of the .svn folders that svn uses to track the repository and with the help of those handy back-tics, rm -rv does the recursive removal of svn tracking directories.

Adding new files

During that early "making lots of new stuff" phase, I often generate quite a number of files that need to be added to svn at once. This is one of those times where having (say) class files in their own subdirectory is nice. Running this command will svn add every file that isn't currently being tracked by svn in the current directory.
svn add `svn stat |grep \? |awk '{print $2}'`
Grep finds every svn stat output line that includes a question mark (meaning the item is not currently under version control) and pipes it through awk '{print $2}', which shows only the text (the filename) from the second column of the svn stat output. The back-tics and svn add finish the magic.

Using a Mulligan

I'll use something similar to the above along with svn revert to rollback a working copy "all the way." Say you're experimenting in your working directory; you've added a few files, hit a dead end, and want to revert the whole tree as well as delete anything you've added. In other words, you're looking for a full-on "do over." Using svn revert --recursive will revert any files that svn is tracking, but will leave anything svn is not tracking alone, so we have to also remove those files.
svn revert --recursive && rm -rf `svn stat |grep \? |awk '{print $2}'`

As with any other nerd foo, make sure that you really want to do what you're asking for when you use these commands. These make my life with svn just a little easier.

January 03, 2009

iPhone responsiveness and memory usage

I recently answered a question on a private mailing list about how to make a network - based (XML parsing and such) iPhone application more responsive. I've been encouraged to post it here by a few folks (Thanks, guys! You know who you are.). So, I figure "why not?" Here you go. (Slightly modified)

The original question (paraphrased): I have an iPhone application that downloads XML data from the web, parses it, and loads it all into memory. I notice that my app is a little sluggish and that it crashes as odd times, probably due to having too much information in memory. There are other apps that do this kind of thing on the app store and I notice that many of them are more responsive than mine. How does one make one's "network cloud-based" app more responsive?

My Answer

You're running into a few of the more common problems with embedded programming. Here are a few little tricks that have helped me in general (done a little embedded Linux programming, too) and on the iPhone.

Regarding bandwith / data size / responsiveness as a result of downloading data

You'll be surprised that the actual downloading of anything but pretty large amounts of data is pretty fast by itself. The part that is slow is the stuff that happens before the download even starts. Edge / 3G latency sucks and even DNS lookup is really really really (really?) slow on the iPhone in almost all circumstances AFAICT. I've found that strategies that download more data at a time while discarding unneeded data that you're getting on the fly, rather than making multiple requests for specific small chunks of data has been a performance win. That's a little counter intuitive.

Regarding memory

There is a reason the NSXMLDocument doesn't exist on the iPhone. You can get away with throwing a smallish XML tree into memory and go ahead and do it if it simplifies your life for small data sets, but the combination of larger XML trees in memory and whatever else (including UI) you have cached in memory can cause you to hit the memory ceiling at weird times; that's probably what's happening when your app crashes. The app will get terminated if you don't release memory when you get these memory warnings. Take a look at apples "Books" SQLite example. They're not doing exactly what you're looking for there (no network code), but the handling of memory works as they're doing it. They load data from a SQLite database lazily, but keep that data in memory until the application closes, until the owning object is deallocated, or when memory gets scarce. When the app gets those fun memory warnings, they save any changed data and release everything that they can get back out of the database later. This is similar in theory to the lazy loading of views we've all grown accustomed to (and that is partly handled for you). When a view controller gets a memory warning, its views are released by default if they don't have a superview, which is why you have to figure out if the view is still there every time you (re)load it. Doing something similar with your XML data by saving it somewhere when you get a memory warning and releasing the associated data structure, reloading it piecemeal from disk as needed (be it an SQLite database or what-have-you) works pretty well and appears to be the recommended approach. Ultimately, figuring out how much and which data to put on "disk" and/or in memory (and when) is the biggest PITA in embedded programming, but it's also the place where you're going to get the responsiveness you're looking for. http://furbo.org/2008/08/27/dealing-with-memory-loss-the-cleanup/

Regarding UI responsiveness

Don't use a lot of subviews and avoid opacity. Instead draw the views yourself... Draw once, if possible. ( http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-uitableview/ )

Other tricks

Use Instruments and Shark with the code running ON THE DEVICE to see how much memory you're using, how much disk access you're doing, and what is actually going on timing-wise. Optimize those specific areas profiling the simulator (faster edit, compile, test cycle). Rinse (on the device) and repeat.

Put breakpoints in memory warning methods and figure out when (and why) it's happening (again, on the device). Is it when you download data, load view A, then view B? Can you release view A's data when it goes off the screen to keep the memory footprint from growing? Do you need the whole tree for view B? Can you download it all, dump to disk, and load lazily? Blah blah blah. This stuff can get tricky, but try the obvious things first. Find out what is causing the memory ceiling problem and you're well on your way. :)

Using the LLVM/Clang static analyzer has saved me a lot of headaches as well. It's easy to forget that you have a web view somewhere with a bunch of cached data floating off screen causing the device to go bork bork...

Cache yesterday's data, load, and display it before going out to the network for today's data. Use a progress indicator and some other UI indicator to let the user know that you're updating from the network and that they're looking at what may be old data. Do this asynchronously. When it comes to displaying data from the network, sometimes you have to fake it 'til you make it. You don't have to rewrite Google gears or anything, just throw everything you need from the server onto disk and reload it when the app launches.

Gus Mueller's FMDB rocks for SQLite work. I have a version with embedded Dtrace probes if anybody wants it. (This will have to be the subject of a later article after I find some time to update my version to Gus' latest version and to send it to him to look at / integrate if he wishes first...)

November 15, 2008

touchengine -- iPhone Google App Engine communication

My friend Noah Gift, an awesome python programmer who wrote the very popular book Python for Unix and Linux System Administration and I are working on a new open source framework that aims to facilitate communication between the iPhone SDK and Google App engine. In the spirit of the open source mantra, "release early, release often," we've made it available here on Google Code and is MIT licensed. We're also working on a couple of apps that use the framework; we plan to eat our own dogfood so-to-speak.

Current features

  • Includes a slightly modified version of the python plist library to allow syndication of data from Google App Engine to the iPhone via xml plists.
  • Includes a generically useful caching plist downloader library for the iPhone SDK that keeps the user in sync with Google App Engine data and allows offline access to that data.

Example Code

  • isonnet, a Google App Engine application that syndicates Shakespeare's Sonnets in plist form for consumption by the iPhone app.
  • Sonnet, a viewer application for the iPhone, which we're going to release soon for free on the app store, that connects to our Google App Engine site to download, cache, and display Shakespeare's Sonnets.

Future Features / Informal Roadmap

  • Authentication with Google App Engine with the user's Google ID
  • Two-way communication and data sync between app engine and the iPhone SDK
  • Integration and automatic plist syndication of underlying Google App Engine Data Storage and objects
  • Support for Application skinning through plist syndication
  • Support for storage of the iPhone user's application preferences on Google App Engine.
Look for our article on IBM DeveloperWorks coming soon.

If you have any questions or comments for us, please feel free to contact me at jonathan ((at)) thisdomainyou'reonrightnow ((dot com)).

November 14, 2008

Reverse DNS on the iPhone

Reverse DNS via the usual OS X means doesn't seem to work on the iPhone. It looks like this is a known bug/limitation. The new apple developer forums (login required) have a thread that's dedicated to the problem. (rdar://problem/5929766 is mentioned there). Apple seems to have used the eraser on some other of their DNS code on the iPhone. Saurik has an interesting hack to work - around a perhaps related change in DNS behavior.

You can actually get reverse DNS lookup on the iPhone to work using res_query (which is in libresolv, so make sure you link against libresolv.dylib) to query DNS and dns_parse_packet, which you'll find in dns_utils.h, to do the work of parsing out the DNS server reply works rather well. I could not get the recommended tools in dns.h to work, but I did discover that res_query returns the same raw reply from the DNS Server that dns_parse_packet expects from the utilities in dns.h. The code:

Continue reading "Reverse DNS on the iPhone" »

October 01, 2008

iPhone NDA -- some thoughts

When the news came out today that Apple has chosen to lift the iPhone NDA, I (like many developers) breathed a sigh of relief. I would like to take a moment to thank my peers for being so outspoken and to thank Apple for finally deciding to make my life as an iPhone developer excedingly easier by allowing the members of the development community to do as they so often and so unselfishly do to help one another with various recurring difficulties that crop up in our craft. The Mac OS X software community that I've had the pleasure of getting to know over the last couple of years continues to astound me. I'm proud to count myself among them. They are, to a person, a wonderful, well-meaning, honest, and interesting group of people who, as evidenced by the recent brouhaha over the aforementioned NDA, are very vocal about their beliefs and try to treat others as they expect to be treated. I've decided to take a page from that book and voice a belief of mine: A contract is a contract.

Having had the pleasure of arguing over contracts (including several of my own), intellectual property, personal liberty, and many similar topics with more than a few software developers, attorneys, students, musicians, my astonishingly intelligent parents, and various other people I have admired over the years, I've come to a few conclusions. One of the fundamental principals I try to follow in my own dealings with others is, I'm told, a basic brocard of civil law -- Pacta sunt servanda ("agreements must be kept").

Continue reading "iPhone NDA -- some thoughts" »

September 14, 2008

Using Shark and custom DTrace probes to debug Nagios on Mac OS X

Nagios has for several years represented a favorite wrench in my networking toolbox. It does a fantastic job of monitoring various hosts and services, warning the sysadmin if things start to get wonky, usually well before any user notices a change in service. It compiles and runs cleanly and has always performed like a champ for me. I wouldn't want to run a server without it.

I recently purchased a Mac Mini to use as a small portable network monitor for those occasions when I require some short-term network monitoring. While I strongly considered installing Linux on it, as that's what I usually use for this sort of thing, I decided to build the tools I needed in OS X Leopard. I use Leopard Server at work (I love the newiCal server), and since Leopard is officially Unix compliant, I didn't expect too much trouble, especially with great sources of FOSS for the Mac like MacPorts.

I installed Nagios through Macports (sudo port install Nagios), configured a few hosts to check, and tested the warning system. Everything was happily up and running and I had moved on to downloading and installing ntop and a few other tools when I noticed the CPU fan spooling up. Running top indicated that nagios was spinning a whole processor core. Hmm.

Google tells that others have seen this problem, but I could find no solution online. Let's find out why this is happening.

Continue reading "Using Shark and custom DTrace probes to debug Nagios on Mac OS X" »

December 09, 2007

Always - on Print Preview

Some software is, in my experience, not quite WYSIWYG when it comes to the final printed output so I have gotten into the habit of viewing a PDF in preview before I actually send a document to my printer. This has saved many trees.

Apple makes it fairly easy to do this. Just command-P and click on the weird-looking PDF button to get the drop down menu to select "Open PDF In Preview."

preview.jpg

I would like to see most every print job automatically open in preview without needing to take my hands off of the keyboard. One can make this happen with a little bit of effort, some help from Folder Actions, and a little piece of free third-party software. Here's how I do it.

First, download and install Cups PDF per the instructions on its home page. This will create a printer and driver that dumps a pdf file into ~/Desktop/cups-pdf/. Set your default printer to CUPS-PDF.

Next, you will setup a folder action on the ~/Desktop/cups-pdf/ to open each file that gets dumped in there in preview. I keep my folder actions scripts in ~/Desktop/Folder\ Actions/, so I put the following applescript (derived from a similar script that ships with Leopard) there.

  
(*
add - new item alert

This Folder Action handler is triggered whenever items are added to the attached folder.
The script will display an alert containing the number of items added and offering the user
the option to reveal the added items in Finder.

Copyright © 2002–2007 Apple Inc.

You may incorporate this Apple sample code into your program(s) without
restriction. This Apple sample code has been provided "AS IS" and the
responsibility for its operation is yours. You are not permitted to
redistribute this Apple sample code as "Apple sample code" after having
made changes. If you're going to redistribute the code, we require
that you make it clear that the code was descended from Apple sample
code, but that you've made changes.
*)

property dialog_timeout : 5 -- set the amount of time before dialogs auto-answer.

on adding folder items to this_folder after receiving added_items
try
tell application "Finder"
--get the name of the folder
set the folder_name to the name of this_folder
end tell

-- find out how many new items have been placed in the folder
set the item_count to the number of items in the added_items
--create the alert string
set alert_message to ("Folder Actions Alert:" & return & return) as Unicode text
if the item_count is greater than 1 then
set alert_message to alert_message & (the item_count as text) & " new items have "
else
set alert_message to alert_message & "One new item has "
end if
set alert_message to alert_message & "been placed in folder " & «data utxt201C» & the folder_name & «data utxt201D» & "."
set the alert_message to (the alert_message & return & return & "Would you like to view the added items?")

display dialog the alert_message buttons {"Yes", "No"} default button 1 with icon 1 giving up after dialog_timeout
set the user_choice to the button returned of the result

if user_choice is "Yes" then
tell application "Preview"
--fire it up
activate
--open the items
open the added_items
end tell
end if
end try
end adding folder items to

Ctrl (or right) click the cups-pdf folder, Enable Folder Actions, then Configure Folder Actions via the contextual menues seen below.


EnableFolderActions.jpg

When you select "Configure Folder Actions" you'll set the script that runs every time a new file is added to that folder to the one above.

folderActions.jpg


Now every time you hit command - P to print in a application, you'll hit enter to print to your default CUPS-PDF printer, which will write a PDF to the cups-pdf folder on your desktop. The folder action will then pop-up this window (for five seconds).

PreviewMe.jpg


All you have to do is hit enter.

So! Every time I print I hit command - P, enter, wait a sec, enter. Instant preview.

December 03, 2007

Poking around in others' software is sometimes useful (and thanks to Zorn)

I need an NSNumberFormatter subclass for a PyObjC project I'm working on that reformats an NSNumber to hours:minutes:seconds. Thinking I've seen this before (and kind of hoping that there was some voodoo I was missing somewhere to make this simple), I decided to poke around in applications that deal with time. After a little head-scratching, I was reminded of that most useful time-tracking and invoicing application I've grown to love called Billable (Zorn!). On this screenshot (from the Clickable Bliss site) we see a field labeled "Time Spent:" with a "Start" button next to it. Thinking to myself, "I want that formatter!" I fired up F-Script Anywhere, injected it into Billable and dug down until I was the class name for the formatter. CBTimeLengthFormatter.jpg Time to pray to google. Ah! Zorn! You beautiful helpful coding-type person. You've pasted it for us. Thank you! Now all that is left is to pythonify it. (I made a few modifications to the behavior, but it's the same general idea)
#
#  CBTimeLengthFormatter.py
#  PuppyTracker
#
#  Created by Jonathan Saggau on 12/3/07.
#  Copyright (c) 2007 __MyCompanyName__. All rights reserved.


from Foundation import *
from math import floor

#modified from http://paste.lisp.org/display/21854
class CBTimeLengthFormatter(NSNumberFormatter):

    def stringForObjectValue_(self, anObject):
        if (not (anObject.isKindOfClass_(NSNumber))):
            return(None)
            
        if (anObject.intValue() <= 0):
            return("00:00:00")
        intval = int(floor(anObject))
        
        hours = intval / (60*60)
        minutes = (intval - (hours * 60 * 60)) / 60
        seconds = intval - (minutes * 60) - (hours * 60 * 60)
        string = "%02i:%02i:%02i" %((hours), (minutes), (seconds))
        return(string)
    
    def getObjectValue_forString_errorDescription_(self, objVal, inString, err):
        """Take a string like "00:00:00" and turns it into a NSNumber and returns YES
           Also able to handle 10:10 (as 10 minutes, 10 seconds) and 10 (as 10 seconds)"""
        
        string = NSString.stringWithString_(inString)
        
        #catch for nil or empty string
        if (string == None or string.isEqualToString_("")):
            return True, 0, None
        
        stringList = string.split(":")
        #make seconds first, instead of hours
        stringList.reverse()
        
        #turn each into an integer, filtering out empty strings
        try:
            stringList = [int(each) for each in stringList if each is not u'']
            
        #if we can't make any part of this into an int, bail
        except ValueError, e:
            return False, 0, None
        
        #make sure we have Seconds, Hours, Minutes by padding the list with zeros
        #in case we have (say) Seconds, Hours only
        while ( len(stringList) < 3):
            stringList.append(0)
        
                        #sec            #min               #hour
        timeInSeconds = stringList[0] + stringList[1]*60 + stringList[2]*60*60
        return True, timeInSeconds, None

November 24, 2007

Living with multiple Macs

I have three machines running os X:
  • My Macbook Pro for travelin' to them clients; it lives wherever I am.
  • My iMac for writing code on a big screen; it lives on my desk
  • My Mini for watching movies, listening to music, serving web pages, serving mail, serving my calendars; it lives in my Middle Atlantic rack (highly recommended rack manufacturer, by the way. Nice stuff.)
Synchronizing my data (especially my home directly) has recently become much more important to me. I've been spending rather more time physically at clients and can't afford the "I think I edited that on the desktop machine, which is turned off at home" problem. For code, this is handled quite nicely by subversion, but I also need a simple way to synchronize my home directories between my laptop and my desktop. (I don't so much care about the contents of my home directory in my server because I don't really produce content there). My friend Noah Gift, who really is a gift to my world would tell you to use nfs and apple's offline synchronization to do this for you. This is fancy and fine voodoo, and something I used to do way back when I used Linux primarily (gasp) but I didn't really want to serve my home directory from a central location. Version controlled home is also overkill for me and dealing with conflicting files in svn can be a bit of a pain (yes, Hal, I thought about your suggestion as well). I really want my laptop to act kind of like my iPhone. I want to come home, plug in, deal with a few conflicts if and only if I care at that point, and sync without having to also think.
What is need is a simple directory synchronizer. Ok. Shouldn't be hard to find, right? Wrong. Every (payware, even) GUI file synchronizer I've tried has ended in the spinning beachball of death. Grumble Grumble. Ok again. Time to slide off into command-line utilities. I thought maybe I would use git instead of svn, but I don't want to use git (yet). I'm resistant to use anything designed for version control for this purpose and I'm too lazy to learn yet another one, even though git is supposed to be very fast at resolving differences between trees. Maybe later. Sorry Linus.
Enter a little-known synchronizing tool called Unison. I Love this software.
From the Unison site:
  • Unison runs on both Windows and many flavors of Unix (Solaris, Linux, OS X, etc.) systems. Moreover, Unison works across platforms, allowing you to synchronize a Windows laptop with a Unix server, for example.
  • Unlike simple mirroring or backup utilities, Unison can deal with updates to both replicas of a distributed directory structure. Updates that do not conflict are propagated automatically. Conflicting updates are detected and displayed.
  • Unlike a distributed filesystem, Unison is a user-level program: there is no need to modify the kernel or to have superuser privileges on either host.
  • Unison works between any pair of machines connected to the internet, communicating over either a direct socket link or tunneling over an encrypted ssh connection. It is careful with network bandwidth, and runs well over slow links such as PPP connections. Transfers of small updates to large files are optimized using a compression protocol similar to rsync.
  • Unison is resilient to failure. It is careful to leave the replicas and its own private structures in a sensible state at all times, even in case of abnormal termination or communication failures.
  • Unison has a clear and precise specification.
  • Unison is free; full source code is available under the GNU Public License.
In other words, exactly what I need. :) Good instructions for setting it up for this purpose are available on the Linux Journal site. It's easy to keep it from synchronizing certain directories (I leave ~/Library and ~/svnCheckouts alone, for example) and it's easy to keep it from synchronizing certain file types as well (.DS_Store, .Spotlight*, .Trashes, etc.). If I reorganize my entire home folder on the train, that reorganization is mirrored on the desktop. No muss. No fuss. No "svn move."
So! When I come home, I shutdown my laptop (it's usually sleeping in my bag), restart holding the T key, which turns it into a very expensive external drive, connect a firewire 800 cable between it and my desktop and synchronize. Happy me.

No more Finder, no more Dock

I don't use the either anymore.
I find the Terminal sufficient for most large-scale file movings (especially with the help of tree). Path Finder is where I live to browse my filesystem. Launching applications (also searching the web and making coffee) belongs to Quicksilver, which was recently open-sourced!
FYI: tree compiles on OSX Leopard if you make the following very small changes to tree.c:
--- ./tree-1.5.1.1/tree.c
+++ ./tree-1.5.1.1JS/tree.c
@@ -17,7 +17,7 @@
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

-#include 
+//#include 
#include 
#include 
#include 
@@ -182,7 +182,7 @@
#ifdef CYGWIN
extern int MB_CUR_MAX;
#else
-extern size_t MB_CUR_MAX;
+extern int __mb_cur_max;
#endif

int main(int argc, char **argv)

November 11, 2007

More Leopard calDAV fun. Migration!

As you know from my last post, I have lots and lots of calendars. It used to be much much worse. Sharing calendars between my three Macs before Leopard server was a pain in the ass. Sure, you could put a calendar on a regular DAV server and subscribe to that calendar on your other machines, but you could not write to that calendar from any machine other than that which created it initially. All subscribed machines were read-only. What does this mean to me? You know about my 14 calendars, right? Triple it. That's right. I used to have 42 calendars on each of my 3 machines. 14 calendars to which I could write and 28 to which I was subscribed so I could read any appointments from my other two machines. What a mess.

Leopard calendar server has brought me back to a debatably sane 14 calendars. But how does one migrate all of those calendars spread across multiple machines onto the shiny new calDAV server? Like so:

1. Subscribe to your shiny new calDAV server (under the Accounts tab) in iCal preferences.
2. Create a new calendar on your calDAV server through iCal (File Menu -> new Calendar -> your server) for each of your 14 calendars.
3. Create a *.ics file from your old calendar by:
a. Selecting the calendars you want to export by using the export functionality in iCal (File Menu -> Export) on each of your machines or...
b. Mounting your DAV server and downloading all of 'em at once (assuming you're keeping your calendars on a DAV server now, which I was, this method is a lot easier for obvious reasons)...
4. Select the calendar from the source list (one residing on your new calDAV server) and import the *.ics files you wish to store in the new calendar (File Menu -> import).

Your appointments are now on your calDAV server and will show up on all of your subscribed machines fully editable from all.

November 06, 2007

Leopard server, calDAV, and Mozilla Sunbird

For those wishing to use Mozilla Sunbird with the calDAV server that comes with Leopard, it's not setup in Sunbird in (quite) the same way as it is in iCal.

Let's say you have the calDAV Account URL working in ical (which will subscribe you to all of your calendars) and its URL is

 https://somedomain.com:8443/principals/users/username/ 

In Sunbird, you'll have to subscribe to the your calendars individually. Every calDAV user I've setup so far in Leopard server has been given a default calendar called simply "calendar", which you can subscribed to from Sunbird at

https://somedomain.com:8443/calendars/users/username/calendar/
Easy enough. However, if you add a new calendar in iCal, it will create a calendar named with a UUID. In order to figure out what that UUID is, it's easiest to just mount the calDAV in the finder (Command - K) at
https://somedomain.com:8443/calendars/users/username/
You'll see several directories in there: calendar, dropbox, oubox, notifications, etc. The one that looks like a UUID (for example, 037D3206-9C1D-4C6C-8E54-B3E3CAF90ABF) is the directory you'll want to subscribe to in Sunbird. In the example above, you would subscribe to
https://somedomain.com:8443/calendars/users/username/037D3206-9C1D-4C6C-8E54-B3E3CAF90ABF/

October 26, 2007

Yeah, it breathes

Wow. Alex.SpeechVoice is creepy. Apple's latest "read your email to you" speech synthesizer breathes (!) before it (he?) starts speaking. It's also, by far, the largest file in my installation of Leopard.

See?

LepprSpace.jpg

Update:

The software generating the view above is called GrandPerspective and I love it. Thanks to DeRay for emailing me about this.

September 04, 2007

MarsEdit 2 is suhweet

Having been a MarsEdit user since before its Red-Sweater-ization, of course I just bought me an upgrade for that there brand spankin' new MarsEdit 2.0. It's awesome, as expected.

Daniel Rocks.

Carry on, punkass.

August 14, 2007

iPhone Native Pong Application: the real "sweet" SDK

The native iPhone hack I put together for the C4[1] conference is in the process of a "wow, I hope somebody can read this code someday" cleanup. I will release the source in the coming days. Stay tuned for a download link.

In the meantime, here are a couple of teasers.

Screenshots:

scrnsht1.jpg


scrnsht2.jpg


And here is some code for playing a sound file from your native iPhone Application's bundle (with the class-dump'd headers you'll need, too). The iPhone, as was mentioned by everyone with a native hack at C4, has some really intelligent API. Hats off, ladies and gentlemen, to the iPhone team. They must be having a grand ol' time internally playing with this thing.



//
// PongAudio.h
//
// Created by Jonathan Saggau on 2007-08-12.
// Copyright (c) Jonathan Saggau All rights reserved.
//

#import <Foundation/Foundation.h>
@class AVItem;
@class AVController;
@class AVQueue;

@interface PongAudio : NSObject
{
AVItem *bounce;
AVItem *loser;
AVQueue *q;
AVController *controller;
}

-(void)playBounce;
-(void)playLoser;

-(void)stop;
@end

//
// PongAudio.m
//
// Created by Jonathan Saggau on 2007-08-12.
// Copyright (c) 2007 __MyCompanyName__. All rights reserved.
//

#import "PongAudio.h"
#import "AVItem.h"
#import "AVController.h"
#import "AVQueue.h"

//From Aaron hillegass
#define LogMethod() NSLog(@"-[%@ %s]", self, _cmd)

@interface PongAudio (PrivateAPI)
-(void)play:(AVItem *)item;
@end

@implementation PongAudio


- (id)init
{
self = [super init];
if (nil!= self)
{
NSError *err;
NSString *path = [[NSBundle mainBundle] pathForResource:@"PongBounce" ofType:@"wav" inDirectory:@"/"];
bounce = [[AVItem alloc] initWithPath:path error:&err];
if (nil != err)
{
NSLog(@"err! = %@ \n item = [[AVItem alloc] initWithPath:path error:&err];", err);
exit(1);
}

path = [[NSBundle mainBundle] pathForResource:@"PongLose" ofType:@"wav" inDirectory:@"/"];
loser = [[AVItem alloc] initWithPath:path error:&err];
if (nil != err)
{
NSLog(@"err! = %@ \n item = [[AVItem alloc] initWithPath:path error:&err];", err);
exit(1);
}

controller = [[AVController alloc] init];
[controller setDelegate:self];
q = [[AVQueue alloc] init];
[q appendItem:bounce error:&err];
if (nil != err)
{
NSLog(@"err! = %@ \n [q appendItem:item error:&err];", err);
exit(1);
}

[q appendItem:loser error:&err];
if (nil != err)
{
NSLog(@"err! = %@ \n [q appendItem:item error:&err];", err);
exit(1);
}
}
return self;
}

- (void)dealloc
{
[bounce release]; bounce = nil;
[loser release]; loser = nil;
[q release]; q = nil;
[controller release]; controller = nil;
[super dealloc];
}

-(void)playBounce
{
[self play:bounce];
}

-(void)playLoser
{
[self play:loser];
}

-(void)play:(AVItem *)item;
{
[controller setCurrentItem:item];

//play NOW
[controller setCurrentTime:(double)0.0];
//should probably check this eventually, too.
//- (BOOL)isCurrentItemReady;
NSError *err;
[controller play:&err];
if(nil != err)
{
NSLog(@"err! = %@ [controller play:&err];", err);
exit(1);
}
}

-(void)stop;
{
[controller pause];
}

- (void)queueItemWasAdded:(id)fp8
{
LogMethod();
}
@end


//AVController.h
/*
* Generated by class-dump 3.1.1.
*
* class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2006 by Steve Nygard.
*/


#import <Foundation/Foundation.h>
#import "CDStructures.h"

@interface AVController : NSObject
{
struct AVControllerPrivate *_priv;
}

+ (void)setEnableNetworkMode:(BOOL)fp8;
+ (id)avController;
+ (id)avControllerWithQueue:(id)fp8 error:(id *)fp12;
- (id)initWithError:(id *)fp8;
- (void)setAVItemClass:(Class)fp8;
- (id)init;
- (void)dealloc;
- (struct AVControllerPrivate *)privateStorage;
- (BOOL)isNewImageAvailableForTime:(const CDAnonymousStruct1 *)fp8 willNeverBeAvailable:(char *)fp12;
- (long)copyImageForTime:(struct __CVBuffer **)fp8 time:(const CDAnonymousStruct1 *)fp12;
- (void)itemPropertyDidChangeNotification:(id)fp8;
- (void)scheduleQueueSpaceCheck;
- (void)checkQueueSpace;
- (void)queueItemWasAdded:(id)fp8;
- (void)queueItemWasAddedNotification:(id)fp8;
- (void)queueItemWillBeRemovedNotification:(id)fp8;
- (void)cancelPrepareForPlayback;
- (void)setQueue:(id)fp8;
- (id)queue;
- (void)feederRangeWasInserted:(id)fp8;
- (void)feederRangeWasRemoved:(id)fp8;
- (void)feederInvalidatedWithCurrentItemMoved:(id)fp8;
- (void)setQueueFeeder:(id)fp8 withIndex:(int)fp12;
- (void)setQueueFeeder:(id)fp8;
- (id)queueFeeder;
- (BOOL)setRepeatMode:(int)fp8;
- (int)repeatMode;
- (BOOL)havePlayedCurrentItem;
- (id)currentItem;
- (id)currentItemPriv;
- (void)setCurrentItem:(id)fp8 preservingRate:(BOOL)fp12;
- (void)setCurrentItem:(id)fp8;
- (void)makeCurrentItemReady;
- (BOOL)isCurrentItemReady;
- (void)continueAfterRepeatGap;
- (void)cancelContinueAfterRepeatGap;
- (void)doCancelContinueAfterRepeatGap;
- (void)doScheduleContinueAfterRepeatGap:(id)fp8;
- (BOOL)beginRepeatGap;
- (void)itemFailedPlaying;
- (void)itemFinishedPlaying:(id)fp8;
- (void)itemCompletedDecode;
- (BOOL)play:(id *)fp8;
- (void)pause;
- (void)dequeueFirstItem;
- (unsigned int)indexOfCurrentQueueFeederItem;
- (BOOL)setIndexOfCurrentQueueFeederItem:(unsigned int)fp8 error:(id *)fp12;
- (id)addNextFeederItemToQueue;
- (BOOL)playNextItem:(id *)fp8;
- (float)rate;
- (BOOL)shouldBeginPlayingItem:(id)fp8 error:(id *)fp12;
- (BOOL)setRate:(float)fp8 error:(id *)fp12;
- (BOOL)resumePlayback:(double)fp8 error:(id *)fp16;
- (id)errorWithDescription:(id)fp8 code:(long)fp12;
- (void)makeError:(id *)fp8 withDescription:(id)fp12 code:(long)fp16;
- (BOOL)beginInterruption:(id *)fp8;
- (BOOL)activate:(id *)fp8;
- (void)endInterruptionWithStatus:(id)fp8;
- (float)volume;
- (void)setVolume:(float)fp8;
- (double)currentTime;
- (void)setCurrentTime:(double)fp8;
- (BOOL)muted;
- (void)setMuted:(BOOL)fp8;
- (void)setEQPreset:(int)fp8;
- (int)eqPreset;
- (struct OpaqueFigVisualContext *)visualContext;
- (void)setVisualContext:(struct OpaqueFigVisualContext *)fp8;
- (id)outputQTESFilePath;
- (void)setOutputQTESFilePath:(id)fp8;
- (id)lkLayer;
- (void)setLayer:(id)fp8;
- (id)attributeForKey:(id)fp8;
- (BOOL)setAttribute:(id)fp8 forKey:(id)fp12 error:(id *)fp16;
- (struct _LKImageQueue *)lkImageQueue;
- (struct _LKImageQueue *)lkEnsureQueueForWidth:(unsigned int)fp8 Height:(unsigned int)fp12;
- (double)lkServerTime;
- (BOOL)okToNotifyFromThisThread;
- (void)fmpTimeJumped;
- (void)fmpRateDidChange;
- (void)rateDidChangeToRate:(float)fp8;
- (void)repeatModeHasChanged:(int)fp8;
- (void)currentItemWillChangeToItem:(id)fp8 oldItemCurrentTime:(double)fp12;
- (void)currentItemHasChanged:(id)fp8;
- (void)itemHasFinishedPlayingNotification:(id)fp8;
- (void)resynchronizeTiming;
- (id)delegate;
- (void)setDelegate:(id)fp8;
- (BOOL)setItemAttribute:(id)fp8 value:(id)fp12 forKey:(id)fp16 error:(id *)fp20;
- (id)itemAttribute:(id)fp8 forKey:(id)fp12;
- (id)initWithQueue:(id)fp8 error:(id *)fp12;
- (BOOL)isValid;
- (id)initWithQueue:(id)fp8 fmpType:(unsigned long)fp12 error:(id *)fp16;
- (void)applyAttributesFromItem:(id)fp8;
- (void)fmpRelease:(id)fp8;
- (void)failPlayback:(id)fp8 reason:(long)fp12 notifyClient:(unsigned char)fp16;
- (void)prepareForPlaybackReply:(long)fp8;
- (int)instantiateFMPRef:(struct opaqueFigMoviePlaybackRef **)fp8 forItem:(id)fp12;
- (void)maybeDumpPerformanceDictionary:(struct opaqueFigMoviePlaybackRef *)fp8;
- (void)removeFMPRefListeners:(struct opaqueFigMoviePlaybackRef *)fp8;
- (void)shutdownFMPRef:(struct opaqueFigMoviePlaybackRef *)fp8;
- (void)updateTimeMarkerObservations;
- (void)scheduleUpdateTimeMarkerObservations;
- (void)registerTimeMarkerObserver:(id)fp8 forItem:(id)fp12 times:(id)fp16 context:(id)fp20;
- (void)removeObserver:(id)fp8 fromTMOArray:(id)fp12;
- (void)unregisterTimeMarkerObserver:(id)fp8 forItem:(id)fp12;

@end

//AVExternalAudio.h
/*
* Generated by class-dump 3.1.1.
*
* class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2006 by Steve Nygard.
*/

#import <Foundation/Foundation.h>
#import "CDStructures.h"

@interface AVExternalAudio : NSObject
{
struct AVExternalAudioPrivate *_priv;
}

+ (id)avExternalAudio:(id)fp8;
- (id)initWithDelegate:(id)fp8;
- (void)dealloc;
- (id)attributeForKey:(id)fp8;
- (BOOL)setAttribute:(id)fp8 forKey:(id)fp12 error:(id *)fp16;
- (void)makeError:(id *)fp8 withDescription:(id)fp12 code:(long)fp16;
- (void)postServerConnectionDiedNotification:(id)fp8;
- (void)fmpServerConnectionDied;
- (BOOL)okToNotifyFromThisThread;
- (BOOL)activate:(id *)fp8;
- (float)volume;
- (BOOL)isActive;
- (void)postUserVolumeChangedNotification:(id)fp8;
- (void)fmpUserVolumeDidChange;
- (void)fmpChangeConnectionActive:(BOOL)fp8;

@end

//AVItem.h
/*
* Generated by class-dump 3.1.1.
*
* class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2006 by Steve Nygard.
*/

#import <Foundation/Foundation.h>
#import "CDStructures.h"

@interface AVItem : NSObject
{
struct AVItemPrivate *_priv;
}

+ (id)avItem;
+ (id)avItemWithPath:(id)fp8 error:(id *)fp12;
- (id)initWithError:(id *)fp8;
- (id)init;
- (id)initWithPath:(id)fp8 error:(id *)fp12;
- (void)dealloc;
- (BOOL)setPath:(id)fp8 error:(id *)fp12;
- (int)_instantiateItem;
- (id)path;
- (double)duration;
- (struct CGSize)naturalSize;
- (float)volume;
- (void)setVolume:(float)fp8;
- (void)setEQPreset:(int)fp8;
- (int)eqPreset;
- (id)attributeForKey:(id)fp8;
- (BOOL)setAttribute:(id)fp8 forKey:(id)fp12 error:(id *)fp16;

@end

//AVQueue.h
/*
* Generated by class-dump 3.1.1.
*
* class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2006 by Steve Nygard.
*/

#import <Foundation/Foundation.h>

@class NSMutableArray, NSRecursiveLock;

@interface AVQueue : NSObject
{
NSRecursiveLock *_mutex;
NSMutableArray *_items;
void *_reserved;
unsigned int _flags;
}

+ (id)avQueue;
+ (id)queueWithArray:(id)fp8 error:(id *)fp12;
- (id)initWithError:(id *)fp8;
- (id)init;
- (id)initWithArray:(id)fp8 error:(id *)fp12;
- (void)dealloc;
- (int)_instantiateItem;
- (unsigned int)itemCount;
- (id)itemAtIndex:(unsigned int)fp8;
- (unsigned int)indexOfItem:(id)fp8;
- (id)itemAfterItem:(id)fp8;
- (BOOL)appendItemsFromArray:(id)fp8 error:(id *)fp12;
- (BOOL)appendItem:(id)fp8 error:(id *)fp12;
- (void)itemWasAdded:(id)fp8;
- (BOOL)insertItem:(id)fp8 atIndex:(unsigned int)fp12 error:(id *)fp16;
- (BOOL)insertItem:(id)fp8 afterItem:(id)fp12 error:(id *)fp16;
- (void)itemWillBeRemoved:(id)fp8;
- (BOOL)removeItem:(id)fp8;
- (BOOL)removeItemAtIndex:(unsigned int)fp8;
- (void)removeItemsInRange:(struct _NSRange)fp8;
- (void)removeAllItems;

@end

July 01, 2007

My other iphone is a rental.

This post may be short, given that I'm learning to type with this thing. I do have to make a little confession. This is my backup iPhone. That's right. Thinking a client (who, I've learned, got his before me) would want one, and realizing I could return a sealed product, I grabbed two when I went to the Apple store. With the number transfer and activation problems, I made an impulsive decision to pay the restocking fee on this one and unboxed iPhone number 2. Now I think ATT got suspicious of me trying to activate two phones on two plans... Well, let's just say that the prepaid plans are no great deal. (yes, they accepted my credit the first time around). Oh well. My other iPhone is a rental.

March 07, 2007

Webkit and cocoa bindings.

During the C4 conference, I remember Brent Simmons (NewsGator, NetNewswire) and others talking about various interesting possible ways of using a web view in cocoa. I remember thinking about how cool it might be to build some custom UI in a web browser window using html, css, and javascript instead of using drawInRect and friends in my custom views. Wouldn't it be terribly cool if cocoa bindings would be easy to connect our cocoa data model to various bits of (say) an html form. This would allow us to sucker our web designer friends into making us a pretty skin for our app. Slick. (Not that this idea is so new... reference the iTunes store).

Jim Speth, the guy who inadvertently taught me PyObjc with his PyInjector project, has been working on a bit of code on top of WebKit that accomplishes the above by adding cocoa bindings to the DOM of an embedded web view. Originally written in PyObjC (woot!) it's a framework containing a subclass of NSTreeController and a custom WebView to allow us direct, Cocoa bindings aware, hierarchical access to the DOM. Cool stuff. Recently, he's been invited to add this functionality to WebKit itself. Good on ya, Jim! This functionality is now available in the current nightly builds of WebKit. Jim has also been kind enough to have recently posted an example app that shows us how to access and modify the width and height of images in a webview through cocoa bindings and some javascript. It requires that you download and mount the nightly dmg of WebKit to run. It's very pretty. Download it! You get to stretch Jim's face.

In my own app I would like to present the user with a fairly simple form-based web interface for entering data that I (or perhaps the power user) can then skin with custom css to allow easy export to a regular web site or blog. All of the data will live in a coredata store and I would really like the web view to seamlessly integrate with the rest of cocoa. I really don't want the app to look like a glorified web browser. With this in mind, I twisted Jim's example around to connect a slider and an NSTextField to a text input field in a WebView. It's available in my subversion repository at http://jonathansaggau.com/svn/JavaScriptBindings and is mostly Jim Speth's code.
Jim's code includes an Objective C class that fires KVC/KVO notifications in Cocoa for a corresponding javascript function. It allows us to call JSKVC.willChange(document, 'boxInfo'); from our javascript to fire the Cocoa KVC methods, which in turn allow us to use Cocoa bindings to connect everything together.

@implementation JSKVC
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
{
    if (aSelector == @selector(object:willChangeValueForKey:))
        return NO;
    if (aSelector == @selector(object:didChangeValueForKey:))
        return NO;
    return YES;
}
+ (NSString *)webScriptNameForSelector:(SEL)aSelector
{
    if (aSelector == @selector(object:willChangeValueForKey:))
        return @"willChange";
    if (aSelector == @selector(object:didChangeValueForKey:))
        return @"didChange";
    return nil;
}
- (void)object:(id)object willChangeValueForKey:(NSString *)key
{
    [object willChangeValueForKey:key];
}
- (void)object:(id)object didChangeValueForKey:(NSString *)key
{
    [object didChangeValueForKey:key];
}

The HTML is just a common text input box that we give a unique ID so that javascript can identify it.
<html>
<head>
<title>bindings example</title>
<script type="text/javascript" language="javascript" src="boxinfo.js"></script>
</head>
<body>
    <p><input type="text" id="box" value="0" maxlength="4" name="contentBox" /></p>
</body>
</html>
</pre>

We need a little javascript magic now to model our text box and connect things up to the KVC notifier class. First we build a simple prototype class to define accessors for the text box value.

// bindings.js BoxInfo.prototype = { get content() { if (!this.box) return null; return this.box.value; }, set content(w) { if (!this.box) return; this.box.value = w; }, }
Then we'll need a method that will call our cocoa KVO willChange and didChange methods. JSKVC will get injected into the javascript runtime from cocoa below.
function BoxInfo() {
    this.box = null;
    this.setBox = function(newBox) {
        // store the new box value, with KVO notifcations
        JSKVC.willChange(document, 'boxInfo');
        this.box = newBox;
        JSKVC.didChange(document, 'boxInfo');
    }
}
To finish things off in javascript, we need to tell it which box to deal with. Jim's code has a nice bit of functionality that allows the user to choose what he/she is modifying by clicking on an image. Mine is simply getting the box we're interested in through its unique ID.
function installBoxInfo() {
    // create an BoxInfo object for the (DOM) document, using KVO notifications
    JSKVC.willChange(document, 'boxInfo');
    document.boxInfo = new BoxInfo();
    JSKVC.didChange(document, 'boxInfo');
    
    var boxElement = document.getElementById( 'box' );
    document.boxInfo.setBox(boxElement);
    //boxElement.setAttribute('style', 'outline: -3px solid white;');
    boxElement.onblur = function() { 
        JSKVC.willChange(document, 'boxInfo');
        JSKVC.didChange(document, 'boxInfo');
    };
}
The boxElement.onblur event (for those of us, like me who aren't familiar with javascript) is the event that gets triggered when the user leaves the text box. Since the user is likely to have changed the contents of the text box, we call willChange and didChange to let cocoa know it needs to update the other widgets and doodads. More on this in a bit.

Now we need to get all of this into the cocoa runtime. In the WebFrameLoadDelegate of the WebView, we need to inject some javascript code. This is where things get really interesting. We're evaluating our boxinfo.js file and injecting the JSKVC class into javascript so that we can fire KVC notifications in Cocoa.
- (void)webView:(WebView *)sender windowScriptObjectAvailable:(WebScriptObject *)windowScriptObject
{
    // as soon as the window object is available, inject the code for the boxInfo object
    NSString *stringForBoxInfo = [self stringForResource:@"boxinfo" ofType:@"js"];
    [windowScriptObject evaluateWebScript:stringForBoxInfo];
    // add the key-value notification object to the script environment
    [windowScriptObject setValue:[[JSKVC alloc] init] forKey:@"JSKVC"];
}
Then, once the web frame is loaded, we need to setup our BoxInfo object by calling the installBoxInfo(); function
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
    // modify the DOM document each time a new page loads
    [[webView windowScriptObject] evaluateWebScript:@"installBoxInfo();"];
}
And, should we ever want to (say) call a method in javascript from cocoa, we set up a WebScriptObject by evaluating a small bit of javascript code corresponding to the object we want to message (document.boxInfo below) and then call- (id)callWebScriptMethod:(NSString *)name withArguments:(NSArray *)args;
WebScriptObject *boxInfo = [[webView windowScriptObject] evaluateWebScript:@"document.boxInfo"];
[boxInfo callWebScriptMethod:@"setBox" withArguments:[NSArray arrayWithObject:obj]];
That's about all of the code. Now let's take a look at the NIB file.

The interesting bits in the window below will be the webview, the NSTextField, and the slider. The other views are bound to various parts of the DOM.

webWindow.jpg

There is a DomTreeController from Jim and the WebKit folks and a simple NSObjectController to hold the boxInfo class (from javascript)

WebJavaNibClasses.jpg

The DomTreeController is bound to a new attribute of the WebView called mainFrameDocument. This gives us access to the whole of the web page DOM through cocoa bindings.

bindingJavsc1.jpg

The NSObjectController is bound to the mainFrameDocument.boxInfo object that we defined in javascript. (Which would be document.boxInfo in JS)

bindingJavsc2.jpg

All the is left is to bind both of the cocoa controls (NSTextView and the NSSlider) to the boxInfo NSObjectController (boxInfo.selection.content) and, well, Bob's your uncle (hi Jo).

bindingJavsc3.jpg

Yo run the code we need to have the nightly build of WebKit mounted on our machine. We can set the following environment variables on our executable in xcode and run from there or we can just dump them in a script and allow that script to run the project. We're telling dyld to look for frameworks in the WebKit.app (more on this in a later post) and to unset the system WebKit framework path (causing dlyd to ignore the older version that's installed on our machine).
#!/bin/tcsh
set PATH=`dirname $0`
setenv DYLD_FRAMEWORK_PATH /Volumes/WebKit/WebKit.app/Contents/Resources/
setenv WebKit_UNSET_DYLD_FRAMEWORK_PATH YES
$PATH/build/debug/JavaScriptBindings.app/Contents/MacOS/JavaScriptBindings

And now for my favorite screenshot.

JavaScriptBoundAndGagged.jpg

Thanks to Jim's magic WebKit additions and cocoa bindings. The value in the NSTextField, the NSSlider and in the text input field of the WebView are all synchronized. We can even use the web page to input data.

In a couple of days, I'll revisit this topic for a short post on how to build WebKit as an embeddable framework so you can use this now in a shipping application (at 24MB, it's pretty big) and how the nightly builds of WebKit work. Stay tuned.

February 27, 2007

Core Data Double Linked List How To

I have been working on a core data app lately and stumbled upon some data that should maintain relative order. Since core data is a collection of objects stored in a database of sorts, this is not supported out of the box. There are a couple of ways to accomplish this.

Most of us who find ourselves in this situation will keep an incremental index as an instance variable in each member of our collection and sort based on that index number. Under certain circumstances, this is the best way to do it. However, A kind of nasty problem arises when we want to reorder the collection of objects. Either we're forced to auto-increment the indices by something other than 1 to allow us a little wiggle room for local insertion or reordering of objects or we're going to have to re-index most (or all) of the objects in our collection any time the order changes or an object is inserted. For instance, if I were to insert an object at the front of an indexed list, I would probably have to iterate through the entire collection to increment the index number. No fun.

While reading the long back and forth on the cocoadev list from June of 2005, I stumbled upon a post by Bill Bumgarner that I found quite interesting.

Actually, depending on context of implementation, using a linked list 
is both quite easy and very efficient.  Obviously, inserting/deleting 
objects into a linked list is trivial and fast if you have both a 
next and a previous pointer.  Given that Core Data will maintain the 
inverse relationship automatically, it will also maintain the linked 
list pointers automatically.


Given that many UI type usage contexts will require all of the objects to be faulted into memory anyway, walking the "next" relations and filling an NSMutableArray for display purposes is about three lines of code.

... In this particular example, the amount of overhead in the form of additional lines of code incurred by the developer is pretty trivial. ... b.bum
He's writing about a common data structure called a double (or doubly) linked list wherein each object in the list holds a pointer to the next and previous objects that can, in certain situations, be more useful for ordering data than an indexed set. We can enumerate in either direction very easily, asking each object in the chain for next or previous until we get nil and we can insert, remove and reorder objects with little hassle. All we have to do is make sure that next and previous values of adjacent objects are set appropriately (more on this in a second). Unfortunately, We can't (efficiently) get an object at an index like we can with an indexed list because we're purposely not indexing the collection. Also, remember that much of cocoa will want a data structure that responds to objectAtIndex which, as Bill mentiones above, will require us to iterate the chain and put it into an array. We also can't place an object in the chain at two different positions (but two objects with identical attributes can coexist in the chain).

To use core data for ordered collections we have a decision to make. Either we can make insertion and reordering fast or we can make getting an object at an index fast. If you like the indexed set idea you can go here:

Jesse Grosjean's implementation and Uli Kusterer's as well

If you like the double linked list idea, stick around. You can download the whole package from my svn repository by getting subversion and typing the following into the terminal:
svn co https://jonathansaggau.com/svn/MOLinkedList
Data Model

Model.jpg There are four entities here:
JSMOLLContainer (abstract entity):
  Contains most of the logic of inserting, deleting, reordering, etc.
  Relationship:
     Nodes: To-Many relationship
            Delete Rule: cascade (removing the container removes the nodes)
ThingContainer (subentity of JSMOLLContainer):
  You can replace this with your subentity, including any attributes you might want to add.
JSMONode(abstract entity)::
  Contains only getters and setters for next and previous.
  Relationships:
     Container (JSMOLLContainer):
            Delete Rule: Nullify
     Next (JSMONode):
            Delete Rule: Nullify
     Prev (JSMONode):
            Delete Rule: Nullify
Thing (subentity of JSMONode):
  You can replace this with your subentity, including any attributes you might want to add.  I've chosen to add a "name" attribute (a string) as a demonstration.
Implementation

I chose to use Jonathan "Wolf" Rentzsch's mogenerator to create default implementation files. It's awesome. Default accessors are built for you and placed in a machine-generated superclass for each of your objects. Mogenerator's code keeps the managed object model consistent for us and allows us to implement specific functionality in our class files. This is magic.

First tries are always throw away code, right? When I tried to implement a double linked list through messaging the node objects (say calling setNext directly on a node object to insert a new node), things became difficult in a hurry. Basic functionality was simple; the problems started creeping in when I realized that I had to guard against a lot of strange edge cases when inserting and removing objects. Coredata objects like to stick around for undo / redo purposes, so I was constantly making sure I didn't call any objects that responded to isDeleted with YES. Needless to say, I couldn't get the unit tests to work reliably talking just to the nodes.

If one tries to implement a double linked list by forcing the user to message a managing container (something we're used to doing with arrays anyway), life gets much simpler. Here we quickly rub up against a very nice feature of coredata (and also the most frustrating thing with regard to debugging). It will happily fire inverse relationships for us to keep the object graph consistent. When we call setNext on our JSMONode subclass, coredata calls setPrevious on the inverse entity. If we forget that core data is doing that for us, we get into infinite loops.

Here's the interface file for the custom parts of the Container

 
#import "_JSMOLLContainer.h"
//_JSMOLLContainer is the machine generated code from mogenerator.  It has only
//the accessors and the KVO notifications within.

@interface JSMOLLContainer : _JSMOLLContainer 
{
    //we cache first and last values because the fetch takes a while
    @private
    JSMONode *_cachedFirst;
    JSMONode *_cachedLast;
}

- (void)addNodesObjectAtFront:(JSMONode*)value_;
- (void)addNodesObjectAtEnd:(JSMONode*)value_;
- (void) moveNode: (JSMONode *)moveMe
           before: (JSMONode *)beforeMe;
- (void) moveNode: (JSMONode *)moveMe
            after: (JSMONode *)afterMe;
- (JSMONode *)first;
- (JSMONode *)last;
- (NSArray *)nodesArray;
- (NSEnumerator *)nodesEnumerator;
- (NSEnumerator *)reverseNodesEnumerator;
@end
Adding a node


To add a node object we first add it to our collection of nodes by calling the mogenerater generated superclass method. This fires all of the appropriate KVO/KVC notifications. You'll no doubt notice that there are ivars (_cachedFirst and _cachedLast) for caching the first and last nodes in the chain. It turns out that we need to know first and last pretty often. Fetching this from core data can be painfully slow, so we cache this information when we can.
- (void)addNodesObject: (JSMONode*)value_
{
    //add node object at the END of the chain.
    [super addNodesObject: value_];
    JSMONode *last = [self last];
    JSMONode *newNext = [last next];  // should be null
    [value_ setPrev: last];
    [value_ setNext: newNext];
    [self set_cachedLast: value_];
    if (IsEmpty(last))
        [self set_cachedFirst:value_];
}
There is a similar method for putting a node object at the head of the chain.

Removing a node


To remove a node object we must first remove it from the superclass (this removes it from our collection) and set the bordering objects' next and previous values appropriately to keep the chain consistent. The appropriate inverse relationship methods are fired for us. (Remember those infinite loops I was talking about?).
- (void)removeNodesObject: (JSMONode*)value_ 
{
    [super removeNodesObject: value_];
    //set next and prev as needed on bordering objs
    if (!IsEmpty([value_ prev]))
        [[value_ prev] setNext: [value_ next]];
    else
        [[value_ next] setPrev: [value_ prev]];
}
Reordering
The easiest way to reorder a node is first to remove it (thus keeping the chain consistent with regard to adjacent objects), then tell the new previous node to set its next object to the node being moved and tell the node being moved to set its next object accordingly. (At 3:00 AM, this gets hard on the mind).
- (void) moveNode: (JSMONode *)moveMe
           before: (JSMONode *)beforeMe
{
    [self removeNodesObject: moveMe];
    
    JSMONode *myNewPrev = [beforeMe prev];
    if(!IsEmpty(myNewPrev))
    {
        //if we are not moving moveMe to the front of the chain, this works
        //otherwise there IS NO myNewPrev and we have to use the method below.
        [myNewPrev setNext: moveMe];
        [moveMe setNext: beforeMe];
    }
    else
    {
        [moveMe setPrev: myNewPrev];
        [beforeMe setPrev: moveMe];
    }
}
The rest of the implementation deals with building up simple enumerators and also with fetching the first and last nodes (where prev is Null or next is Null respectively) from the Managed Object Context when it turns out we haven't cached it appropriately. Eventually, the fetch requests will go away. Right now, they'll only likely be needed when we've done some reordering as that code doesn't update the cached first and last values as it should. To make sure that we're really returning the first node, we simply check that the _cachedFirst node's prev value is Null (actually [NSNull null]); that's what IsEmpty does here.
- (NSArray *)_nodesFilteredUsingPredicate: (NSPredicate *)predicate
{
    if(0 == [[self nodesSet] count]) return [NSArray array];
    NSEntityDescription * entity = [NSEntityDescription entityForName: @"JSMONode" 
                                               inManagedObjectContext: [self managedObjectContext]];
    NSFetchRequest * fetch = [[NSFetchRequest alloc] init]; 
    [fetch setEntity: entity]; 
    [fetch setPredicate: predicate]; 
    
    NSManagedObjectContext *context = [self managedObjectContext];
    NSArray * results = [context executeFetchRequest: fetch error: nil];
    [fetch release];
    return results;
}

- (JSMONode *)first
{
    if(0 == [[self nodesSet] count]) return nil;
    //If we haven't yet cachedFirst we need to find it.
    //IF cachedFirst previous value is not null, cachedFirst is no longer actually first.
    if (!IsEmpty(_cachedFirst) && IsEmpty([_cachedFirst prev]))
        return _cachedFirst;
    
    JSMONode *first;
    NSPredicate *predicate = [NSPredicate predicateWithFormat: @"container == %@ AND prev == %@", self, [NSNull null]];
    NSArray *results = [self _nodesFilteredUsingPredicate: predicate];
    
    if (0 == [results count])
    {
        [self set_cachedFirst: nil];
        return nil;
    }
    
    NSAssert(([results count] == 1), @"There should be one or fewer firstThing(s)");
    first = [results objectAtIndex: 0];
    [self set_cachedFirst: first];
    return first;
}

- (JSMONode *)last
{
    if(0 == [[self nodesSet] count]) return nil;
    if (!IsEmpty(_cachedLast) && IsEmpty([_cachedLast next]))
        return _cachedLast;
    
    JSMONode *last;
    NSPredicate *predicate = [NSPredicate predicateWithFormat: @"container == %@ AND next == %@", self, [NSNull null]];
    NSArray *results = [self _nodesFilteredUsingPredicate: predicate];    
    if (0 == [results count])
    {
        [self set_cachedLast: nil];
        return nil;
    }
    
    NSAssert(([results count] == 1), @"There should be one or fewer lastThing(s)");
    last = [results objectAtIndex: 0];
    [self set_cachedLast: last];
    return last;
}
Meanwhile, back at the Hall of Justice: usage So how do we use it? We simply insert a JSMOLLContainer subclass (in the case of the diagram above, it's a ThingContainer) into the managed object context like so:

container = [NSEntityDescription insertNewObjectForEntityForName:@"ThingContainer" 
                                                               inManagedObjectContext:moc];


Then we start putting node objects into it like so:

thingZero = [[NSEntityDescription insertNewObjectForEntityForName:@"Thing" 
                                                inManagedObjectContext:moc]

     //perhaps we want to set an attribute
     [thingZero setName:@"Thing Zero Here"];
     [container addNodesObject:thingZero];

     thingOne = [[NSEntityDescription insertNewObjectForEntityForName:@"Thing" 
                                                inManagedObjectContext:moc]

     //perhaps we want to set an attribute
     [thingOne setName:@"Thing One Here"];
     [container addNodesObject: thingOne];
Now we've a container with two things in this order: thingZero -> thingOne.

If we wanted to iterate through a list, we have a couple of options: 1. We can ask the container for its object enumerator and do it "cocoa style"
    JSMONode *currentThing;
    NSEnumerator *nodesEnumerator = [container nodesEnumerator];
    while (currentThing = [nodesEnumerator nextObject])
    {
        //do something
    }

2. We can ask the container for the first object and keep asking for next
    JSMONode *currentThing = [container first];
    
    while (!IsEmpty(currentThing))
    {
        //do something
        currentThing = [currentThing next];
    }

At this point, I should probably show you the IsEmpty function. This is mostly stolen from Wil Shipley's blog . I just added the test for [NSNull null], which is the "empty" object for insertion into cocoa collections (like a core data graph, for instance).
//Thanks Wil
static inline BOOL IsEmpty(id thing) {
    return thing == nil
    || ([thing isEqual:[NSNull null]]) //JS addition for coredata
    || ([thing respondsToSelector:@selector(length)]
        && [(NSData *)thing length] == 0)
    || ([thing respondsToSelector:@selector(count)]
        && [(NSArray *)thing count] == 0);
}


Inserting an object before or after another object currently in the chain is (for now) a two step process. Let's say you have a node called "node3" in a linked list and you want to add a node called "node7" to the list after node 3. Let's pretend that we have nodes zero through six already in the list. (node0 -> node1 -> node2 -> ... node6) node7 (outside)

1. Put node7 in the container by simply adding it to the end
[container addNodesObject: node7]
2. Then place it after node3
[container moveNode: node7
                   after: node3;]


That's it. It's somewhat suboptimal in that the container will insert at end, then remove, then reinsert the node behind the scenes, but I haven't had to optimize this code yet in practice and it keeps the API simple.

So there you have it. Make a subentity (or several) of the JSMOLLContainer and the of JSMONode objects and enjoy. Take a look at the subversion repository. It includes test code that will give you a good idea of how everything works in practice. Oh. Do what you want with it. It's offered AS IS. No Warranty.

November 11, 2006

Graphviz rocks

I needed to come up with an innovative (pun intended, for those who get the joke) way of tracking time by task for one of my clients recently. I considered dusting off DIA or perhaps omnigraffle, but I realized that the tedium involved in laying out the hierarchical structure I wanted to see would probably kill me. So, I thought I would give Graphviz a shot.

The nice thing here is that it will lay out a hierarchy for you and flow your chart in an efficient manner. I would have gone slowly mad doing this by hand.

Click on the image below to see a larger version.


GraphvizLG.jpg

You can see the markup I gave graphviz in the extended portion of the post.

Continue reading "Graphviz rocks" »