It’s always exciting to live through a period of exploration and evolution, isn’t it? A bit too exciting at times, you might think. Here’s a collection of subtleties you might run into out in the unexplored regions of the new ecosystem, particularly on the Objective-C borderlands where the ground is treacherous and monsters roam:
Today, we received a report for a very weird crash with a stack trace that contained only UIKit symbols, but was clearly triggered by a specific action in PSPDFKit…
… That was it. These seemingly innocent extensions were overriding private API. Apple’s private API detection is not super sophisticated and wasn’t triggered when the app was uploaded to the App Store. It’s also not a public symbol so there were no warnings, not even a log message. Unprefixed categories are always dangerous, especially on classes that you do not own, like UIViewController. In PSPDFKit, we use categories for shared code, but prefix any method with pspdf_ to be absolutely sure we do not hit any name clashes. It’s certainly not pretty, and prefixes in Swift look even more alien, yet as you can see in this bug hunt, they are definitely necessary.
The whole story is worth a read if you haven’t run into the overriding private API problem before — especially not of the “crash immediately on launch with the new OS point release” variety, but that’s another story altogether — or you can just cut to the chase with
tl;dr: Swift extensions on Objective-C classes still need to be prefixed. You can use
@objc(prefix_name)to keep the name pretty in Swift and expose a prefixed version for the ObjC runtime.
“When you declare an outlet in Swift, you should make the type of the outlet an implicitly unwrapped optional … When your class is initialized from a storyboard or xib file, you can assume that the outlet has been connected.” — Using Swift with Cocoa and Objective-C, Apple
… Except in practice there are tons of edge cases in the lifecycle of a view controller where this simply isn’t true. And what happens when you try to access emailField when the view isn’t loaded for some reason? The app crashes…
UIKit was written during the era of nil messaging, and I’ve come to realize it isn’t safe to 100% assume IBOutlets can’t be nil. Going forward I’ll be using optionals for my IBOutlets. I have a task in my bug tracker to scrub all my IBOutlets to covert them from implicitly unwrapped to standard optionals. A few extra question marks never hurt anyone; I’d rather my app not crash.
Some more discussion of appropriate outlet semantics in Outlets: Strong! Or Weak?
⚠ Double-check attribute names that override protocol extensions.
⚠ For every attribute defined in a protocol extension, declare it in the protocol itself.
⚠ Do not extend an imported protocol with a new attribute that may need dynamic dispatch.
⚠ Avoid extending a protocol with a restriction if the new attribute might need dynamic dispatch.
⚠ Avoid assigning the result of an expression with side-effects to left-hand-side with optional chaining.
⚠ Avoid in-out parameters in closures.
⚠ Avoid currying with in-out parameters because the code will fail if you later change it to explicitly create a closure.
Be careful out there!
h/t: iOS Dev Weekly!