Overooped

More of a programming nerd than is strictly healthy. See also {nevyn.nu, thirdcog.eu, twitter}

Awesome blog?

Projects

Wed Dec 30
2009

Filtering a UITableView, and keyboard wonkiness

Hey, another bug that took half a day to fix. That always deserves a blog entry.

So I have a UITableView, and on it I have a tableHeaderView containing a UISearchBar. The UISearchBar filters the contents of the UITableView, and thus at every key press I have to reloadData (or more specifically, I -[UITableView reloadSections:withRowAnimation:]). For some reason, this calls setUserInteractionEnabled:NO on the UITableView, which in turn makes the UISearchBar’s UIFieldEditor resignFirstResponder, which makes the keyboard collapse. After the reload, user interaction is restored, UISearchBar gets focus, and the keyboard comes up.

This is rather embarrassing: I even subclassed UITableView, disabling setUserInteractionEnabled, before I realized that right there, before my eyes, is a UISearchBar delegate method called -[UISearchBarDelegate searchBarShouldEndEditing:]. Just add a bool ivar to the view controller saying whether the search bar may be resign key, set it to false before refreshing, refresh, and set it to true, and in the above delegate, return the ivar bool. Done, no more disappearing and reappearing keyboards.

Oh, and the code:


- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
	_searchBarMayResign = NO;
	[self.tableView reloadData]; // or equivalent
	_searchBarMayResign = YES;
}
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar;
{
	return _searchBarMayResign;
}
Wed Oct 14
2009
 The write-compile-test cycle is very very tedious when doing iPhone development on the device, because the “compile” step needs to include “install on device”, which can be very, very slow. It can take up to a minute, depending on how many apps you have installed and the current cycle of the moon. Imagine, then, doing tiny interface changes and you want to see how that tiny fix changes the UI (which sometimes you really need to do on the device to get a feel for it) — ten tries changing an animation delay could mean ten minutes of just waiting for installation. If you’re jailbroken, there is an easier faster way.


In Cydia, install ldid, rsync and ssh

Follow this guide to install an ssh key pair on your iPhone, so that the script can install the app without asking for password.
Add an additional build target to your app, and call it “Upload” or something.
Make that build target depend on your real app (as in the picture above)
Add a “run shell script” build phase, and give it this script:
export DEVICE_NAME=Mishimazu.local
rsync -avz "${CONFIGURATION_BUILD_DIR}/${PROJECT}.app" root@${DEVICE_NAME}:/Applications/
ssh root@${DEVICE_NAME} ldid -s "/Applications/${PROJECT}.app/${PROJECT}"

Replace “Mishimazu” with the name of your iPhone.
Change your active target to “Upload”, and build as usual.

A few notes though.

This script does not launch the app, you’ll have to do that yourself.
You don’t get the console routed to Xcode. Open up the Console in the Organizer for a workaround (not as good though)
Xcode debugger won’t work
File locations might have changed! You no longer have your private uuid bundle with your documents, but rather need to place documents and related things in /var/mobile. It’s possible NSSearchPathForDirectoriesInDomains will figure the right paths out for you, I haven’t tested; just make sure you’re aware of this
You are no longer sandboxed. This might change assumptions you do in code


In short, only use this deployment method for simple things, and install as usual when you need to really make sure things still work as they should, before a beta or appstore deploy. Of course, if you’re targeting the Cydia store or similar, that doesn’t apply.

The write-compile-test cycle is very very tedious when doing iPhone development on the device, because the “compile” step needs to include “install on device”, which can be very, very slow. It can take up to a minute, depending on how many apps you have installed and the current cycle of the moon. Imagine, then, doing tiny interface changes and you want to see how that tiny fix changes the UI (which sometimes you really need to do on the device to get a feel for it) — ten tries changing an animation delay could mean ten minutes of just waiting for installation. If you’re jailbroken, there is an easier faster way.

  1. In Cydia, install ldid, rsync and ssh
  2. Follow this guide to install an ssh key pair on your iPhone, so that the script can install the app without asking for password.
  3. Add an additional build target to your app, and call it “Upload” or something.
  4. Make that build target depend on your real app (as in the picture above)
  5. Add a “run shell script” build phase, and give it this script:
    export DEVICE_NAME=Mishimazu.local
    rsync -avz "${CONFIGURATION_BUILD_DIR}/${PROJECT}.app" root@${DEVICE_NAME}:/Applications/
    ssh root@${DEVICE_NAME} ldid -s "/Applications/${PROJECT}.app/${PROJECT}"
  6. Replace “Mishimazu” with the name of your iPhone.
  7. Change your active target to “Upload”, and build as usual.

A few notes though.

  • This script does not launch the app, you’ll have to do that yourself.
  • You don’t get the console routed to Xcode. Open up the Console in the Organizer for a workaround (not as good though)
  • Xcode debugger won’t work
  • File locations might have changed! You no longer have your private uuid bundle with your documents, but rather need to place documents and related things in /var/mobile. It’s possible NSSearchPathForDirectoriesInDomains will figure the right paths out for you, I haven’t tested; just make sure you’re aware of this
  • You are no longer sandboxed. This might change assumptions you do in code
  • In short, only use this deployment method for simple things, and install as usual when you need to really make sure things still work as they should, before a beta or appstore deploy. Of course, if you’re targeting the Cydia store or similar, that doesn’t apply.

Sun May 24
2009

xib + subversion + automerge = pain

Don’t let Subversion automerge your xib files. This error isn’t even on the freakin’ google:

ibtool: some object IDs were duplicated

You can work around this by 1) telling subversion the file can’t be merged by setting its mime type to application/octet-stream 2) requiring the file to be svn locked before you edit it for an exclusive lock. You can tell your subversion client to automatically give xib files these properties by adding a few lines to your ~/.subversion/config file.


## Under [miscellany]
enable-auto-props = yes

## under [auto-props]
*.nib = svn:mime-type=application/octet-stream;svn:needs-lock=*
*.xib = svn:mime-type=application/octet-stream;svn:needs-lock=*

Of course, your merged xib is still beyond saving, so just fetch the previous version and redo all your changes! (also, more on subversion locking and exclusive checkouts)

Wed Jan 28
2009

NSFileHandle Considered Harmful [Updated]

Update 20090627: This bug has been fixed in 10.6. I’d still only recommend using it for very simple cases, but at least it works now!

NSFileHandle has a bug where the calling thread will lock up indefinitely if a data of size >4096 is requested. Reduced case:


#import 

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSFileHandle *fh = [[NSFileHandle alloc] initWithFileDescriptor:fileno(fopen("/dev/random", "r"))];
    
    NSLog(@"Reading 4096 bytes... This will succeed.");
    [fh readDataOfLength:4096];
    NSLog(@"Reading 4097 bytes... This will lock for infinity");
    [fh readDataOfLength:4097];
    NSLog(@"This will never be printed.");
    
    [fh closeFile];
    [fh release];

    [pool drain];
    return 0;
}

(Warning: running this program might lock up your computer; killing the rogue process is difficult as NSFileHandle will read an infinite amount of data from /dev/random, swapping out your OS.)

This is extra dangerous if your read length is dynamic, such as in a protocol where the first incoming bytes defines how long the upcoming chunk to read is.

Solution: Use the C API for working with file descriptors instead, such as read() and write(). A file descriptor can be extracted with [NSFileHandle fileDescriptor] if you receive one from another API.

(This bug has been reported.)

Thu Jan 22
2009

[Cocoa] Less code when working with delegates

The usual pattern is to do this in your class that has a delegate:

-(void)frobnicate;
{
  ...
  BOOL extraJuicy = NO;
  if(delegate && [delegate respondsToSelector:@selector(frobnicatorShouldAddJuice:)])
    extraJuicy = [delegate frobnicatorShouldAddJuice:self];
  if(extraJuicy)
    ...
  ...
}

for every delegation method, which gets old, fast.

With DelegationHelper, you can do this:


// Setup
@interface FrobnicatorDelegationHelper : DelegationHelper  {} @end

@interface Frobnicator... { FrobnicatorDelegationHelper *delegate; ... } ...@end

@implementation
-(void)setDelegate:(id)delegate_
{
  delegate = [[DelegationHelper alloc] init];
  delegate.delegate = delegate_;
  
  [delegate setDefaultDelegationObject:BOOLobj(YES) forSelector:(frobnicatorShouldAddJuice:)];
}

// And then just...
-(void)frobnicate;
{
  ...
  if([delegate frobnicateShouldAddJuice:self])
    ...
  ...
}

Okay, the setup requires three extra lines and one line per selector (for defaults), but we’ve cut down the actual delegate calls from four lines to one! So if you have more than a single delegate method for your class, the DelegationHelper will help you.

For more samples, see main.m in my repository.

Wed Jan 14
2009
My brother needed some math homework help figuring out quadratic equations. I really want to teach him programming and I’ve shown him some very basic C before, so together we threw together a quick app solve quadratic formulas with the pq-formula :) [note: he chose project name :P]

(If you are a beta tester for any of my apps, you can even download and install the app and try it yourself!)

My brother needed some math homework help figuring out quadratic equations. I really want to teach him programming and I’ve shown him some very basic C before, so together we threw together a quick app solve quadratic formulas with the pq-formula :) [note: he chose project name :P]

(If you are a beta tester for any of my apps, you can even download and install the app and try it yourself!)

Mon Oct 20
2008

Updated my Mac Frameworks page with GLEW 1.5.0.

Mon Jul 14
2008

Properly bundling .frameworks in your application package

Update: My blog post on @rpath supersedes/complements this post (it’s not finished yet, though).



I’m sure you’ve run into it. You build your app and it works fine, but when you distribute it, your users get:

Library not loaded: /Users/Richard/Library/Frameworks/libmng.framework/Versions/A/libmng
  Referenced from: /Users/nevyn/Downloads/Sphere-Mac RC3/SphereEngine.app/Contents/MacOS/SphereEngine
  Reason: image not found

One more time. This subject seems to pop up quite often, but I think I’ve finally gotten it nailed. Before I used to fetch the sources of all the libraries I was using, set up an .xcodeproj and set install_name to “@executable_path/../Frameworks/” (and that I did here). That’s not really necessary, and not possible for non-open source frameworks. So, no matter what framework or library you have, this is how you bundle it anyway, no matter what the install_name is.

  1. Add an extra Copy Files target action in your XCode project (and rename it Copy Frameworks)
  2. Get info on the new terget action, and set its destination to Frameworks.
  3. Then, drag all custom frameworks to this target action, and they will be automatically bundled with the application when you build.
  4. You will now need to gather some information. Run `otool -L on YourApp.app/Contents/MacOS/YourApp` and note its output for each of the lines corresponding to a framework you just bundled.
  5. Next, add a Shell Script target action. This target action will call the install_name_tool to rewrite the linking information in the built binary to reference the bundled frameworks instead, even if the frameworks haven’t been built with an install_name of @executable_path/../Frameworks. Copy and modify as appropriate:
function relocateLibraryInCurrentApp() {
  install_name_tool -change $1$2 @executable_path/../Frameworks/$2 $CONFIGURATION_BUILD_DIR/$EXECUTABLE_PATH
}

relocateLibraryInCurrentApp /usr/local/lib/ libfmodex.dylib #note the space
relocateLibraryInCurrentApp /Library/Frameworks/ Foobar.framework/Versions/A/Foobar #note the space
Note the two different styles for a loose dylib and for a .framework. Just add one relocateLibraryInCurrentApp for each library or framework you’re bundling. Good luck!


Addendum: I realized that making a post that just describes how to do something, not why, is kind of lame.

In Mac OS, each binary contains a list of paths to binaries which it was linked to and which need to be loaded for all code to be available. When you launch an app, the runtime will thus load the app’s code, and for each library it needs to find will try to load it from the path set in the app’s binary. (For frameworks it’ll also look in /System/Library/Frameworks and /Library/Frameworks). What install_name_tool does is simply to rewrite that path inside the binary given as the fourth argument, searching exactly for the string argument #2, and change it to argument #3.

Additional resources:

Sun May 18
2008

Weekend Hacking

I had a lot to do this weekend and decided to do none of it, and instead ported sfxr to Cocoa, with a native UI and proper save/load :) My version’s called cfxr (as in “Cocoa sfxr”) and is available at http://thirdcog.eu/apps/cfxr . I’m not saying it’s better than sfxr, only different and more native. If you’ve got a 10.5 Mac, check it out and let me know what you think :) It’s basically just an experiment to learn Core Data and Bindings (Thanks, Scott!), and a reason to make save/load work better on the Mac.

The sfxr code (more specifically, the sdl port), when I first saw it, seemed like the worst mess I’ve ever seen. Sure, it’s a quick hack, but not even keeping state in a struct? OMG. But after working with this code for a weekend, it’s surprisingly good for what it is! “Porting” it to Cocoa was as easy as finding out which globals were properties of the sound (that is, attributes), and adding “sound.” before all accesses to those, and #defining objc syntax as C syntax for the four major playback methods. I guess I could’ve #defined the sound attribute accesses as well, making an upgrade as easy as a copy-paste, but I felt I had already done enough code generation for one day :P (Check out the legacyAccessors.m in the source to see what I mean :P Not very good looking code but it got the job done.)

It’s a rough 0.1 and might need some work. I was also thinking about making the playback part an AudioUnit or VST (just for fun) to make the playback more flexible. It works pretty nice as it is though, so do check it out :)

Mon Feb 11
2008

Not sure why Core Audio isn’t an Objective C API

It’s for performance, right? It’s the only good reason I can think of. And, you know, it sounds sensible. I mean, ultra-low latency and all that, you probably don’t want that objc dispatch overhead.

I just did an experiment, however. I dislike working with C APIs, so I’m writing Cocoa wrappers for Core Audio, just exposing those pesky Component properties that take five lines to set or get, with simple methods. Suddenly I thought, “Wait, what if I try to use an objc method as a render callback? Those require very low latency and are called often. So I should be seeing some of that objc overhead.”.

Very unscientific comparison, comparing a simple sine renderer in c and objc, on an MBP 1.83x2 (source available on request):

  • CPU usage in app using C callback: 3.0%
  • CPU usage in app using ObjC callback: 3.1%

This isn’t, by far, any compelling evidence that Core Audio should be Objective C; I’m just saying it seems more feasible than I originally thought it’d be. Also, actually thinking about the problem, I realize that the callback’s only called 44100/512 ≈ 86 times a second and has about 10 ms to complete (astronomically long in computer terms).

But NeXT did it that way, didn’t they? I want to remember that NeXT had basically /everything/ in objc, including drivers and audio and such things. So, why not Mac OS X? NeXT was hardly known for being a slow OS. Tell me what I’m missing in the comments.

Fork me on GitHub