Objective-C and Swift - Being Friendly

There are times where Objective-C is still necessary for Swift development. For instance, with one such app I've built in Swift, Speculid, I needed to bring Objective-C code in. With the introduction of C++ libraries in version 2.0, Objective-C became necessary. Luckily there are ways to make both Objective-C and Swift work together in a friendly fashion.

Simplifying Installation with C++ Libraries

Speculid is a completely open source application built with the latest version of Xcode. It automates the process of converting PNG and SVG files into complete Image Sets and App Icons. In the first version, it required the installation of Inkscape and ImageMagick to facilitate the conversion of images from SVG or PNG to PDF or PNG. This became cumbersome both in the installation and usage. With version 2.0, I wanted to make the process of installation and integration as easy as possible by removing any prerequisites steps and pre-existing dependencies. This meant packaging any required dependencies within the application package. In this case, we are using two C++ libraries, Cairo and libRSVG. Cairo is the leading 2D library used throughout the open source community and libRSVG is used for reading SVG content. With these two libraries available, the challenge then became integrating them into the Swift code. As of now, Swift has no way to directly interface with C++. Therefore, we need an intermediary and those intermediaries can either be code in C or Objective-C. While C has some simplicity to it, in this case, I went with Objective-C.

Bridging the Gap with Objective-C

Not only do I have experience with Objective-C, but Objective-C has a strong integration with the Apple ecosystem; despite the simplicity of C. While I am using Objective-C, I want to keep the bulk of the code in Swift. Objective-C will only serve to interface with the C++ libraries as needed. Swift will interact with the user, manage jobs, etc... Objective-C will call the C++ libraries to build the actual graphics. The Swift code will be completely unaware of the code actually used to read, manipulate, and write the resulting files. Here is a brief rundown of the process:

Writing Swift-Friendly Code in Objective-C

There are a couple of ways to make our Objective-C and Swift code work well together. One is making sure the API uses compatible types.

Using Protocols to Facilitate Communication

Objective-C does not use Swift Structs. This leaves Classes as the only way to pass complex data between the Swift and the Objective-C framework. However, in order to keep the interfaces agnostic of each other, we can create Protocols in the Objective-C API which can signify to Swift what is required. In our Objective-C framework, there are two methods available to Swift. The main method which calls the Cairo library and takes an ImageSpecificationProtocol and an ImageHandle object:

@interface CairoInterface : NSObject
+  (BOOL)exportImage:(id<ImageHandle>) sourceHandle withSpecification: (id<ImageSpecificationProtocol>) specification error: (NSError **) error;
@end

Abstracting with Protocols

ImageHandle is another Protocol which the Objective-C framework will use containing the painting surface needed by Cairo. However, the Swift framework doesn’t need to know anything about Cairo. So how does Swift create an ImageHandle without having access to the Cairo surface reference? That's where the other method comes in. We provide a builder/factory class in the Objective-C class which takes parameters available in Swift:

@interface ImageHandleBuilder : NSObject
@property (class, nonatomic, assign, readonly) ImageHandleBuilder*  shared;
- (id<ImageHandle>) imageHandleFromFile: (id<ImageFileProtocol>) file error:(NSError**) error;
@end

The ImageFileProtocol protocol looks this:

@protocol ImageFileProtocol <NSObject>
@property (readonly) NSURL * _Nonnull url;
@property (readonly) ImageFileFormat format;
@end

Protocols as Guidance

ImageFileFormat is an enum which is available in Swift and denotes a file type (PNG, PDF, or SVG). NSURL is automatically taken care of and will be a URL in Swift. As long as we implement ImageFileProtocol in Swift with these properties and it subclasses NSObject, we can easily pass that object to ImageHandleBuilder, we should be able to call the ImageHandleBuilder easily. This goes the same for ImageSpecificationProtocol inCairoInterace.exportImage:withSpecification:error:. ImageSpecificationProtocol contains the information needed to know what and how the information is painted:

@protocol ImageSpecificationProtocol <NSObject>
@property (readonly) id<ImageFileProtocol> file;
@property (readonly) GeometryDimension geometry;
@property (readonly) BOOL removeAlphaChannel;
@property (readonly) id<CairoColorProtocol> backgroundColor;
@end

Let’s look at each of these properties excluding ImageFileProtocol which we already talked about...

With the CairoColorProtocol, we could simply create a new class that contains the info that is needed. However, in Swift, we could minimize new code by simply using an Extension on an existing class.

Using Extensions to Add Functionality to Existing Classes

Here is how the protocol for CairoColorProtocol looks in Objective-C:

@protocol CairoColorProtocol <NSObject>
@property (readonly) double red;
@property (readonly) double green;
@property (readonly) double blue;
@end

In this case rather then creating a new class to implement this in Swift we can extend existing class: NSColor. Here the simple implementation as a Swift extension:

extension NSColor: CairoColorProtocol {
  public var red: Double {
    return Double(redComponent)
  }

  public var green: Double {
    return Double(greenComponent)
  }

  public var blue: Double {
    return Double(blueComponent)
  }
}

Now we can easily communicate between Swift and Objective-C. However, there are a couple of improvements we could make to our Objective-C code to make it even better for Swift.

Writing Swift friendly code in Objective-C

Since day one of Swift, working with Objective-C has been a requirement in some way. As the years have passed, Objective-C has added attributes to make it friendly to paradigms in Swift. There are two main ways we’ll look at: error handling and optionals.

Error Handling

Since the inclusion of try statements in Swift 2.0, we’ve replaced the need for NSError pointers as parameters in Swift. However, if you follow the standard NSError paradigms in Objective-C, Swift will see that method as though it could throw an Error. Here’s an example from the CairoInterface class:

@interface CairoInterface : NSObject
+  (BOOL)exportImage:(id<ImageHandle>) sourceHandle withSpecification: (id<ImageSpecificationProtocol>) specification error: (NSError **) error;
@end

Swift will see this as:

class CairoInterface : NSObject {
    static func exportImage(_ sourceHandle: ImageHandle!, withSpecification specification: ImageSpecificationProtocol!) throws
}

Two things are required for the translation to an error throwing function:

Let’s take a look again at the ImageHandleBuilder method:

@interface ImageHandleBuilder : NSObject
@property (class, nonatomic, assign, readonly) ImageHandleBuilder*  shared;
- (id<ImageHandle>) imageHandleFromFile: (id<ImageFileProtocol>) file error:(NSError**) error;
@end

The difference is that it returns an actual value possibly. Otherwise, we still have an NSError pointer parameter named error.

class ImageHandleBuilder : NSObject {
    func imageHandleFromFile(_ file: ImageFileProtocol!) throws -> ImageHandle!
}

By following this standard, we’ve made error handling much cleaner and easier between languages. However, we still have annoying implicit optionals. Luckily there’s a way to fix that as well.

Optionals and Objective-C

One of the first features of Swift was the idea of Optionals. Optionals remove the ambiguity of pointers used by Objective-C. Unfortunately, for each of these pointers in Objective-C, Swift sees an implicitly unwrapped optional. You can read more about optionals and implicitly unwrapped optionals here. Luckily, there are Objective-C attributes which can help clarify optionals better. Objective-C has two attributes to signify to Swift whether a parameter, property, or return value is optional or not:

_Nonnull and _Nullable. Let’s look again at ImageHandleBuilder:

@interface ImageHandleBuilder : NSObject
@property (class, nonatomic, assign, readonly) ImageHandleBuilder*  shared;
- (id<ImageHandle>) imageHandleFromFile: (id<ImageFileProtocol>) file error:(NSError**) error;
@end

We have a singleton, shared which must exist and we have a function

imageHandleFromFile:error: which needs an ImageFileProtocol and may return an ImageHandle. There, the property shared needs to be _Nonnull and the file parameter in

imageHandleFromFile:error: needs to be _Nonnull. Since

imageHandleFromFile:error: can fail and throw an error, the return type must be optional so we mark that we the _Nullable attribute. Here is the result:

@interface ImageHandleBuilder : NSObject
@property (class, nonatomic, assign, readonly) ImageHandleBuilder* _Nonnull shared;
- (id<ImageHandle> _Nullable) imageHandleFromFile: (id<ImageFileProtocol> _Nonnull) file error:(NSError**) error;
@end

So now rather than Swift seeing:

class ImageHandleBuilder : NSObject {
    class var shared : ImageHandleBuilder! { get }
    func imageHandleFromFile(_ file: ImageFileProtocol!) throws -> ImageHandle!
}

We’ll see:

class ImageHandleBuilder : NSObject {
    class var shared : ImageHandleBuilder { get }
    func imageHandleFromFile(_ file: ImageFileProtocol) throws -> ImageHandle?
}

This makes the API much more understandable in Swift and easier to interface with.

Making Code Friendly

When you need to write Objective-C code or perhaps you are in the process of migrating Objective-C code over to Swift. There are a few things you can do:

Next, we'll be talking about using those C++ libraries in a framework. If you want to stay up-to-date, fill out the form so I can let you know when the article is posted. In the meantime, feel free to checkout Speculid or the open-source repo, as well as Apple's documentation on migrating from Objective-C to Swift.