But there’s a problem with the implementation. It’s empty. Given the following implementation, a new instance of SomeThing will have a nil scroll view property, in violation of its interface. The compiler does not complain.

@implementationSomeThing@end

The scroll view property, which should be nonnull, or in Swift, not optional, is never given a value on initialization. So what happens when we use if from Swift?

I’ve explicitly added types to these variables to show that they’re not optional. Those explicit types are the types that are inferred by the compiler if they weren’t there. Swift doesn’t think they’re optional, and doesn’t treat them as optional in any way.

What do you think will happen when the program runs?

Absolutely nothing.

Most people expect it would crash. It doesn’t crash. It instantiates a SomeThing into thing. Then thing’s “scroll view” is read and put into scrollView, and exits without any problems at all.

Lines 1-20 show methods that return sized structs, like CGSize, or C primitives like floats, ints, and enums with those types, Swift will return a perfectly valid zero-filled value.

Line 22 shows that calling a method with no return value is perfectly valid.

Line 27 shows that a nonnull return value on the nil scrollView is subject to the same problem at the root of all of this. clipView is a non-optional reference to an NSClipView object. But it’s actually nil.

And line 29, setting a value on the nil clipView, rounds out the typical operations we may want to do on an object. Swift doesn’t complain at all about setting a value on a nil objects. This shouldn’t be surprising since for Objective-C objects, it’s the same as calling a method.

Any Objective-C things we want to do with these objects succeeds, which is nearly everything since they’re Objective-C objects. We’ve entered the territory of undefined behavior. It’s a sort of “Objective-C mode”.

There are things we can do to detect this non-optional nil condition. Indeed, the simplest way to get this to fail is to print the unexpectedly nil object. But maybe we can do better than that.

Detecting nil when nil isn’t possible

Say we want to guard against this. The problem is that since Swift doesn’t think this value can be nil, it’s not trivial to check. Comparing the non-optional value results in a warning: “Non-optional expression of type ‘NSSScrollView’ used in a check for optionals”

We could use a function like the second isNil to detect, assert, and adapt our logic around this unusual case.

Swift Extensions

If you make a Swift extension to the Objective-C class and call them on one of these nil things that aren’t supposed to exist, those methods still get called.

Still working with out unexpectedly nil scroll view instance, we can do something like this:

extensionNSScrollView {func doAThing() { print("doing it") // <- This will get called }}

In those circumstances, you can be inside an instance method of an object and have self be 0x0. And since the type on self is not optional, the silent unexpected nil values can continue to propagate.

Extension methods open up the opportunity for new unexpected behavior though. They execute code instead of running in an unusual “Obective-C” mode where messaging nil returns zero-like values or no-ops. So extension methods can have side-effects like the print statement above. And they can also return non-zero values:

The program crashes on the second ine. This happens because an Objective-C NSCalendar is a Calendar in Swift. But this isn’t just a rename. It’s bridged to the Swift Foundation Calendar type.

What’s happening here isn’t that we crash when getting an unexpected value out of calendarProvider, but that Swift automatically converts any instances of NSCalendar objects with a C or Objective-C implementation with a Calendar object from the Swift Foundation library. The Swift Foundation library’s Calendar has a method _unconditionallyBridgeFromObjectiveC that’s part of the _ObjectiveCBridgeable protocol that converts an Optional<NSCalendar> to a Foundation.Calendar. we can look at the source for Calendar._unconditionallyBridgeFromObjectiveC.

What’s interesting here is that the argument to the bridge function is an Optional<NSCalendar>. The static method, by its signature, accepts nil. What’s happening then? In this case, The culprit for the crash and what saves us from unexpected behavior later on is a force unwrap. Though the value that’s actually passed in to the function is Optional<NSCalendar>.some(nil), which is still not a valid value and we’re still in undefined behavior territory, so it’s pleasantly surprising that a force unwrap catches this case.

Array Properties

Nonnull array properties in Objective-C get bridged to Swift in a very strange way. The following Objective-C class declared a public nonnull NSArray property.

There are at least two very strange things going on in this program. Lines 1 and 2 are uneventful. We can instantiate an OffendingObject, and print its description. Its array property is appropriately is represented by the description Objective-C give nil in a format string, “(null)”.

In line 3, strange things start to happen. print(obj.array) accesses the array property in Swift. That expression should result in nil, which should be a bit of a problem for Swift. Instead, it describes an empty array, as if there were no contractual violation by the OffendingObject at all. If we store the value of obj.array at that point, we do indeed get an empty Array<Any>. It suggests what happens next, which gets really interesting.

We check the object’s property again in line 4 by printing the description of the object. Its array property is still nil.

This situation doesn’t look self-consistent. Under some conditions, Swift will create an Array if it doesn’t find one where it’s expected.

In line 5, we add a string, “thing” to the object’s array. Somewhat surprisingly, that doesn’t crash. And the OffendingObject ends up with an array seemingly conjured out of nowhere that contains a value.

Swift Arrays and Objective-C arrays are funny though. If we set aside where the new empty array comes from for a moment, the rest of the behavior we’ve seen with arrays makes sense. An NSArray can’t be mutated. But swift arrays are different. Semantically, they are value types, not reference types. So changing a var example: Array by adding a new element is the same as creating a new array and assigning it back to the var example container. It’s the variable example that changes, not the Array.

The reason the Swift code to “add to” that array works is because the OffendingObject is compatible with those operations. NSArray isn’t mutable, but the arrayproperty is readwrite. So the code is getting the array in the property, creating a new array with those contents plus a new object, then storing that new array back into the OffendingObject’s property.

This code is doing something very similar to our isNil function above. The if let check on line 2 correctly identifies the nil value, we jump to line 7, where a new empty Array is created and returned.

What do we do now?

Swift is an incredible language. It’s in the top few of my favorite languages, and one of the languages I know best. I delighted in finding this issue and digging into it because I love a good mystery. And I also see this sort of issue as one of the growing pains of any language. Swift is growing up, and it’s going to develop quirks and gotchas, and language features people are strongly advised against using. That’s delightful.

That’s how I felt early in the investigation. My feelings towards the language haven’t changed, but I see this behavior as hugely problematic. Swift is breaking what, in my mind, were the two biggest promises it was making:

The safety afforded by a strong type system

Stellar Objective-C interoperability

The good news is that the Swift team knows about the issue: SR-8622 and SR-120.

The bad news is currently medium priority and has been known since mid-2018, or earlier depending on how you read it.

The other good news is that a reasonable solution has at least been mentioned in SR-8622:

The cost of checking every nonnull return value was determined to be too high, but maybe we could do it in Debug builds.

—Jordan Rose, SR-8622

Having the the compiler automatically check and assert that nonnull Objective-C types returned by Objective-C methods are indeed present would be fantastic, whether for debug builds or as an independent flag. I hope Apple, or some Swift open source contributor can make that happen soon.

In the mean time, I’m not going to be using Swift any less. It’s important to know where the sharp edges are.