Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| D/Objective-C: Syntax | |
| ===================== | |
| This document is an overview of the extensions D/Objective-C brings to the D programming language. It assumes some prior knowledge of both [D] and [Objective-C]. | |
| [D]: http://d-programming-language.org/ | |
| [Objective-C]: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html | |
| <p class="note" markdown="1"> | |
| Note: Some parts of this document describe features which are not yet implemented and are very much subject to change. Unimplemented sections of this document are marked as such. | |
| </p> | |
| * [Using an existing Objective-C class](#extern-class) | |
| * [Declaring instance methods](#instance-methods) | |
| * [Overloading](#overloading) | |
| * [Defining a subclass](#subclass) | |
| * [Constructors](#constructors) | |
| * [Properties](#properties) | |
| * [Objective-C Protocols](#protocols) | |
| * [Class Methods](#class-methods) | |
| * [Class References](#class-ref) | |
| * [Class Extensions (also known as Categories)](#class-ext) | |
| * [`NSString` Literals](#nsstring-literals) | |
| * [Selector Literals](#selector-literals) | |
| * [Protocol References](#protocol-ref) | |
| * [Interface Builder Attributes](#interface-builder) | |
| * [Special Considerations](#special-considerations) | |
| * [Casts](#casts) | |
| * [`NSObject` vs. `ObjcObject` vs. `id`](#id) | |
| * [Exceptions](#exceptions) | |
| * [Memory Management](#memory) | |
| * [Null Objects](#null) | |
| * [Applying D Attributes](#attrib) | |
| * [Design By Contract, Unit Tests](#dbc-unittest) | |
| * [Global Functions](#global-func) | |
| * [Inner Classes](#inner) | |
| * [Memory Safety](#safety) | |
| * [Generated Selectors](#generated-selectors) | |
| * [Blocks](#blocs) | |
| - - - | |
| ## Using an existing Objective-C class {#extern-class} | |
| To use an existing Objective-C class, we must first write a declaration for | |
| that class, and we must mark this class as comming from Objective-C. Here is | |
| an abbreviated declaration for class `NSComboBox`: | |
| extern (Objective-C) | |
| class NSComboBox : NSTextField | |
| { | |
| private ObjcObject _dataSource; | |
| ... | |
| } | |
| This declaration will not emit any code because it was tagged as | |
| `extern (Objective-C)`, but it will let know to the compiler that the `NSComboBox` | |
| class exists and can be used. Since `NSComboBox` derives from `NSObject`, the | |
| `NSObject` declaration must also be reacheable or we'll get an error. | |
| Declaring members variables of the class is important. Even if we don't plan | |
| on using them, they are needed to properly calculate the size of derived | |
| classes. | |
| ### Declaring Instance Methods {#instance-methods} | |
| Objective-C uses a syntax that greatly differs from D when it comes to | |
| calling member functions -- instance methods and class methods in Objective-C | |
| parlance. In Objective-C, a method is called using the following syntax: | |
| [comboBox insertItemWithObjectValue:val atIndex:idx]; | |
| This will call the method `insertItemWithObjectValue:atIndex:` on the | |
| object `comboBox` with two arguments: `val` and `idx`. | |
| To make Objective-C methods accessible to D programs, we need to map them to a | |
| D function name. This is acomplished by declaring a member function and | |
| giving it a selector: | |
| extern (Objective-C) | |
| class NSComboBox : NSTextField | |
| { | |
| private void* _dataSource; | |
| void insertItem(ObjcObject object, NSInteger value) [insertItemWithObjectValue:atIndex:]; | |
| } | |
| Now we can call the method in our D program as if it was a regular member | |
| function: | |
| comboBox.insertItem(val, idx); | |
| ### Overloading {#overloading} | |
| Objective-C does not support function overloading, which makes it impossible to | |
| have two methods with the same name. D supports overloading, and we can take | |
| advantage of that in a class declaration: | |
| extern (Objective-C) | |
| class NSComboBox : NSTextField | |
| { | |
| private void* _dataSource; | |
| void insertItem(ObjcObject object, NSInteger value) [insertItemWithObjectValue:atIndex:]; | |
| void insertItem(ObjcObject object) [insertItemWithObjectValue:]; | |
| } | |
| comboBox.insertItem(val, idx); // calls insertItemWithObjectValue:atIndex: | |
| comboBox.insertItem(val); // calls insertItemWithObjectValue: | |
| ## Defining a Subclass {#subclass} | |
| Creating a subclass from an existing Objective-C class is easy, first we must | |
| make sure the base class is declared: | |
| extern (Objective-C) | |
| class NSObject | |
| { | |
| ... | |
| } | |
| Then we write a derived class as usual: | |
| class WaterBucket : NSObject | |
| { | |
| float volume; | |
| void evaporate(float celcius) | |
| { | |
| if (celcius > 100) volume -= 0.5 * (celcius - 100); | |
| } | |
| } | |
| WaterBucket being a class derived from an Objective-C class, it automatically | |
| becomes an Objective-C class itself. We can now pass instances of WaterBucket | |
| to any function expecting an Objective-C object. | |
| Note that no Objective-C selector name was specified for the `evaporate` | |
| function above. In this case, the compiler will generate one. If we need the | |
| function to have a specific selector name, then we must write it explicitly: | |
| void evaporate(float celcius) [evaporate:] | |
| { | |
| if (celcius > 100) volume -= 0.5 * (celcius - 100); | |
| } | |
| If however we were overriding a function present in the base class, or | |
| implementing a function from an interface, the Objective-C selector would be | |
| inherited. | |
| ### Constructors {#constructors} | |
| To create a new Objective-C object in Objective-C, one would call the allocator | |
| function and then the initializer: | |
| NSObject *o = [[NSObject alloc] init]; | |
| In D, we do this instead: | |
| auto o = new NSObject(); | |
| The `new` operator knows how to allocate and initialize an Objective-C object, | |
| it only need helps to find the right selector for a given constructor. | |
| When declaring an Objective-C class, we can map constructor to selector names: | |
| extern (Objective-C) | |
| class NSSound : NSObject | |
| { | |
| this(NSURL url, bool byRef) [initWithContentsOfURL:byReference:]; | |
| this(NSString path, bool byRef) [initWithContentsOfFile:byReference:]; | |
| this(NSData data) [initWithData:]; | |
| } | |
| Like for member functions, omiting the selector will make the compiler | |
| generate one. But if a constructor is inherited from a base class or | |
| implements a constructor defined in an interface, it'll inherit that selector | |
| instead. | |
| ### Properties {#properties} | |
| When not given explicit selecectors, property functions are given the | |
| appropriate method names so they can participate in key-value coding. | |
| class Value : NSObject | |
| { | |
| @property BigInt number(); | |
| @property void number(BigInt v); | |
| @property void number(int v); | |
| } | |
| Given the above code, the compiler will use the selector `number` for the | |
| getter, `setNumber:` for the setter having the same parameter type as the | |
| getter, and the second alternate setter will get the same compiler-generated | |
| selector as a normal function. | |
| ## Objective-C Protocols {#protocols} | |
| Protocols in Objective-C are mapped to interfaces in D. This declares an | |
| Objective-C protocol: | |
| extern (Objective-C) | |
| interface NSCoding | |
| { | |
| void encodeWithCoder(NSCoder aCoder) [encodeWithCoder:]; | |
| this(NSCoder aDecoder) [initWithCoder:]; | |
| } | |
| Unlike regular D interfaces, we can define a constructor in an Objective-C | |
| protocol. | |
| The protocol than then be implemented in any Objective-C class: | |
| class Cell : NSObject, NSCoding | |
| { | |
| int value; | |
| void encodeWithCoder(NSCoder aCoder) | |
| { | |
| aCoder.encodeInt(value, "value"); | |
| } | |
| this(NSCoder aDecoder) | |
| { | |
| value = aDecoder.decodeInt("value"); | |
| } | |
| } | |
| {Note: We probably need support for @optional interface methods too.} | |
| ## Class Methods {#class-methods} | |
| Each class in Objective-C is an object in itself that contains a set of methods | |
| that relates to the class itself, with no access to instances of that class. | |
| The D equivalent is to use a static member function: | |
| extern (Objective-C) | |
| class NSSound : NSObject | |
| { | |
| static NSSound soundNamed(NSString *name) [soundNamed:]; | |
| } | |
| There is one key difference from a regular D static function however. | |
| Objective-C class methods are dispatched dynamically on the class object, so | |
| they have a `this` reference to the class they're being called on. | |
| `this` might be a pointer to a class derived from the one our function was | |
| defined in, and through it we can call a static function from that derived | |
| class if it overrides one in the current class. Here is an example: | |
| class A : NSObject | |
| { | |
| static void name() { writeln("A"); } | |
| static void writeName() { writeln("My name is ", name()); } | |
| } | |
| class B : A | |
| { | |
| static void name() { writeln("B"); } | |
| } | |
| B.writeName(); // prints "My name is B" | |
| This is not possible with regular static functions in D. | |
| ### Class References {#class-ref} | |
| In Objective-C, you can get a reference to a class by calling the `class` method: | |
| [instance class]; // return the class object for instance | |
| [NSObject class]; // return the class object for the NSObject type | |
| This works similarily in D: | |
| instance.class; // get the class object for instance | |
| NSObject.class; // get the class object for the NSObject type | |
| The only difference is that D is strongly-typed, which means that `x.class` | |
| returns a different type depending on the type of `x`. | |
| Inside an instance method, use `this.class` to get the current class object; | |
| you cannot omit `this` like you can for regular members as it would be | |
| ambiguous for the parser. | |
| There is no `classinfo` property for Objective-C objects. | |
| ## Class Extensions (also known as Categories) {unimplemented} {#class-ext} | |
| With Objective-C it is possible for different compilation units, and even | |
| different libraries, to define new methods that will apply to existing classes. | |
| extern (Objective-C) | |
| class NSString : NSObject | |
| { | |
| wchar characterAtIndex(size_t index) [characterAtIndex:]; | |
| @propety size_t length() [length]; | |
| } | |
| extern (Objective-C) | |
| __classext LastCharacter : NSString | |
| { | |
| wchar lastCharacter() @property; | |
| } | |
| unittest | |
| { | |
| NSString s = "hello"; | |
| assert(s.lastCharacter == 'o'); | |
| } | |
| The `__classext LastCharacter : NSString` syntax maps to an Objective-C | |
| class extension named `LastCharacter` adding methods to the `NSString` class. | |
| Methods in the extension are dispatched dynamically, so you can override them in | |
| a subclass of `NSString`, or in an extension of that subclass. | |
| Having two extensions defining a function with the same selector will make the | |
| Objective-C runtime use one of the two implementations in both cases. | |
| {Question: should we mangle the extension name in the selector to avoid | |
| conflicts? This would transparently implement Apple's recommendation that methods in third-party extensions should use a prefix to avoid clashes with future versions of the extended class and other extensions.} | |
| ## `NSString` Literals {#nsstring-literals} | |
| D string literals are changed to NSString literals whenever the context | |
| requires it. The following Objective-C code: | |
| NSString *str = @"hello"; | |
| becomes even simpler: | |
| NSString str = "hello"; | |
| Automatic conversion only works for strings literals. If the string comes from | |
| a variable, you'll need to construct the `NSString` object yourself. | |
| ## Selector Literals {#selector-literals} | |
| When you need to express a selector, in Objective-C you use the `@selector` | |
| keyword: | |
| SEL sel = @selector(hasSuffix:); | |
| In D, selectors are type-safe. To create a selector type, you must know the | |
| return type and the parameter type this selector should have. You can then | |
| BOOL __selector(NSString) sel = &NSString.hasSuffix; | |
| A selector type can be used just like a delegate, with one difference. When | |
| calling a selector, you need to add the object this selector applies to as the | |
| first argument: | |
| NSString s = "hello world"; | |
| sel(s, "world"); // same as s.hasSuffix("world") | |
| ## Protocol References {#protocol-ref} | |
| When you need to get a reference to a protocol, in Objective-C you use the `@protocol` | |
| keyword: | |
| Protocol *p = @protocol(NSCoding); | |
| In D, you use the `protocolof` property of the interface: | |
| Protocol p = NSCoding.protocolof; | |
| ## Interface Builder Attributes {unimplemented} {#interface-builder} | |
| The `@IBAction` attribute forces the compiler generate a function selector | |
| matching the name of the function, making the function usable as an action in | |
| Interface Builder and elsewhere. | |
| The `@IBOutlet` attribute mark fields that should be available in Interface Builder. | |
| class Controller : NSObject | |
| { | |
| @IBOutlet NSTextField textField; | |
| @IBAction void clearField(NSButton sender) | |
| { | |
| textField.stringValue = ""; | |
| } | |
| } | |
| Special Considerations {#special-considerations} | |
| ---------------------- | |
| ### Casts {#casts} | |
| The `cast` operator works the same as for regular D objects: if the object you | |
| try to cast to is not of the right type, you will get a `null` refrence. | |
| NSView view = cast(NSView)object; | |
| // produce the same result as: | |
| NSView view = ( object && object.isKindOfClass(NSView.class) ? object : null ); | |
| For interfaces, the cast is implemented similarily: | |
| NSCoding coding = cast(NSCoding)object; | |
| // produce the same result as: | |
| NSCoding coding = ( object && object.conformsToProtocol(NSCoding.protocolof) ? object : null ); | |
| The compiler will not emit any runtime check when casting to a base type. | |
| ### `NSObject` vs. `ObjcObject` vs. `id` {#id} | |
| There are two `NSObject` in Objective-C: `NSObject` the protocol and `NSObject` | |
| the class. Not all classes are derived from the `NSObject` class, but they all | |
| implement the `NSObject` protocol. | |
| In D having, an interface and a class with the same name is less practical. | |
| So the `NSObject` protocol is mapped to the `ObjcObject` interface instead. | |
| Because all Objective-C objects implement `ObjcObject` (the `NSObject` | |
| protocol), `ObjcObject` is used as the base type to hold a generic Objective-C | |
| object instead. The Objective-C language uses `id` for that purpose, but `id` | |
| cannot work in D because the correct mapping of selectors requires that we | |
| know the class or inteface declaration. | |
| So if you have a generic Objective-C object and you need to call one of its | |
| functions, you must first cast it to the right type, like this: | |
| void showWindow(ObjcObject obj) | |
| { | |
| if (auto window = cast(NSWindow)obj) | |
| window.makeKeyAndOrderFront(); | |
| } | |
| ### Exceptions {#exceptions} | |
| Exceptions are bridged between Objective-C and D code. Since the two | |
| languages use a different exception handling mechanism, the compiler has to | |
| track which function can throw which kind of exception. It works this way: | |
| 1. Function with `extern(Objective-C)` linkage are assumed to throw using the | |
| Objective-C mechanism. | |
| 2. Any other function is assumed to throw using the D exception mechanism. | |
| You can put an Objective-C type in a catch clause to catch an Objective-C | |
| exception inside your D program: | |
| try | |
| NSLog("%@", obj); | |
| catch (NSException e) | |
| writeln(e.description); | |
| Note that when propagating in D code, Objective-C exceptions are | |
| wrapped inside a `Throwable` object, so if you catch `Throwable` you'll also | |
| catch Objective-C exceptions. Similarily, Objective-C code will see any | |
| thrown D object wrapped inside an instance of `NSException`, so Objective-C | |
| code will be able to catch your exceptions. | |
| ### Memory Management {unimplemented} {#memory} | |
| Only the reference-counted variant of Objective-C is supported, but reference | |
| counting is automated which makes things much easier. | |
| Assiging an Objective-C object to a variable will automatically call the | |
| `retain` function to increase the reference count of the object, and clearing | |
| a variable will call the `release` function on the reference object. Returning | |
| a variable from a function will call the `autorelease` function. | |
| auto a = textField.stringValue; // implicit a.retain() | |
| auto b = a; // implicit b.retain() | |
| b = null; // implicit b.release() | |
| a = null; // implicit a.release() | |
| The compiler can perform flow analysis when optimizing to elide unnecessary | |
| calls to retain and release. | |
| Functions in `extern (Objective-C)` class or interface declarations that return | |
| a retained object reference must be marked with the `@retained` attribute. | |
| The `@retained` attribute is inherited when overriding a function. Most | |
| functions do not need this since they return autoreleased objects. | |
| interface NSCopying | |
| { | |
| @retained | |
| ObjcObject copyWithZone(NSZone* zone) [copyWithZone:]; | |
| } | |
| Note that casting an Objective-C object reference to some other pointer type | |
| will break this mechanism. `retain` and `release` must be called manually in | |
| those cases. | |
| To create a "weak" object reference that does not change the reference count | |
| and automatically becomes `null` when the referenced object is destroyed, use | |
| the `WeakRef` template in the `objc` module. This is needed to break circular | |
| refrences that would prevent memory from being deallocated. | |
| {Note: need to check how to implement auto-nulling `WeakRef` efficiently.} | |
| Member variables of Objective-C classes defined in a D module are managed by | |
| the garbage collector as usual. | |
| {Note: need to check how to implement this with Apple's Modern Objective-C | |
| runtime.} | |
| ### Null Objects {unimplemented} {#null} | |
| Because of the way the Objective-C runtime handle dynamic dispatch, calling a | |
| function on a `null` Objective-C object does nothing and return a zero value | |
| if the function returns an integral type, or `null` for a pointer type. Struct | |
| return values can contain garbage however. | |
| **Do not count on that behaviour in D.** While a D compiler will use the | |
| Objective-C runtime dispatch mechanism whenever it can, it might also call | |
| directly or inline the function when possible. | |
| As a convenience to detect calls to `null` objects, you can use the | |
| `-objcnullcheck` command line directive to make the compiler emit instructions | |
| that check for `null` before each call to an Objective-C method and throw when | |
| it encounters `null`. | |
| {Question: Is disallowing calls on `null` objects desirable? How can we ensure | |
| memory-safety for struct return values?} | |
| ### Applying D attributes {#attrib} | |
| You can apply D attributes to Objective-C methods as usual and they'll have | |
| the same effect as on any D function. | |
| abstract, final | |
| pure, nothrow | |
| @safe, @trusted, @system | |
| Type modifiers such as `const`, `immutable`, and `shared` can also be used on | |
| Objective-C classes. | |
| ### Design by Contract, Unit Tests {#dbc-unittest} | |
| D features such as `unittest`, `in` and `out` contracts as well as `invariant` | |
| all work as expected when defining Objective-C classes in D. | |
| Note that `invariant` will only be called upon entering public functions | |
| defined in D. External Objective-C function won't check the invariants since | |
| Objective-C is unaware of them. | |
| ## Global Functions {#global-func} | |
| `extern(Objective-C)` global functions use the same ABI as C functions, with | |
| the exception that any exception thrown from Objective-C functions are assumed | |
| to use Objective-C's exception handling. | |
| ### Inner Classes {unimplemented} {#inner} | |
| Objective-C classes defined in D can contain inner classes. You can also | |
| derive an inner class from an Objective-C object. | |
| ### Memory Safety {#safety} | |
| While the Objective-C language provide no construct to guarenty memory safety, | |
| D does. Properly declared external Objective-C objects should be usable in | |
| SafeD and provide the same guarenties. | |
| ### Generated Selectors {#generated-selectors} | |
| When a function has no explicit selector, the compiler generate one in a way | |
| that permits function overloading. To this end, a function with one or more | |
| arguments will have the type of its arguments mangled inside the selector name. | |
| Mangling follows what the `type.mangleof` expression returns. | |
| For instance, here is the generated selector for these member functions: | |
| int length(); // generated selector: length | |
| void moveTo(float x, float y); // generated selector: moveTo_f:f: | |
| void moveTo(double x, double y); // generated selector: moveTo_d:d: | |
| void addSubview(NSView view); // generated selector: addSubview_C4cocoa6appkit6NSView: | |
| You generally don't need to care about this. To get the selector of | |
| a function, take its adress and simply assign it to a selector variable: | |
| void __selector(NSView view) sel = &NSView.addSubview; | |
| ## Blocks {unimplemented} {#blocks} | |
| While not stricly speaking part of Objective-C, Apple's block extension for C | |
| and Objective-C is now used at many places through the Mac OS X Objective-C | |
| Cocoa APIs. A block is roughly the same thing as a D delegate, but it is | |
| stored in a different data structure. | |
| The type of a block in D is expressed using the same syntax as a delegate, | |
| except that you must use the `__block` keyword. If an Objective-C function | |
| wants a block argument, you declare it like this: | |
| extern (Objective-C) | |
| class NSWorkspace | |
| { | |
| void recycleURLs(NSArray urls, void __block(NSDictionary newURLs, NSError error) handler) | |
| [recycleURLs:completionHandler:]; | |
| } | |
| Delegates are implicitly converted to blocks when necessary, so you | |
| generally don't need to think about them. | |
| workspace.recycleURLs(urls, (NSDictionary newURLs, NSError error) { | |
| if (error == null) | |
| writeln("success!"); | |
| }); | |
| Blocks are only available on Mac OS X 10.6 (Snow Leopard) and later. |