NSMutableArray much less safe in Swift 3?

In Swift 2.x, the following code would fail to compile:

let anArray = NSMutableArray()
let something = callSomethingThatReturnsAnOptionalObject()
anArray.addObject(something) << error "value of optional type '***?' not unwrapped."


Whereas in Swift 3 the equivalent happily compiles:

let anArray = NSMutableArray()
let something = callSomethingThatReturnsAnOptionalObject()
anArray.add(something)


and in the case where 'something' end up as a nil optional the code will happily continue until some Obj-C code tries to use that 'nil' optional. In my case it was NSCoder trying to encode the array and failing when it hit the offending item.


This seems really dangerous, why was it allowed to happen. Seems like a regression to me?


(I haven't checked, but I suspect other NSMutableXXX containers can suffer the same issue)

Replies

In Swift 2, NSMutableArray stored things of type AnyObject, but in Swift 3 the type is changed to Any. This means that optionals, which are enums, can be stored.


Optionals were bridged to an opaque object that was not accessible in Objective-C, and that is probably still the case, but a proposal has been accepted (SE-0140) that changes this:

Converting an

Optional<T>
to an
Any
should raise a warning unless the conversion is made explicit. When an
Optional<T>
value does end up in an
Any
, and gets bridged to an Objective-C object, if it contains
some
value, that value should be bridged; otherwise,
NSNull
or another sentinel object should be used.


This was accepted a few weeks ago, and I don't know when that will be implemented and shipped in Xcode.

SE0140 is shown as implemented in Swift 3.0.1, so the new warning should be appearing in Xcode 8.1.


I just tried a simple example in 8.1b2, and it did indeed produce the warning.

(I haven't checked, but I suspect other NSMutableXXX containers can suffer the same issue)

Indeed. I generally avoid this issue by working in ‘Swift space’ as much as possible. That way I get strict type checking right up until the last minute. To continue your example, I’d write this:

var anArray: [Foo] = []
let something = callSomethingThatReturnsAnOptionalObject()
anArray.append(something)
coder.encode(anArray as NSArray)

which fails to compile because

append(_:)
won’t accept a
Foo?
.

This doesn’t work all the time (for example, if the array is heterogeneous) but it’s helpful in many cases.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"