Interfacing to Objective-C
D has limited support for interfacing with Objective-C. It supports external classes and calling instance methods. It is only available on OS X, compiling for 64bit.
Fully working example is available at the bottom.
Declaring an External Class
extern (Objective-C) interface NSString { const(char)* UTF8String() @selector("UTF8String"); }
Currently all Objective-C classes need to be declared as interfaces in D. All Objective-C classes that should be accessible from within D need to be declared with the Objective-C linkage.
The @selector attribute indicates which Objective-C selector should be used when calling this method from D. This attribute needs to be attached to all methods.
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 characters that are 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:
extern (Objective-C) interface 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 and aliased in object, meaning it will be implicitly imported. 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 attribue:
- The attribue can only be attached to methods with Objective-C linkage
- The attribue can only be attached once to a method
- The attribue 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 an interface. Example:
extern (Objective-C) interface NSObject { NSObject init() @selector("init"); }
All methods inside an interface 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 interpet 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
Since the only parts of Objective-C that is currently supported is calling instance methods, this example demonstrates how the Objective-C runtime can be used to achieve a running example.
This example will create an Objective-C string, NSString, and log the message using NSLog to stderr.
extern (Objective-C) interface Class { NSString alloc() @selector("alloc"); }
This interface is used to emulate the Class type available in the Objective-C runtime. The instance method alloc will be used to emulate a class method in NSObject, alloc.
extern (Objective-C) interface NSString { NSString initWithUTF8String(in char* str) @selector("initWithUTF8String:"); void release() @selector("release"); }
This is a simplified declaration of the NSString 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) Class objc_lookUpClass(in char* name)
The objc_lookUpClass function is used to get the class definition of the class with the given name.
extern (C) void NSLog(NSString, ...);
This NSLog function prints a message to the System Log facility, i.e. to stderr and Console.
auto cls = objc_lookUpClass("NSString");
Get the class definition of NSString.
auto str = cls.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; extern (Objective-C) interface Class { NSString alloc() @selector("alloc"); } extern (Objective-C) interface NSString { NSString initWithUTF8String(in char* str) @selector("initWithUTF8String:"); void release() @selector("release"); } extern (C) void NSLog(NSString, ...); extern (C) Class objc_lookUpClass(in char* name); void main() { auto cls = objc_lookUpClass("NSString"); auto str = cls.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