Swiftc, or not swiftc

...That is the question.

And the answer, as so often, is probably "It depends...".

It takes time to run a swift script -- the code must be parsed, the compiler invoked, a temporary executable created and (finally) run:

Sub-300ms isn't bad, and those of you on Apple Silicon will see even quicker times. But if your script is inside a loop those milliseconds start to add up:

OK, a silly example -- but it shows there's little or no "re-use" happening, even when the script is the same every time.

But we can compile the script ourselves, to a temporary or permanent file, then call that executable in an "Execute a Shell Script" Action:

Much faster for our loop of 10 repetitions! But for a single run:

Twice as long as the single swift script Action -- explicit compilation has considerable extra overheads.

@griffman has already shown us one way round this -- use the compiled script if that's already present, create and use it if not. That does mean holding a "library" of compiled scripts somewhere -- and I, for one, will forget to delete the compiled version when I edit the original script!

Note that wanting to include KM variables in your script is generally not a good reason to re-compile the script every time it's used. There may some some edge cases where that's required, but it's better to either pre-compile and include a reference to the variable -- Local_i in this case, which we want to be an integer:

import Foundation

let env = ProcessInfo.processInfo.environment
let i = Int(env["KMVAR_Local_i"] ?? "1") ?? 1

...or parameterise our script then call it with arguments from shell Action:

import Foundation

guard CommandLine.arguments.count > 1,
      let i = Int(CommandLine.arguments[1]) else {
    print("Usage: square <integer>")
    exit(1)
}

As mentioned earlier, pre-compilation does take more time. But you can hide that from the user by doing it asynchronously early in the macro -- the demo has it happen while the user is contemplating the Prompt dialog, so they don't even notice it.

Did I say demo? Here's one that prints out the square of every integer in a user-defined range, complete with time taken, for the various methods described above. Hardly the most useful of things but it does let you play with repetition numbers so you can balance speed against the added complexity of a method. It's got:

  1. Async pre-compilation, in two parts that surround the "Prompt" Action
  2. The obvious and easy "new script for every iteration"
  3. Sync compile pre-loop using KMVAR_ to pull in values
  4. Sync compile pre-loop with script parameters, using command line arguments to push in values
  5. An "Execute Swift Script" that does the loop in the script, so the Action only executes once
  6. A pre-compiled version of (5) that sets the range via command line arguments (could equally have used KMVAR_)

Squares.kmmacros (36.1 KB)

Image

Hopefully this'll spark a few ideas that'll help people use swift without having to stop for a coffee every time their macro runs :wink:

And I'm sure there are other tricks I don't know about -- I look forward to your suggestions!

5 Likes

The wonderful wizard of @Nige_S!!
More tips please!

Thank you,

KC

I know I've told you this already, but for UI interaction, I've created a pre-compiled binary that's bundled in a KM plugin. This single plugin can run any type of UI interaction and all you need to do is feed it the target and interaction type:

1 Like