cocoaconf dc - automate with swift - tony ingraldi

41
Automate with Swift CocoaConf DC, April 10, 2015 Tony Ingraldi GitHub: tingraldi • Twitter: @TonyIngraldi

Upload: tony-ingraldi

Post on 16-Jul-2015

300 views

Category:

Software


3 download

TRANSCRIPT

Automate with SwiftCocoaConf DC, April 10, 2015

Tony Ingraldi

GitHub: tingraldi • Twitter: @TonyIngraldi

Itinerary

• Motivation: Why Automate?

• A bit of history

• Why Swift?

• Nuts and bolts

Show of Hands?

• Have you used…

• AppleScript?

• Automator?

• Other form of application scripting?

Why Automate?

• Improve productivity

• Avoid repetitive tasks

• Streamline workflows

• Opportunities for experimentation

• Add missing features to OS X apps!

Scriptable Applications

Demo

Sneak peak

Open Scripting Architecture• Introduced way back in October 1993

• The state of the Mac looked like this

Beige!

Open Scripting Architecture

• Survived the transition to OS X

• Leveraged by OS X to launch applications, open documents, etc.

• A standard mechanism for interapplication communication

Language Options

• AppleScript (there from the beginning)

• JavaScript for Automation (as of Yosemite)

• Python (PyObjC, py-appscript)

• Ruby (RubyCocoa, RubyOSA)

• And now Swift!

Language Options

• AppleScript (there from the beginning)

• JavaScript for Automation (as of Yosemite)

• Python (PyObjC, py-appscript)

• Ruby (RubyCocoa, RubyOSA)

• And now Swift!

AppleScript

• A “natural language” programming model

• Primarily geared toward being the “glue” between apps

• Limited inherent functionality

AppleScript

• Can be frustrating to people who are fluent in “normal” programming languages

• Verbosity is a hallmark

set fileName to "name.jpg"

set sansExtension to text 1 thru ((offset of "." in fileName) - 1) of fileName

Swift• A “primary” language

• Ready access to Cocoa

• Can run Swift scripts using #! convention

• A familiar coding style

let fileName = "name.jpg"

let sansExtension = fileName.stringByDeletingPathExtension

Scripting Bridge

• Introduced in Mac OS X Leopard (version 10.5)

• Provides high-level Objective-C access to scriptable applications

• Can be leveraged in Swift, with a bit of extra work

Scripting Bridge Recipe

• 1 part sdef

• 1 part sdp

• A pinch of manual intervention

• Automate to taste

Scripting Definition

• Scriptable applications include resources that describe their scripting interface

• Can be in a variety of forms

• The sdef command-line utility extracts the scripting definition

• Writes to standard output in XML format

Scripting Definition

• Using sdef

sdef /path/to/App.app > App.sdef

• For the most part, using sdef is “run and forget”

• Until you find out what’s wrong when you run sdp

• Some sdp warnings can be ignored

Scripting Definition

• Using sdp

sdp -fh --basename App App.sdef

Demo

sdef and sdp

Kicking the Tires

• Xcode

• Swift and ObjC command line targets

• Includes the header generated by sdp

• Experiment in main.swift and main.m

Demo

Kicking the tires in Xcode

Don’t Run with Scissors• Using Objective-C header leads to pervasive AnyObject typing

• Works, but can lead to ambiguity

• For properties, sometimes have to resort to using valueForKey

• Sometimes leads to awkward method invocation

An Alternative

• Create Swift protocols that cover the generated Objective-C API

• 👍 Supports rich set of types

• 👍 API translation can be automated

• 👎 Requires optional declaration of all properties and methods

Objective-C Excerpt

@interface AcornApplication : SBApplication

@property (copy, readonly) NSString *name;

- (SBElementArray *) windows; - (NSString *) taunt;

@end

SBObject

@objc public protocol SBObjectProtocol: NSObjectProtocol {

func get() -> AnyObject!

}

SBApplication

@objc public protocol SBApplicationProtocol: SBObjectProtocol {

func activate() var delegate:SBApplicationDelegate! { get set }

}

Application Protocol

@objc public protocol AcornApplication: SBApplicationProtocol {

optional var name: String { get } optional func windows() -> SBElementArray

optional func taunt() -> String

} extension SBApplication : AcornApplication {}

Putting it Togetherimport ScriptingBridge

@objc public protocol AcornApplication : … {…} extension SBApplication : AcornApplication {}

let acorn = SBApplication .applicationWithBundleIdentifier( “com.flyingmeat.Acorn4" ) as! AcornApplication

acorn.taunt!()

Acorn Taunt

Plan of Attack

• Create frameworks for target applications

• Install in /Library/Frameworks

• Write scripts instead of compiled code

By the Way• Playgrounds don’t fit well

• The Swift REPL could spontaneously combust at any moment

• What’s a scripter to do?

• Use a text editor

• Edit, run, repeat

Script Invocation

• Hash-bang on the first line

#!/usr/bin/xcrun swift -F /Library/Frameworks

• chmod +x SomeScript.swift

A Little Help

https://github.com/tingraldi/SwiftScripting

• Header conversion utilities

• Sample Application Scripting Frameworks

• Sample Scripts

• Scripting Utilities Framework

Demo

A Scripting Framework

A Little More Helpimport ScriptingBridge

@objc public protocol AcornApplication {…} extension SBApplication : AcornApplication {}

let acorn = SBApplication .applicationWithBundleIdentifier( "com.flyingmeat.Acorn4" ) as! AcornApplication

acorn.taunt!()

A Little More Help

import ScriptingBridge import AcornScripting // Acorn Swift protocols

let acorn = SBApplication .applicationWithBundleIdentifier( "com.flyingmeat.Acorn4" ) as! AcornApplication

acorn.taunt!()

A Little More Help

import ScriptingUtilities // Helper framework import AcornScripting // Acorn Swift protocols

let acorn = Application(name: "Acorn") as! AcornApplication

acorn.taunt!()

Where to Put Scripts?

• Short answer: anywhere

• Some applications have special script folders

• Scripting Menu

• Invoke via an Automator Service

Demo

Scripting Menu, etc.

Summary

• Automate for productivity

• Add “missing features”

• Reinforce/leverage Swift expertise

Questions?