Image:CocoaNav icon.png

PARMANOIR

Less bugs through compiler optimizations6

Stupid bug day !
// Get a symbol
void* sym = dlsym(handle, "function1");
// Cast it to correct signature
double (*fn)() = sym;
// Call it
fn();
// Get another symbol
void* sym2 = dlsym(handle, "function2");

// Cast it to correct signature
SomeStruct (*fn2)(float, float, float) = sym2;
// Call it
fn(1, 2, 3);

// Misassign new symbol to first pointer
fn = sym2;
// Call uninitialized function pointer
fn2(1, 2, 3);

And *BUG* ! Hmm … strange … let's try that in a Release build, one of my Stupid Voodoo Programming Tactiques, and … it works ! I stare at the code for a stupid amount of time, dumbfounded, wondering the obvious bug lies. Oh. There. I'm getting a new function pointer but calling the former one with the new signature … oups ! I'm getting a new function pointer but assigning it to the first one, then calling that uninitialized pointer.

How come it works in Release but not in Debug ? In Debug, GCC optimizes nothing and allocates two function pointers. In Release, GCC allocates one function pointer and uses it in both calls. Therefore calling fn(1, 2, 3) equals calling fn2(1, 2, 3); : that's Invisible Release Build Bug.

Moral of the day : always Release builds ! :)

EDIT I messed up while cleaning the code for this post. I even messed up the diagnostic, but … not the explanation ! :) To sum it up : XCode won't warn you when calling uninitialized function pointers.

CocoaNav JS, a light CocoaNav for Safari1

CocoaNav JS is a light version of CocoaNav. It's faster to use as the class list is static (dumped from CocoaNav), but also less powerful as you can't search methods or protocols, only classes. And sometimes it's beachballing and looking like it will crash Safari, but I assure you it won't ! Just let it run.

CocoaNav is Leopard only, and I see Tiger users and Windows users checking the page out :) If you're one of them, this will give you a quick way to discover the Cocoa Class Tree, each class having a link to Apple Developer.

CocoaNav JS

EDIT : turns out that slowdown occurs when I set the canvas's dimensions. I use it to draw lines between classes. On my Mac the layout produces a 1198x16000 table, the canvas is overlaid beneath and sized to cover it all. If Safari allocates one big plane, that comes up to a 73 Mb RGBA surface. Anyone got any canvas tips, or a better way ? I'm just drawing lines here.

NSWindow goodies : bottomCornerRounded, usesLightBottomGradient

Some undocumented NSWindow methods to add sharp corners to textured windows, and an iPhoto/iTunes like bottom gradient bar.

Image:BottomCornerRounded usesLightBottomGradient.png

As this is undocumented, proceed with caution with respondsToSelector.

if ([window respondsToSelector:@selector(setBottomCornerRounded:)])
	[window setBottomCornerRounded:NO];

Contents

Inspecting NSUndoManager's undo stack2

I had trouble understanding NSUndoManager and its retain policy so I wrote some code, and then I was enlightened :) There's a great article on ex-cinder.com that explains the concepts, so I won't go into detail - check the code out.

Two methods and their retain behaviours

When doing an undoable action, you register in NSUndoManager a method than undoes what you just did. It will be called when undoing. Depending on your method's arguments, call one of these :

  • registerUndoWithTarget registers a call with one ObjC object as parameter
  • prepareWithInvocationTarget registers a call with multiple parameters, even non ObjC objects like NSPoint

If you set a value from 4 to 5, register in the undomanager a call to set the value to 4, then set it to 5. If you add an object with addObject:, register a call to removeObject:. That's really all there is to it.

prepareWithInvocationTarget and registerUndoWithTarget both retain their arguments. Each undoable change ups the retain count. So when moving an object around, the retain count will go up at each position change. Don't worry if an object reaches a retain count of 30. Clearing the undo stack gets the retain count back to its initial value.

Peering through NSUndoManager

NSUndoManager does not offer any public way to view its undo and redo stacks. Thanks to F-Script, we can see that it has an undocumented _undoStack method, but nothing for the redo stack. NSUndoManager's header file shows 2 instance variables _redoStack and _undoStack. We'll fetch these with object_getInstanceVariable and call description on them, which dumps their contents.

Undoing adding and removing of objects in an NSArray

Storing objects in an array ups their retain count. Create an object (count=1), add it in an array (count=2). If you don't release the object (count=1) after adding it to the array, NSUndoManager will restore the object to its original retain count (1) when disposing of it, thus it will never be released. This is fine if you overload your objects' release method and plan on pooling them. If not, release your objects after adding them to the array or they will stick around forever.

Sample code

Image:iconZip.png Undo Inspector.zip

Contents

Cocoa Regular Expressions via JavascriptCore

Ever been stumped by the lack of regular expressions in Cocoa ? You can do quite a lot of complicated stuff in a few lines of code but comes the time to match a string to some pattern and then … where are regexes ? On your mac, they're living in PHP, Ruby, Perl, on webpages via WebKit, in some command line tools, but where in Cocoa ? Nowhere ! How about using WebKit ? Why not, but WebKit needs a NSWindow, a WebView, an html page with some code. Someone on CocoaDev suggested JavascriptCore, so we'll use just that !

Using JavascriptCore

To use JavascriptCore and get some results out of it :

  • create a context : JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
  • create a JS script string : JSStringRef scriptJS = JSStringCreateWithCFString((CFStringRef)@"var p = new RegExp(pattern, flags); string.match(p)");
  • evaluate our script and get the result : JSValueRef result = JSEvaluateScript(ctx, scriptJS, NULL, NULL, 0, NULL);
  • convert our Javascript result to a Cocoa string :
JSStringRef resultStringJS = JSValueToStringCopy(ctx, result, NULL);
CFStringRef resultString = JSStringCopyCFString(kCFAllocatorDefault, resultStringJS);
JSStringRelease(resultStringJS);
// Returns a Cocoa object by casting from a CFString
return	(id)resultString;

There's more as you have to do conversions to and from Cocoa. WebKit bridges strings, arrays, numbers but JavascriptCore does nothing, so we have to handle it ourselves.

From there we do a direct mapping of Javascript's match and replace.

Augmenting NSString

We'll define three methods :

  • match matches the first result found. Returns a string or nil. Equivalent to Javascript's string.match(/pattern/)
  • matchAll returns all matches. Returns a string array or nil. Equivalent to Javascript's string.match(/pattern/g)
  • replace matches the first result found. Returns a string or nil. Equivalent to Javascript's string.replace(/pattern/)

Given a sample string That is a sample STRING and a pattern of I[\w]+ (match capital I followed by one or more [a-zA-Z0-9]) :

// matches ING
- (id)matchWithPattern:(NSString*)pattern;
// matches ['ING']
- (id)matchAllWithPattern:(NSString*)pattern;
// returns That is a sample STR***
- (id)replaceWithString:(NSString*)replacement andPattern:(NSString*)pattern;

And case (in)sensitive versions :

// matches is
- (id)matchWithPattern:(NSString*)pattern isCaseSensitive:(BOOL)c;
// matches ['is', 'ING']
- (id)matchAllWithPattern:(NSString*)pattern isCaseSensitive:(BOOL)c;
// returns That *** a sample STR***
- (id)replaceWithString:(NSString*)replacement andPattern:(NSString*)pattern isCaseSensitive:(BOOL)c;

Sample code

Image:iconZip.png RegEx via JavascriptCore.zip NOTE this uses JavascriptCore for regular expressions, and WebView to display the results. That means there are actually two Javascript engines running :) one for matching, one for displaying. The displaying is of course completely optional, if you want to use regexes in your project copy RegExJS.m and RegExJS.h then add JavascriptCore.framework.

Crossing the WebKit bridge

Unfortunately the WebKit bridge is not as full featured as RubyCocoa's. You can call ObjC methods, use strings, numbers and arrays, and that's it. No C methods or structs, no derivating JS objects from ObjC objects. Following Using Objective-C From JavaScript describes the possibilities, the one thing of note is that calls like [someObject someMethod:@"hello" withAnotherParam:@"world"] are translated as someObject.someMethod_withAnotherParam_('hello', 'world') .

What's of note

  • NSDictionary is useless. Returning [NSDictionary dictionaryWithObjectsAndKeys:@"someString", @"key", nil] will yield a very strange JS object. You can display it with document.write(hashFromObjC) and you'll get what you expect : { key : someString }, but that hash is not usable at all ! Trying hash['key'] or hash.key gives nothing, trying to loop over it with for (name in hash) … gives nothing either. On the other direction, passing a hash to ObjC wil yield a WebScriptObject, not a NSDictionary. So, hashes are out.
  • Retain count. Exposing an object with [windowScriptObject setValue:theObject forKey:@"theObject"]; retains the object. Each ObjC method returning an object retains it. If you don't 'cache' the object you got and always do objCobjectExposedToJS.getObject().doStuff(), the retain count of the object returned by getObject will be incremented each time you call it. BUT getting an object in an array doesn't retain it.
  • No manual garbage collection start method. Internet explorer had GC.collect(), useful to check for memory leaks. In Safari, there's a WebCoreStatistics that's handling quite a lot of stuff, including garbage collection. Add a method collect to your JS-exposed object that calls [objc_getClass("WebCoreStatistics") garbageCollectJavaScriptObjects] and you'll get a manual way to start JS collection. Check WebCoreStatistics.mm in the WebKit source for a list of its methods, including interesting stuff like javaScriptObjectsCount.
  • Crossing stuff like NSPoint will have to be done with arrays.
#define NSPointToJS(p) [NSArray arrayWithObjects:[NSNumber numberWithFloat:p.x], [NSNumber numberWithFloat:p.y], nil]
#define NSPointFromJS(d) NSMakePoint([[d objectAtIndex:0] floatValue], [[d objectAtIndex:1] floatValue])

Double and Triple Click

OK, that's pretty stupid :) but I had trouble figuring out what part of Cocoa handled double clicks. Turns out there's a clickCount message in NSEvent that will do just that.

Compared to Javascript :

JSObjC
mouse downonmousedownmouseDown
mouse uponmouseupmouseUp
clickonclickmouseUp, check [event clickCount]==1
double clickondblclickmouseUp, check [event clickCount]==2
triple click
paragraph selection
-mouseUp, check [event clickCount]==3


clickCount also lives for mousedown, so you can check things like triple mouse down. No great use I suppose but it's there.
2008 07 026Less bugs through compiler optimizations
2008 06 251CocoaNav JS, a light CocoaNav for Safari
2008 06 23NSWindow goodies : bottomCornerRounded, usesLightBottomGradient
2008 06 222Inspecting NSUndoManager's undo stack
2008 06 16Cocoa Regular Expressions via JavascriptCore
2008 06 15Crossing the WebKit bridge
2008 06 08Double and Triple Click
2008 06 05Photoshop-like compositing with Core Animation
2008 06 052One way binding to NSSlider
2008 05 30Threaded Core Animation, Part Deux
2008 05 281A 2D Bevel Technique
2008 05 264Zero Opacity Trick
2008 05 244Sorry NNW readers
2008 05 23What's IB connecting to when you connect to First Responder ?
2008 05 22Lanczos Scaling seems to handle Gamma well
2008 05 21Core Animation Phantom Fade
2008 05 21CocoaNav 1.01
2008 05 202Introducing CocoaNav, a Cocoa Class Browser
2008 05 182Exposing everything to WebKit
2008 05 14Projected coordinates of a 3D CALayer

Powered by MediaWiki

Hi ! I'm learning Cocoa to (hopefully !) become an indie developer.

I've written software all my professional life, in C++, PHP, Javascript. I've designed websites and web interfaces. My last venture went into flames as clients were happy but didn't like paying very much.

I've had little luck in the B2B world, I'm hoping for a better future writing Mac applications.

Image:rss.png Feed

Planet Cocoa