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.
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.
- Using an existing Objective-C class
- Defining a subclass
- Objective-C Protocols
- Class Methods
- Class Extensions (also known as Categories)
NSString
Literals- Selector Literals
- Protocol References
- Interface Builder Attributes
- Special Considerations
Using an existing Objective-C class
To use an existing Objective-C class, we must first write a declaration for
that class, and we must mark this class as coming 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
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
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
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
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
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 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
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
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}
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
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
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
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}
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
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
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 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:
- Function with
extern(Objective-C)
linkage are assumed to throw using the Objective-C mechanism. - 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}
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}
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
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
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
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}
Objective-C classes defined in D can contain inner classes. You can also derive an inner class from an Objective-C object.
Memory Safety
While the Objective-C language provides no construct to guaranty memory safety, D does. Properly declared external Objective-C objects should be usable in SafeD and provide the same guaranties.
Generated Selectors
When a function has no explicit selector, the compiler generates 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}
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.