Interfacing to Objective-C

Contents
  1. Classes
    1. Declaring an External Class
    2. Defining a Class
  2. Protocols
    1. Declaring a Protocol
    2. Optional Methods
  3. Instance Variables
  4. Calling an Instance Method
  5. The @selector Attribute
    1. Compiler Checks
  6. The @optional Attribute
    1. Compiler Checks
  7. Objective-C Linkage
  8. Memory Management
  9. Frameworks
    1. Framework Paths
  10. Full Usage Example

D supports interfacing with Objective-C. It supports protocols, classes, subclasses, instance variables, instance methods and class methods. Platform support might vary between different compilers.

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.

Protocols

Declaring a Protocol

import core.attribute : selector;
import core.stdc.stdio : printf;

extern (Objective-C)
interface Foo
{
    static void foo() @selector("foo");
    void bar() @selector("bar");
}

extern (Objective-C)
class Bar : Foo
{
    static void foo() @selector("foo")
    {
        printf("foo\n");
    }

    void bar() @selector("bar")
    {
        printf("bar\n");
    }
}

Objective-C protocols are represented as interfaces in D and are declared using the interface keyword.

All Objective-C protocols that should be accessible from within D need to be declared with the Objective-C linkage.

Objective-C protocols support virtual class (static) methods. These methods must be implemented by the class that implements the protocol (unless they are optional). To match these semantics, static methods are virtual. That also means that static methods with Objective-C linkage, inside an interface cannot have a body.

Optional Methods

import core.attribute : optional, selector;
import core.stdc.stdio : printf;

struct objc_selector;
alias SEL = objc_selector*;

extern (C) SEL sel_registerName(in char* str);

extern (Objective-C)
extern class NSObject
{
    static NSObject alloc() @selector("alloc");
    NSObject init() @selector("init");
}

extern (Objective-C)
interface Foo
{
    bool respondsToSelector(SEL sel) @selector("respondsToSelector:");
    void foo() @selector("foo");

    // this is an optional method
    @optional void bar() @selector("bar");
}

extern (Objective-C)
class Bar : NSObject, Foo
{
    override static Bar alloc() @selector("alloc");
    override Bar init() @selector("init");

    bool respondsToSelector(SEL sel) @selector("respondsToSelector:");

    void foo() @selector("foo")
    {
        printf("foo\n");
    }
}

void main()
{
    Foo f = Bar.alloc.init;

    // check, at runtime, if the instance `f` implements the method `bar`
    if (f.respondsToSelector(sel_registerName("bar")))
        f.bar();
    else
        f.foo();
}

Objective-C protocols support optional methods. Optional methods are not required to be implemented by the class that implements the protocol. To safely call an optional method, a runtime check should be performed to make sure the receiver implements the method.

In D, optional methods are represented using the @optional attribute.

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 @optional Attribute

The @optional attribute is a compiler recognized UDA. It is used to tell the compiler that a method, with Objective-C linkage, declared inside an interface is optional. That means that the class that implements the interface does not have to implement the method.

To safely call an optional method, a runtime check should be performed to make sure the receiver implements the method.

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 @optional attribute:

  • The attribute can only be attached to methods with Objective-C linkage
  • The attribute can only be attached to a method inside an interface
  • The attribute can only be attached once to a method
  • The attribute cannot be attached to a template method

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

© 1999–2021 The D Language Foundation
Licensed under the Boost License 1.0.
https://dlang.org/spec/objc_interface.html