Wed
Dec
30
2009
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;
}
Sun
May
24
2009
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
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
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.
Mon
Jul
14
2008
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.
- Add an extra Copy Files target action in your XCode project (and rename it Copy Frameworks)
- Get info on the new terget action, and set its destination to Frameworks.
- Then, drag all custom frameworks to this target action, and they will be automatically bundled with the application when you build.
- 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.
- 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
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
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.
Tue
Jan
29
2008
So I use a lot of small utility classes that just jump between projects. Not enough to warrant a library, but still code I can’t live without. For example, ary(id first, …) instead of [NSArray arrayWithObjects:…], dict(id key, id value, …) for a dictionary, sf(NSString *format, …) instead of [NSString stringWithFormat:…], a zooming and delta-scrollable CALayer, vector class with most linear algebra, line class, simple macros to turn of CoreAnimation animations… You get the point.
Anyways, from my last blog entry you might have gathered that NSOperationQueue, CoreAnimation and garbage collection don’t really work well together. It seems to be a problem with ensuring that only a single is committing changes/transactions at a time (hence a crash in CALayerEnsureTransaction). NSOperationQueue sets up a full thread with a run loop for every single operation. I have no idea why they chose this wasteful approach; personally, I’d reuse a set of threads. And since NSOperationQueue isn’t working, and my entire acoustic modeling simulation is built around NSOperations, I decided to do just that. Thus, FakeOperationQueue joins my army of utility classes. It has the exact same interface as NSOperationQueue (almost, I only covered what I use), and works on NSOperations. Note that it doesn’t consider dependencies! It’s a very simple class, only a hundred lines of code.
Rentzsch seemed somewhat interested in my fake queue. Thus, I decided I might as well put my stuff online.