Upcoming and OnDemand Webinars View full list

Dude, Where’s my Call?

Mark Dalrymple

Imagine one day you’re feeding some innocuous looking code to a Swift compiler:

// xcrun -sdk macosx swiftc -emit-executable cg.swift

import CoreGraphics

let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, 0.0, 23.0)

And then you get a smackdown:

cg.swift:7:12: error: 'CGPathCreateMutable()' has been replaced by 'CGMutablePath.init()'
<unknown>:0: note: 'CGPathCreateMutable()' has been explicitly marked unavailable here
cg.swift:8:1: error: 'CGPathMoveToPoint' has been replaced by instance method 'CGMutablePath.moveTo(_:x:y:)'
<unknown>:0: note: 'CGPathMoveToPoint' has been explicitly marked unavailable here

Where’d it go? It got renamed.

One of Swift 3’s big features is “The Grand Renaming”, brought about
via Swift-Evolution proposal SE-0005 (Better Translation of
Objective-C APIs Into
Swift)

and SE-0006 (Apply API Guidelines to the Standard
Library)
.
The Grand Renaming renames operations in C and Objective-C
APIs giving them a Swiftier feel. There’s a migrator in Xcode that
will massage your Swift 2 code in to the new style. It’ll perform a lot
of the mechanical changes, leaving you with some mop-up due to other language
changes, such as removal of the C for
loop
.

Some of the renamings are pretty mild, such as this one in NSView:

// Swift 2
let localPoint = someView.convertPoint(event.locationInWindow, fromView: nil)

// Swift 3
let localPoint = someView.convert(event.locationInWindow, from: nil)

Here Point was removed from the method’s base name. You know you’re
dealing with a point, so there’s no need to repeat that fact. fromView was
renamed to just from because the word View was only providing redundant type
information, not making the callsite any clearer.

Other changes are much bigger, such as from Core Graphics:

// Swift 2 / (Objective-C)
let path = CGPathCreateMutable()
CGPathMoveToPoint (path, nil, points[i].x, points[i].y)
CGPathAddLineToPoint (path, nil, points[i + 1].x, points[i + 1].y)
CGContextAddPath (context, path)
CGContextStrokePath (context)

// Swift 3
let path = CGMutablePath()
path.move (to: points[i])
path.addLine (to: points[i + 1])

context.addPath (path)
context.strokePath ()

Whoa. That’s pretty
huge. The API now looks like a pleasant Swift-style API rather than a
somewhat old-school C API. Apple totally changed the Core Graphics
(and GCD) APIs in Swift to make them nicer to use. You cannot use the
old-school CG C API in Swift 3, so you will need to become accustomed
to the new style. I ran GrafDemo, the demo program for my Core
Graphics
postings

through the auto-translator (twice). You can see the before and
after for the first version of Swift3
in this pull request, and for Xcode8b6’s version of Swift3 in this pull request.

What Did They Do?

The Core Graphics API is fundamentally a bunch of global variables and
global free functions. That is, functions that aren’t directly tied
to some other entity like a class or a struct. It’s pure convention
that CGContextAddArcToPoint operates on CGContexts, but there’s
nothing stopping you passing in a CGColor. Outside of blowing up at
runtime, that is. It’s C-style object-orientation where you have an opaque
type that’s passed as the first argument, as a kind of magic cookie.
CGContext* functions take a CGContextRef. CGColor* functions take a
CGColorRef.

Through some compiler magic, Apple has transformed these opaque
references into classes, and has added methods to these classes that
map to the C API. When the compiler sees something like

let path = CGMutablePath()
path.addLines(between: self.points)
context.addPath(path)
context.strokePath()

It is actually, under the hood, emitting this sequence of calls:

let path = CGPathCreateMutable()
CGPathAddLines(path, nil, self.points, self.points.count)
CGContextAddPath(context, path)
CGContextStrokePath(context)

“New” Classes

These are the common opaque types that have gotten the Swift 3.0 treatment (omitting some of the esoteric types like CGDisplayMode or CGEvent), as swell as a representative method or two:

  • CGAffineTransformtranslateBy(x:30, y:50), rotate(by: CGFloat.pi / 2.0)
  • CGPath / CGMutablePathcontains(point, using: evenOdd), .addRelativeArc(center: x, radius: r, startAngle: sa, delta: deltaAngle)
  • CGContextcontext.addPath(path), context.clip(to: cgrectArray)
  • CGBitmapContext (folded in to CGContext) – let c = CGContext(data: bytes, width: 30, height: 30, bitsPerComponent: 8, bytesPerRow: 120, space: colorspace, bitmapInfo: 0)
  • CGColorlet color = CGColor(red: 1.0, green: 0.5, blue: 0.333, alpha: 1.0)
  • CGFontlet font = CGFont("Helvetica"), font.fullName
  • CGImageimage.masking(imageMask), image.cropping(to: rect)
  • CGLayerlet layer = GCLayer(context, size: size, auxilaryInfo: aux), layer.size
  • CGPDFContext (folded in to CGContext) / CGPDFDocumentcontext.beginPDFPage(pageInfo)

CGRect and CGPoint already had a set of nice extensions added prior to Swift 3.

How Did They Do It?

The compiler has built-in linguistic transforms that turn
Objective-C’s naming style into a more Swifty form. It drops
duplicate words and words that just repeat type information. It also
moves some words that were before the opening parenthesis in function
calls and moves them into the parens as argument labels. This
automatically cleans up a great number of calls.

Humans, of course, like to make verbal languages subtle and
complicated, so there are mechanisms in the Swift compiler that allow
for manual overrides of what the automated translator comes up with.
These are implementation details (so don’t depend on them in shipping
products), but they offer insight into the work that’s been done to
make existing API be available in Swift.

One mechanism involved are “overlays”, which are secondary libraries
that the compiler imports when you bring in a framework or a C
library. The Swift
Lexicon

describes overlays as “augmenting and extending a library on the
system when the library on the system cannot be modified.
” Those
really nice extensions on CGRect and CGPoint that have been there
forever, such as ` someRect.divide(30.0, fromEdge: .MinXEdge)`? They
came from overlays. The toolchain thinks “Oh, I see you’re linking
against Core Graphics. Let me also include this set of convenience
functions.”

There’s another mechanism,
apinotes,
especially
CoreGraphics.apinotes,
which controls naming and visibility on a symbol-by-symbol basis from
Core Graphics.

For example, in Swift there is no use for calls like CGRectMake
to initialize fundamental structures because there are initializers for them.
So make these calls unavailable:

# The below are inline functions that are irrelevant due to memberwise inits
- Name: CGPointMake
  Availability: nonswift
- Name: CGSizeMake
  Availability: nonswift
- Name: CGVectorMake
  Availability: nonswift
- Name: CGRectMake
  Availability: nonswift

And then other mappings – if you see this in Swift, call that function:

# The below are fixups that inference didn't quite do what we wanted, and are
# pulled over from what used to be in the overlays
- Name: CGRectIsNull
  SwiftName: "getter:CGRect.isNull(self:)"
- Name: CGRectIsEmpty
  SwiftName: "getter:CGRect.isEmpty(self:)"

If the compiler sees something like rect.isEmpty(), it will
emit a call to CGRectIsEmpty.

There are also method and function renames:

# The below are attempts at providing better names than inference
- Name: CGPointApplyAffineTransform
  SwiftName: CGPoint.applying(self:_:)
- Name: CGSizeApplyAffineTransform
  SwiftName: CGSize.applying(self:_:)
- Name: CGRectApplyAffineTransform
  SwiftName: CGRect.applying(self:_:)

When the compiler sees rect.applying(transform), it knows to emit
CGRectApplyAffineTransform.

The compiler only automatically renames Objective-C APIs because of the well-defined
nomenclature. C APIs (like Core Graphics) have to be done
via overlays and apinotes.

What You Can Do

You can do things similar to the apinotes mechanism
via NS_SWIFT_NAME. You use this macro to annotate your C/Objective-C
headers, indicating what name to use in Swift-land. The compiler
will make the same kind of substitutions (“If I see X, I’ll emit Y”)
for your NS_SWIFT_NAMEs.

For example, here’s a call from the Intents (Siri) framework:

- (void)resolveWorkoutNameForEndWorkout:(INEndWorkoutIntent *)intent
                         withCompletion:(void (^)(INSpeakableStringResolutionResult *resolutionResult))completion
     NS_SWIFT_NAME(resolveWorkoutName(forEndWorkout:with:));

Calling it from Objective-C looks like this:

NSObject<INEndWorkoutIntentHandling> *workout = ...;

[workout resolveWorkoutNameForEndWorkout: intent  withCompletion: ^(INSpeakableStringResolutionResult) {
     ...
}];

while in Swift it would be

let workout: INEndWorkoutIntentHandling = ...
workout.resolveWorkoutName(forEndWorkout: workout) {
    response in
    ...
}

NS_SWIFT_NAME, coupled with Objective-C’s lightweight
generics, nullability annotations, and the Swift compiler’s automatic
renaming of Objective-C API, you can get interfaces that feel right at
home in Swift.

It is possible to make your own overlays and apinotes, but those are
intended to be used when Swift is shipped with Apple’s SDKs.
You can distribute apinotes with your own frameworks, but overlays need
to built from within a Swift compiler tree.

For making Swiftier APIs yourself, you should do as much as you can with
with header audits (such as adding nullability annotations and NS_SWIFT_NAME),
and then tossing in a few Swift files in your project as a fake overlay to
cover any additional cases.
These “overlay” files need to be shipped as source until there’s ABI stability.

By grazing through the iOS 10 headers, it looks like newer APIs tend
to use NS_SWIFT_NAME, while older, more established API use
apinotes. This makes sense because the headers are shared amongst
different Swift versions and adding new NS_SWIFT_NAMEs to older,
established headers might break existing code without a compiler
change. Also, apinotes can be added by the compiler team or community
members while changes to header files require attention from the team
that owns the headers. That team might already be at capacity
getting ready to ship their own functionality.

Is It Good?

The Swift 3 versions of Core Graphics is definitely much nicer and
more Swifty. To be honest, I’d prefer to work with something like
this on the Objective-C side as well. You do lose some googlability,
and have to do more mental translation when you see existing CG code
in Stack Overflow postings or in online tutorials. But it’s no worse
than the mental gymnastics needed for general Swift code these days.

There are some API incongruities due to the quasi OO nature of CG and
how it comes in to Swift. From the CoreGraphics.apinotes:

- Name: CGBitmapContextGetWidth
  SwiftName: getter:CGContext.width(self:)
- Name: CGPDFContextBeginPage
  SwiftName: CGContext.beginPDFPage(self:_:)

CGBitmapContext and CGPDFContext calls are glommed on to
CGContext. This means that you can walk up to any CGContext and ask
for its width, or tell it to begin a PDF page. If you ask a non-bitmap context
for its width, you’ll get this runtime
error:

<Error>: CGBitmapContextGetWidth: invalid context 0x100e6c3c0.
If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.

So even though this API is a whole lot Swiftier, the compiler can’t
catch some kinds of API misuse. Xcode will happily offer completions
for calls that aren’t actually applicable. In a sense the C API was a
bit safer, because CGBitmapContextGetWidth sticks it in your
face that that it expects a bitmap context even though the first
argument is technically just a plain old CGContextRef. I’m hoping
this is just a bug
(rdar://27626070).

If you want to see more about things like the Great Renaming and tools
like NS_SWIFT_NAME, check out WWDC 2016 Session
403
– iOS
API Design Guidelines.

(Thanks to UltraNerd Zachary
Waldowski

for insight in the dark corners of Swift and how the compiler works.)

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project