2008 07 02Less bugs through compiler optimizations6
// 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. 2008 06 25CocoaNav JS, a light CocoaNav for Safari1
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.
EDIT : turns out that slowdown occurs when I set thecanvas'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. 2008 06 23NSWindow goodies : bottomCornerRounded, usesLightBottomGradient
NSWindow methods to add sharp corners to textured windows, and an iPhoto/iTunes like bottom gradient bar.
As this is undocumented, proceed with caution with respondsToSelector.
if ([window respondsToSelector:@selector(setBottomCornerRounded:)]) [window setBottomCornerRounded:NO];
Contents |
2008 06 22Inspecting NSUndoManager's undo stack2
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 :
-
registerUndoWithTargetregisters a call with one ObjC object as parameter -
prepareWithInvocationTargetregisters a call with multiple parameters, even non ObjC objects likeNSPoint
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
Contents |
2008 06 16Cocoa Regular Expressions via JavascriptCore
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 :
-
matchmatches the first result found. Returns a string or nil. Equivalent to Javascript'sstring.match(/pattern/) -
matchAllreturns all matches. Returns a string array or nil. Equivalent to Javascript'sstring.match(/pattern/g) -
replacematches the first result found. Returns a string or nil. Equivalent to Javascript'sstring.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
RegExJS.m and RegExJS.h then add JavascriptCore.framework. 2008 06 15Crossing the WebKit bridge
[someObject someMethod:@"hello" withAnotherParam:@"world"] are translated as someObject.someMethod_withAnotherParam_('hello', 'world') .
What's of note
-
NSDictionaryis useless. Returning[NSDictionary dictionaryWithObjectsAndKeys:@"someString", @"key", nil]will yield a very strange JS object. You can display it withdocument.write(hashFromObjC)and you'll get what you expect :{ key : someString }, but that hash is not usable at all ! Tryinghash['key']orhash.keygives nothing, trying to loop over it withfor (name in hash) …gives nothing either. On the other direction, passing a hash to ObjC wil yield aWebScriptObject, not aNSDictionary. 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 doobjCobjectExposedToJS.getObject().doStuff(), the retain count of the object returned bygetObjectwill 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 aWebCoreStatisticsthat's handling quite a lot of stuff, including garbage collection. Add a methodcollectto your JS-exposed object that calls[objc_getClass("WebCoreStatistics") garbageCollectJavaScriptObjects]and you'll get a manual way to start JS collection. CheckWebCoreStatistics.mmin the WebKit source for a list of its methods, including interesting stuff likejavaScriptObjectsCount. - Crossing stuff like
NSPointwill 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])
2008 06 08Double and Triple Click
clickCount message in NSEvent that will do just that.
Compared to Javascript :
| JS | ObjC | |
| mouse down | onmousedown | mouseDown |
| mouse up | onmouseup | mouseUp |
| click | onclick | mouseUp, check [event clickCount]==1 |
| double click | ondblclick | mouseUp, 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.