Swift Scripting (Part 3)

In part one of this series, we covered the basics of the Scripting Bridge and how one might leverage this technology with Swift. In part two, we explored the idea of using Swift protocols to describe the scriptable interface exposed by an application. Here we’ll look at how we can automatically generate the necessary protocols and package them up in an OS X Framework for easy reuse.

To translate the sdp-generated collection of Objective-C interfaces to a collection of Swift protocols, we’ll leverage the Python bindings for libclang. The first step is to install the bindings. This can be done as follows in a Terminal window

curl -O https://pypi.python.org/packages/source/c/clang/clang-3.5.tar.gz
tar zxf clang-3.5.tar.gz
cd clang-3.5
sudo python setup.py install

The next step would be to write a Python script using the clang module to perform the transformation. For the present discussion, we’ll skip the details of the Python code and simply make use of sbhc.py (Scripting Bridge Header Converter), which is available as part of the SwiftScripting project on GitHub. The sbhc.py script is invoked as follows (using Acorn as an example)

/path/to/sbhc.py Acorn.h

sbhc.py does its work silently. The output of the above command would a file named Acorn.swift in the current directory. Acorn.swift would contain a set of enums and protocols for the Acorn application. The SwiftScripting project includes a second utility, sbsc.py (Scripting Bridge Scripting Classes). sbsc.py is used to generate a separate Swift file which defines a single enum representing the scripting classes for the target application. sbsc.py is invoked as follows

/path/to/sbsc.py Acorn.sdef

Note that the sbsc.py script does not make use of the clang module. It invokes xmllint on the sdef specified on the command line to extract the class strings and then emits the enum in a straight-forward manner.

Now, we can create an OS X framework to house all of the necessary ingredients. This framework can then be imported by Swift automation scripts when targeting a particular application. First, create a new OS X Framework project in Xcode.

Create a new OS X framework

You’ll probably want follow a consistent scheme when naming the frameworks that you create. The convention that I’ve adopted is to use the name of the target application followed by Scripting. So, for the Acorn application, the name I use for the scripting framework is AcornScripting.

Once the framework project has been created, you’ll need to add the following files (again, using the Acorn as an example):

Acorn.swift
AcornScripting.swift

The only other task you’ll need to perform in Xcode is to ensure that the Swift source files are designated as members of the framework target. With that taken care of, you can now build the framework.

To build the framework, visit the directory containing the framework’s .xcodeproj file and issue the xcodebuild command. This will perform a release build and all of the output will be contained in the build directory below the project directory. If the build succeeds, the framework will be located under build/Release. The framework should be copied or moved to the /Library/Frameworks directory.

In part four of this series, we’ll explore automation of scriptable applications following the approach introduced parts one through three.

Swift Scripting (Part 2)

In part one of this series, we covered the basics of the Scripting Bridge and how one might leverage this technology with Swift. We left off with some potential problem areas. Specifically, we saw that the default approach for leveraging the sdp-generated Objective-C interface could lead to ambiguity on the Swift side of things.

Let’s now take a closer look at an example of such an ambiguity. The excerpt below is from the sdp-generated header file for the Acorn image editor. Incidentally, Acorn is an excellent product and if you’re searching for an image editor you should check it out!

@interface AcornApplication : SBApplication
// The name of the application.
@property (copy, readonly) NSString *name;

// The version number of the application.
@property (copy, readonly) NSString *version;  

 // Have Acorn taunt you.
- (NSString *) taunt; 
@end

Here’s some sample code that demonstrates how we might access the version property of the application.

import Foundation
import ScriptingBridge

let acorn: AnyObject = SBApplication(bundleIdentifier: "com.flyingmeat.Acorn4")!
print(acorn.version)

The above code will not compile. The problem is that the acorn variable must be declared to be of type AnyObject. Since there are multiple, conflicting definitions of version declared in different classes, the compiler cannot resolve the version property uniquely. Here we can resort to using valueForKey("version") to work around this issue. In other cases, where we want to invoke a method as opposed to simply retrieving the value of a property, this ambiguity can force awkward method invocations such as

object.someMethod() as Void

The above construct would be necessary if there were multiple someMethod implementations defined with different return types, where the desired invocation returns Void. As mentioned in part one, we can avoid the ambiguity entirely by defining one or more Swift protocols that cover the Objective-C API. For the subset of the Acorn API shown above, an equivalent set of Swift protocol looks like this

import AppKit
import ScriptingBridge

@objc public protocol SBObjectProtocol: NSObjectProtocol {
    func get() -> AnyObject!
}

@objc public protocol SBApplicationProtocol: SBObjectProtocol {
    func activate()
    var delegate: SBApplicationDelegate! { get set }
}

@objc public protocol AcornApplication: SBApplicationProtocol {
    // The name of the application.
    optional var name: String { get } 

    // The version number of the application.
    optional var version: String { get } 

    // Have Acorn taunt you.
    optional func taunt() -> String 
}
extension SBApplication: AcornApplication {}

Here we’ve defined two base protocols to represent the commonly used functionality of the SBObject and SBApplication classes. The AcornApplication protocol extends the SBApplication protocol and includes the version property. Note that all properties and methods in the AcornApplication protocol are declared as optional. Also note that the extension on SBApplication indicating that it conforms the AcornApplication has an empty implementation.

With these protocols defined, we can alter the code to use explicit typing and access the version property directly.

import Foundation
import ScriptingBridge

@objc public protocol SBObjectProtocol: NSObjectProtocol {...}
@objc public protocol SBApplicationProtocol: SBObjectProtocol {...}
@objc public protocol AcornApplication: SBApplicationProtocol {...}
extension SBApplication: AcornApplication {}

let acorn = SBApplication(bundleIdentifier: "com.flyingmeat.Acorn4") as! AcornApplication
println(acorn.version!)

Here we have explicitly-typed the acorn variable and we have direct, unambiguous access to the version property. A downside of this approach is that we need to deal with the optional nature of the properties and methods in the protocol. Here we’ve done that by using the ! when dereferencing the version property.

In part three of this series, we’ll look at how the generation of the necessary Swift protocols can be automated and how the resulting code can be packaged up as a Framework to support reuse in a variety of scripts.