Public.ObjectiveC is a Pike module that can be used to integrate ObjectiveC objects and Pike. ObjectiveC is most commonly associated with the
Cocoa development environment used in MacOS X.
Why?
Originally, I wanted to be able to integrate with the Growl system to allow me to send messages and notifications from my Pike apps. Later, I decided that it might be cool to be able to write native Cocoa apps in Pike. This is the (in-progress) result of this.
Some of the features currently available in this module:
- creating instances of Cocoa classes from Pike
- creating instances of Pike classes from Cocoa
- sending messages (call functions) to native Cocoa objects.
- Cocoa sending messages (call functions) to your Pike objects.
- the ability to pass Pike objects to Cocoa
- setting variables in Pike objects from Cocoa
- argument conversion: strings, floats, ints and arrays (in addition to objects).
- loading new bundles (frameworks) on the fly.
- c-level mixins for providing hand-coded versions of methods.
- NSDictionary and NSArray classes have Pike style accessors to make them easier to use from within Pike.
- support for NSRunLoop in the Pike backend
Additionally, I recently started adding support to Pike 7.7 for a Pike framework. This should allow Pike to be embedded into an Objective C application. Some (minor) additional work needs to be done to generate the proper bundle, but the dynamic library itself is being properly created. I'll include an example of using that below, for completeness.
Download
You can download a copy of the bridge compiled for Tiger/Intel
here. Simply untar it into your Pike modules directory. Note that this is an old version and many new features have been added.
Example
And now, without any further ado, some examples:
The following code demonstrates the use of the Message framework to send an email:
Pike v7.7 release 30 running Hilfe v3.5 (Incremental Pike Frontend)
> import Public.ObjectiveC;
> load_bundle("Message.framework");
(1) Result: 0
> Cocoa.NSMailDelivery.deliverMessage_subject_to_("whee", "test message", "hww3@riverweb.com");
(2) Result: 1
The following example is a demonstration of the primary reason I wrote the module. This example sends notifications to the
Growl notification system. You'll need to have Growl installed in order for this to work.
import Public.ObjectiveC;
int i;
object pool;
void create()
{
Public.ObjectiveC.load_bundle("Growl.framework");
program g = get_dynamic_class("GrowlApplicationBridge");
g->setGrowlDelegate_(this);
}
int main()
{
call_out(notify, 6);
return -1;
}
object p;
mapping registrationDictionaryForGrowl(mixed ... args) {
mapping n = ([]);
n->ApplicationName = "PGrowl";
n->AllNotifications = ({"New Announcement"});
n->DefaultNotifications = ({"New Announcement"});
n->ApplicationIcon = Cocoa.NSWorkspace.sharedWorkspace()->iconForFileType_("jpg")->TIFFRepresentation();
return n;
}
void notify()
{
mapping n = ([]);
n->ApplicationName = "PGrowl";
n->NotificationName = "New Announcement";
n->NotificationPriority = Cocoa.NSNumber.numberWithInt_(2);
n->NotificationSticky = Cocoa.NSNumber.numberWithBool_(0);
n->NotificationTitle = "notification from PGrowl";
n->NotificationDescription = "whooo, it's " + Calendar.now()->format_smtp() + "!ngreetings from Public.ObjectiveC!";
n->NotificationIcon = Cocoa.NSWorkspace.sharedWorkspace()->iconForFileType_("jpg")->TIFFRepresentation();
n->NotificationAppIcon = Cocoa.NSWorkspace.sharedWorkspace()->iconForFileType_("jpg")->TIFFRepresentation();
get_dynamic_class("GrowlApplicationBridge")->notifyWithDictionary_(n);
// lather, rinse and repeat.
call_out(notify, 5);
}
Or, if you prefer, without a lot of automatic type conversions:
import Public.ObjectiveC;
int i;
void create()
{
Public.ObjectiveC.low_load_bundle("/System/Library/Frameworks/Growl.framework");
NSClass("GrowlApplicationBridge")->setGrowlDelegate_(this);
}
int main()
{
call_out(notify, 6);
return -1;
}
object p;
object registrationDictionaryForGrowl(mixed ... args) {
object n = Cocoa.NSMutableDictionary->dictionaryWithCapacity(2);
n->setObject_forKey_("PGrowl", "ApplicationName");
n->setObject_forKey_(({"New Announcement"}), "AllNotifications");
n->setObject_forKey_(({"New Announcement"}), "DefaultNotifications");
n->setObject_forKey_(NSClass("NSWorkspace")->sharedWorkspace()->iconForFileType_("jpg")->TIFFRepresentation(),
"ApplicationIcon");
return n;
}
void notify()
{
object n = Cocoa.NSMutableDictionary->dictionaryWithCapacity(6);
n->setObject_forKey_("PGrowl", "ApplicationName");
n->setObject_forKey_("New Announcement", "NotificationName");
n->setObject_forKey_(Cocoa.NSNumber->new()->initWithInt_(2), "NotificationPriority");
n->setObject_forKey_(Cocoa.NSNumber->new()->initWithBool_(0), "NotificationSticky");
n->setObject_forKey_("notification from PGrowl", "NotificationTitle");
n->setObject_forKey_("whooo, it's " + Calendar.now()->format_smtp() + "!ngreetings from Public.ObjectiveC!", "NotificationDescription");
n->setObject_forKey_(NSClass("NSWorkspace")->sharedWorkspace()->iconForFileType_("jpg")->TIFFRepresentation(), "NotificationIcon");
n->setObject_forKey_(NSClass("NSWorkspace")->sharedWorkspace()->iconForFileType_("jpg")->TIFFRepresentation(), "NotificationAppIcon");
NSClass("GrowlApplicationBridge")->notifyWithDictionary_(n);
call_out(notify, 5);
}
The following example is a simple AddressBook export application. This demonstrates iterator support in certain datatypes (like NSArrays), casting of NSStrings to Pike strings and some mixins (the AddressBook constants, for example).
import Public.ObjectiveC;
array fields = ({
Cocoa.ABAddressBook.kABLastNameProperty,
Cocoa.ABAddressBook.kABFirstNameProperty,
Cocoa.ABAddressBook.kABEmailProperty
});
int main()
{
object book = Cocoa.ABAddressBook.sharedAddressBook();
object p = book->people();
foreach(p;; object person)
{
array row = ({});
foreach(fields;; object f)
{
object fv = person->valueForKey_(f);
if(object_program(fv) == Cocoa.ABMultiValue)
row += ({(string)fv->valueAtIndex_(0) });
else row += ({(string)fv});
}
write((row*",") + "n");
}
}
The next example demonstrates the use of the Pike framework in Objective-C code. This example code will start up an embedded Pike interpreter and evaluate some simple code. Note that the The new Pike framework can be generated by a
make framework performed in the build directory of a recent Pike 7.7.
#import <PikeInterpreter/OCPikeInterpreter.h>
#import <Foundation/NSString.h>
int main()
{
id i;
struct svalue * sv;
// required for console mode objective c applications
NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
// these 3 lines set up and start the interpreter.
i = [OCPikeInterpreter sharedInterpreter];
// we can optionally specify an alternate master, or use the one bundled in the framework.
[i setMaster: @"/usr/local/pike/7.7.30/lib/master.pike"];
[i startInterpreter];
// ok, now that we have things set up, let's use it.
// first, an example of calling pike c level apis directly.
f_version(0);
printf("%sn", Pike_sp[-1].u.string->str);
pop_stack();
// next, we'll demonstrate one of the convenience functions available
sv = [i evalString: @"1+2"];
printf("type: %d, value: %dn", sv->type, sv->u.integer);
free_svalue(sv);
// finally, we clean up.
[i stopInterpreter];
[innerPool release];
return 0;
}
And last, but not least, is a sample of using Public.ObjectiveC with Interface Builder to produce a native Cocoa application. This is basically a Pike port of the Currency Converter application used as a tutorial by Apple. You can follow along at the
Apple Developer website.
import Public.ObjectiveC;
object NSApp;
class ConverterController
{
inherit Cocoa.NSObject;
object exchangeRate;
object dollarsToConvert;
object convertedAmount;
void convert_(object action)
{
float x;
x = exchangeRate->floatValue() * dollarsToConvert->floatValue();
convertedAmount->setFloatValue_(x);
}
}
int main(int argc, array argv)
{
NSApp = Cocoa.NSApplication.sharedApplication();
return AppKit()->NSApplicationMain(argc, argv);
}
This module is alpha quality code. It may crash your Pike, though it does work pretty well with threads, both Pike and NSThreads. You can also use it to build real Cocoa applications using Pike. Currently, you have to set up the application bundle yourself, but that's a relatively easy thing to do.
The TODO List
- KVO and Bindings support
- bundle support for writing PrefPanes and such (in progress)
- support for in-out parameters and variants (in-out pointer handling in progress)
- get DO working properly with proxied classes.
- support for c-structs and c-arrays as arguments and return values (in progress)
- investigate feasibility of supporting GNUStep or The Cocotron.
- variable overloading for accessing Cocoa variables from Pike using `->symbol() and friends. (in progress)
- ability to refine methods for classes in an easily extended way (complete, see mixins/ for examples.)
- Backend running while in NSApplicationMain() (complete)
- automated application bundling (in progress, using Pike.framework in 7.7)
If you're a Cocoa developer and would like to help, please get in touch!