Apple event type mappings
Overview
The NSAppleEventDescriptor
Foundation class provides a low-level wrapper around the Carbon Apple Event Manager APIs for building and sending Apple events, and for encapsulating the parameter and result data to be included in those events. NSAppleEventDescriptor
defines a descriptorType
property containing an OSType
(a.k.a. "four-char code") that describes the type of value it holds (e.g. 'utxt'
= typeUnicodeText
= UTF16-encoded text), and a data
property containing the value's data serialized as an NSData
instance.
Apple event data types include:
common scalar types such as booleans, integers, doubles, strings, dates and file URLs
ordered lists
records (struct-like key-value lists where each key is an
OSType
)object specifiers, used to construct first-class queries (also known as references in AppleScript), that identify objects within an application.
NSAppleEventDescriptor
includes methods for converting common Apple event data types to and from their Foundation equivalents (e.g. typeUnicodeText
⟷ NSString
). SwiftAutomation extends and improves on these basic mappings as follows:
AppleScript type | Descriptor type | Symbol name | Swift |
---|---|---|---|
boolean | typeBoolean |
AE.boolean | Bool |
integer | typeSInt32 |
AE.integer | Int |
real | typeIEEE64BitFloatingPoint |
AE.real | Double |
text [1] | typeUnicodeText |
AE.UnicodeText | String |
list | typeAEList |
AE.list | Array |
record | typeAERecord |
AE.record | Dictionary |
date | typeLongDateTime |
AE.date | Date |
alias | typeAlias |
AE.alias | URL [2] |
«class bmrk» | typeBookmarkData |
AE.bookmarkData | URL [2] |
«class furl» | typeFileURL |
AE.fileURL | URL [2] |
N/A [3] | typeNull |
AE.null | PREFIXApp |
reference | typeObjectSpecifier |
AE.reference | PREFIXItem /PREFIXItems [4] |
location reference | typeInsertionLoc |
AE.locationReference | PREFIXInsertion [4] |
class | typeType |
AE.typeClass | PREFIXSymbol [5] |
constant | typeEnumerated |
AE.constant | PREFIXSymbol [5] |
property | typeProperty |
AE.property | PREFIXSymbol [5] |
[1] While AppleScript treats string
, text
, and Unicode text
keywords as synonyms for typeUnicodeText
, the Apple Event Manager still considers them to be different types (typeChar
, cText
, typeUnicodeText
). When specifying a command's requestedType:
always use PREFIXSymbol.unicodeText
(or just AE.unicodeText
) as this is the standard UTF-16 representation that all current macOS apps should support.
[2] typeBookmarkData
, typeAlias
, typeFSRef
, and typeFileURL
descriptors are all unpacked Swift URL
structs. URL
structs are always packed as typeFileURL
descriptors. (Be aware that Swift's URL
structs only hold path information, so any alias/bookmark data identifying the original file system object is lost.)
[3] While the typeNull
descriptor is used to represent the root application object in an object specifier, this root object is not visible within AppleScript which prefers to display the object specifier's target (e.g. application "NAME"
, «script»
) instead. AppleScript does define a null
keyword, but this is never used.
[4] Each application glue file defines its own Item
, Items
, and Insertion
subclasses for that particular application, prefixing them with a three-letter code by default; for example, the TextEdit glue defines TEDItem
, TEDItems
, and TEDInsertion
classes. The Item
and Items
classes are equivalent to AppleScript's reference
data type, except that they distinguish between object specifiers that identify a single property/element and object specifiers that identify multiple elements, whereas AppleScript does not.
[5] Each application glue file defines its own Symbol
subclass for that particular application, prefixing it with a three-letter code by default. For example, the TextEdit glue defines a TEDSymbol
class, with a shorthand TED
typealias for convenience.
Mapping notes
While AE-Swift type conversions generally work quite seamlessly, it is sometimes useful to know some of the details involved, particularly when troubleshooting code that deals with older or buggy applications. The following sections provide additional information.
Missing Value
AppleScript's missing value
symbol serves a similar purpose to Swift's nil
, acting as a placeholder where a value is expected but none is available. On the Apple event side, it is represented as a descriptor of typeType
with the code value cMissingValue
('msng'
). SwiftAutomation provides a choice of mappings when unpacking a missing value
descriptor as a Swift value:
SwiftAutomation.MissingValue
is used when a command's return type isAny
. Unlike genericnil
values,MissingValue
is a unique value of concrete typeMissingValueType
so can be compared for equality without having to cast to a specific type first. For example:let path = try TextEdit().documents[1].path() // returns Any if path != MissingValue { ... }
Optional<T>.none
is used when a command's return type is declared asOptional<T>
. For example:let path = try TextEdit().documents[1].path() as String? if let realPath = path { ... }
Both MissingValue
and nil
values are packed as cMissingValue
descriptors when used in a specifier or command:
// find everyone without a birthday
try Contacts().people[CONIts.birthDate == MissingValue].get() as [CONItem]
let date: Date? = nil
try Contacts().people[CONIts.birthDate == date].get() as [CONItem]
// clear all nicknames!
try Contacts().people.nickname(set: MissingValue)
let name: String? = nil
try Contacts().people.nickname(set: name)
Remember though that nil
is generic so can only be used when its exact type is already known; e.g. the following code will give a compiler error "nil is not compatible with expected argument type 'Any'":
try Contacts().people.nickname(set: nil) // this won't compile
[TO DO: what about documenting MayBeMissing<T>
and/or the .missing(MissingValueType)
case in custom enums created via aeglue's -e
option? there's an argument for removing those features to simplify the API, in which case MissingValue
will only be returned when return type is Any
]
Boolean
SwiftAutomation normally maps typeBoolean
descriptors to and from Swift's Bool
type.
SwiftAutomation can also unpack descriptors of typeTrue
and typeFalse
, although these are not normally returned by applications.
Numbers
SwiftAutomation supports Swift's standard integer and floating-point types: Int
and UInt
, including their 8/16/32/64-bit sized variants (Int8
, etc), Float
, and Double
, with Int
and Double
as the preferred formats.
The Apple Event Manager defines a similar range of descriptor types with different widths and formats, though most applications use typeSInt32
(32-bit signed integer) and typeIEEE64BitFloatingPoint
(Double
) as standard.
Well-designed applications should accept any numeric descriptor type (coercing it when necessary), though to maximize compatibility across all application SwiftAutomation always packs Swift integers as typeSInt32
if possible; only switching to typeSInt64
/typeUInt32
/typeUInt64
(or typeIEEE64BitFloatingPoint
if the isInt64Compatible
option is false) for values that cannot fit into typeSInt32
.
If an NSNumber
instance is supplied as a command parameter, SwiftAutomation will pack it as typeBoolean
if it can determine it represents a Boolean value, otherwise it will pack it as an integer or floating-point number according to its objCType
property. (The NSNumber
class cluster privately represents Boolean values as bridged CFBoolean
(__NSCFBoolean
) instances; however, its public objCType
API does not distinguish between these and char
values so SwiftAutomation cannot guarantee it will detect the correct type. If in doubt, cast the NSNumber
to Bool
/Int
/Double
first to ensure it packs as a specific type.)
Strings
When packing and unpacking String
values, SwiftAutomation uses the NSAppleEventDescriptor
class's +descriptorWithString:
and -stringValue
methods, both of which use descriptors of typeUnicodeText
, coercing other types as needed.
Note that while the CoreServices framework's AEDataModel.h
header states that typeUnicodeText
is deprecated in favor typeUTF8Text
and typeUTF16ExternalRepresentation
, it remains in widespread use; therefore SwiftAutomation continues to use typeUnicodeText
to ensure the broadest compatibility with existing scriptable applications.
Some older Carbon applications might return text values as descriptors of typeChar
, typeIntlText
, or typeStyledText
. These types are long-deprecated and their use strongly discouraged in macOS. SwiftAutomation will coerce these descriptors to typeUnicodeText
before unpacking them, or throw an UnpackError
if the coercion fails.
File system references
// TO DO: SwiftAutomation currently uses Swift's URL struct, which doesn't provide the extra descriptor caching or explicit coercion methods (it also currently doesn't distinguish between path-style URLs and bookmark-style URLs, and treats everything as typeFileURL, which might cause problems on any older, poorly designed Carbon apps that explicitly typecheck their params as typeAlias instead of coercing them to that type as they're supposed to)
The Apple Event Manager defines a number of modern (typeFileURL
, typeBookmarkData
), legacy (typeAlias
), and deprecated (typeFSRef
, typeFSS
) descriptor types for identifying file system objects. (Object specifiers of form {want:file,from:null,form:name,seld:"HFS:PATH:STRING"}
are also recognized by most applications, though not recommended.) Fortunately, the Apple Event Manager also implements a number of coercion handlers for coercing between these types, so when interacting with most applications you should not need to know or care exactly which of these types are used: the application should coerce supplied values to whichever type(s) it requires.
SwiftAutomation packs URL
instances containing file://
URLs as descriptors of typeFileURL
, which the majority of applications should accept. Non-file URLs are not supported and will result in a PackError
being thrown. Occasionally, an older Carbon application may not accept file URL descriptor, in which case it may be necessary to convert the URL
to a different form before passing it to the application command. For example, if an application requires an alias descriptor, then pre-pack the URL
as a typeAlias
descriptor as follows:
let myFile = URL(fileURLWithPath: "/Users/jsmith/MyFile.txt")
let myFileDesc = NSAppleEventDescriptor(fileURL: myFile).coerce(toDescriptorType: typeAlias) [TO DO: not sure this works on recent OS versions]
Similarly, some older Carbon applications may occasionally use colon-delimited HFS path strings even though macOS has long since deprecated these in favor of standard POSIX paths. SwiftAutomation includes the following compatibility functions for converting to and from HFS path strings where unavoidable:
HFSPath(fromFileURL: URL) -> String
fileURL(fromHFSPath: String) -> URL
For example:
let myFile = URL(fileURLWithPath: "/Users/jsmith/MyFile.txt")
let myHFSPath = HFSPath(fromFileURL: myFile)
// "Macintosh HD:Users:jsmith:MyFile.txt"
However, be aware that non-existent paths may not convert correctly, and that HFS paths, unlike POSIX paths, cannot distinguish between two mounted volumes which both have the same name (one of the reasons HFS was deprecated).
Records
The typeAERecord
AE type is a struct-like data structure containing zero or more properties. SwiftAutomation represents AE records as Dictionary<Symbol,Any>
instances. The keys in this dictionary are normally instances of Symbol
or glue-defined subclass representing SwiftAutomation-style property names, e.g. TEDSymbol.text
. Dictionary keys may also be Symbol
instances created with raw four-char codes (if no equivalent human-readable terminology exists), e.g. Symbol(code: "docu")
or String
instances representing "user-defined" keys (an AppleScript-ism, rarely used by applications).
If a dictionary includes a Symbol.class_
(or Symbol(code:"pcls")
) key whose value is also a Symbol
, SwiftAutomation will pack the other items into an AE record coerced to that value's' type. [TO DO: add example for clarity] Similarly, when unpacking an record-based descriptor that isn't typeAERecord
, typeObjectSpecifier
or other known type, SwiftAutomation will unpack it as an Dictionary
instance with an additional Symbol.class_
key and Symbol
value to indicate the descriptor's type.
AERecords may also be packed and unpacked as glue-defined record structs if preferred; see the section on creating and using custom record structs in Chapter 10.
Types and enumerators
SwiftAutomation represents both standard Apple event type names and application-defined class, property, and enumerator names as instances of the glue's Symbol
subclass. For example, a standard TextEdit glue defines a TEDSymbol
subclass, typealiased as TED
for convenience:
// Standard Apple event data types
TED.boolean
TED.UnicodeText
TED.list
...
// Application-defined class names
TED.document
TED.window
TED.disk
...
// Application-defined property names
TED.class_
TED.name
TED.color
...
// Application-defined enumerators
TED.yes
TED.no
TED.ask
...
Descriptors of typeType
, typeEnumerated
, and typeProperty
are unpacked as Symbol
subclass instances, using raw four-char codes instead of names when the corresponding terminology is not available, e.g.:
TEDSymbol(code: "ABCD", type: "type")
Be aware that symbol names for standard data types are derived from AppleScript, not Swift, terminology. Thus the symbolic name for the typeIEEE64BitFloatingPoint
descriptor type, which maps to AppleScript's real
type and Swift's Double
type, is AE.real
(or PREFIX.real
), not AE.double
. This avoids potential conflicts and ensures consistency with standard and custom terminology used by application dictionaries.
Other types
The Apple Event Manager defines many other AE types whose names and codes are defined by SwiftAutomation for completeness. A few of these types are of occasional interest to users, the rest can simply be ignored. In most cases, values of these types will be represented by NSAppleEventDescriptor
instances as SwiftAutomation doesn't automatically convert them.