Interfacing to Objective-C
D has some support for interfacing with Objective-C. It supports classes, subclasses, instance variables, instance and class methods. It is only available on macOS, compiling for 64bit.
Fully working example is available at the bottom.
Classes
Declaring an External Class
import core.attribute : selector; extern (Objective-C) extern class NSString { const(char)* UTF8String() @selector("UTF8String"); }
All Objective-C classes that should be accessible from within D need to be declared with the Objective-C linkage. If the class is declared as extern (in addition to extern (Objective-C)) it is expected to be defined externally.
The @selector attribute indicates which Objective-C selector should be used when calling this method. This attribute needs to be attached to all methods with the Objective-C linkage.
Defining a Class
import core.attribute : selector; // externally defined extern (Objective-C) extern class NSObject { static NSObject alloc() @selector("alloc"); NSObject init() @selector("init"); } extern (Objective-C) class Foo : NSObject { override static Foo alloc() @selector("alloc"); override Foo init() @selector("init"); final int bar(int a) @selector("bar:") { return a; } } void main() { assert(Foo.alloc.init.bar(3) == 3); }
Defining an Objective-C class is exactly the same as declaring an external class but it should not be declared as extern.
To match the Objective-C semantics, static and final methods are virtual. static methods are overridable as well.
Instance Variables
import core.attribute : selector; // externally defined extern (Objective-C) extern class NSObject { static NSObject alloc() @selector("alloc"); NSObject init() @selector("init"); } extern (Objective-C) class Foo : NSObject { int bar_; override static Foo alloc() @selector("alloc"); override Foo init() @selector("init"); int bar() @selector("bar") { return bar_; } } void main() { auto foo = Foo.alloc.init; foo.bar_ = 3; assert(foo.bar == 3); }
Declaring an instance variable looks exactly the same as for a regular D class.
To solve the fragile base class problem, instance variables in Objective-C has a dynamic offset. That means that the base class can change (add or remove instance variables) without the subclasses needing to recompile or relink. Thanks to this feature it's not necessary to declare instance variables when creating bindings to Objective-C classes.
Calling an Instance Method
Calling an Objective-C instance method uses the same syntax as calling regular D methods:
const(char)* result = object.UTF8String();
When the compiler sees a call to a method with Objective-C linkage it will generate a call similar to how an Objective-C compiler would call the method.
The @selector Attribute
The @selector attribute is a compiler recognized UDA. It is used to tell the compiler which selector to use when calling an Objective-C method.
Selectors in Objective-C can contain the colon character, which is not valid in D identifiers. D supports method overloading while Objective-C achieves something similar by using different selectors. For these two reasons it is better to be able to specify the selectors manually in D, instead of trying to infer it. This allows to have a more natural names for the methods in D. Example:
import core.attribute : selector; extern (Objective-C) extern class NSString { NSString initWith(in char*) @selector("initWithUTF8String:"); NSString initWith(NSString) @selector("initWithString:"); }
Here the method initWith is overloaded with two versions, one accepting in char*, the other one NSString. These two methods are mapped to two different Objective-C selectors, initWithUTF8String: and initWithString:.
The attribute is defined in druntime in core.attribute. The attribute is only defined when the version identifier D_ObjectiveC is enabled.
Compiler Checks
The compiler performs the following checks to enforce the correct usage of the @selector attribute:
- The attribute can only be attached to methods with Objective-C linkage
- The attribute can only be attached once to a method
- The attribute cannot be attached to a template method
- The number of colons in the selector needs to match the number of parameters the method is declared with
If any of the checks fail, a compile error will occur.
The D_ObjectiveC Version Identifier
The D_ObjectiveC version identifier is a predefined version identifier. It is enabled if Objective-C support is available for the target.
Objective-C Linkage
Objective-C linkage is achieved by attaching the extern (Objective-C) attribute to a class. Example:
import core.attribute : selector; extern (Objective-C) extern class NSObject { NSObject init() @selector("init"); }
All methods inside a class declared as extern (Objective-C) will get implicit Objective-C linkage.
The linkage is recognized on all platforms but will issue a compile error if it is used on a platform where Objective-C support is not available. This allows to easily hide Objective-C declarations from platforms where it is not available using the version statement, without resorting to string mixins or other workarounds.
Memory Management
The preferred way to do memory management in Objective-C is to use Automatic Reference Counting, ARC. This is not supported in D, therefore manual memory management is required to be used instead. This is achieved by calling release on an Objective-C instance, like in the old days of Objective-C.
Frameworks
Most Objective-C code is bundled in something called a "Framework". This is basically a regular directory, with the .framework extension and a specific directory layout. A framework contains a dynamic library, all public header files and any resources (images, sounds and so on) required by the framework.
These directories are recognized by some tools, like the Objective-C compiler and linker, to be frameworks. To link with a framework from DMD, use the following flags:
-L-framework -L<Framework>where <Framework> is the name of the framework to link with, without the .framework extension. The two -L flags are required because the linker expects a space between the -framework flag and the name of the framework. DMD cannot handle this and will instead interpret the name of the framework as a separate flag.
Framework Paths
Using the above flag, the linker will search in the standard framework paths. The standard search paths for frameworks are:
- /System/Library/Frameworks
- /Library/Frameworks
The following flag from DMD can be used to add a new path in which to search for frameworks:
-L-F<framework_path>
For more information see the reference documentation and the ld man page.
Full Usage Example
This example will create an Objective-C string, NSString, and log the message using NSLog to stderr.
import core.attribute : selector; extern (Objective-C) extern class NSString { static NSString alloc() @selector("alloc"); NSString initWithUTF8String(in char* str) @selector("initWithUTF8String:"); void release() @selector("release"); }
This is a simplified declaration of the NSString class. The alloc method allocates an instance of the class. The initWithUTF8String: method will be used to convert a C string in UTF-8 to an Objective-C string, NSString. The release method is used to release an deallocate the string. Since D doesn't support ARC it's needed to manually release Objective-C instances.
extern (C) void NSLog(NSString, ...);
This NSLog function prints a message to the System Log facility, i.e. to stderr and Console.
auto str = NSString.alloc();
Allocate an instance of the class, NSString.
str = str.initWithUTF8String("Hello World!")
Initialize the Objective-C string using a C string.
NSLog(str);
Log the string to stderr, this will print something like this in the terminal:
2015-07-18 13:14:27.978 main[11045:2934950] Hello World!
str.release();
Release and deallocate the string.
All steps combined look like this:
module main; import core.attribute : selector; extern (Objective-C) extern class NSString { static NSString alloc() @selector("alloc"); NSString initWithUTF8String(in char* str) @selector("initWithUTF8String:"); void release() @selector("release"); } extern (C) void NSLog(NSString, ...); void main() { auto str = NSString.alloc().initWithUTF8String("Hello World!"); NSLog(str); str.release(); }
When compiling the application remember to link with the required libraries, in this case the Foundation framework. Example:
dmd -L-framework -LFoundation main.d