2

The following Swift 3 code crashes. The crash could be easily solved by either removing explicit optional type or by force unwrapping view. Could anyone explain why this code crashes?

let view: UIView? = UIView() // note the explicit *optional* type
_ = NSLayoutConstraint(item: view, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0.0, constant: 44.0)

note: it won't compile with Swift 2.3 or lower

Alexander Vasenin
  • 11,437
  • 4
  • 42
  • 70
  • 2
    This code doesn't crash. It won't compile. – Rob Aug 25 '16 at 01:37
  • 1
    Ah, I see you're doing this in Swift 3. In Swift 2 the first parameter was `AnyObject` (but not `AnyObject?`, like `item2` is) and would correctly warn you if you passed an optional. It's now `Any` (not `Any?` like `item2`). The compiler is clearly not warning us about the fact that the first parameter should not be optional. But it can't be optional, and clearly doesn't like it when you do try to pass an optional. – Rob Aug 25 '16 at 01:47
  • yes, I meant Swift 3 (Xcode 8). added note to the question – Alexander Vasenin Aug 25 '16 at 01:50
  • @Rob Do you know any docs which could explain how optionals are actually implemented? I used to think an optional is just a compile time check, like `__nullable` in ObjC, but it looks it's not. – Alexander Vasenin Aug 25 '16 at 01:54
  • 1
    @AlexanderVasenin http://stackoverflow.com/questions/24548475 – spruceb Aug 25 '16 at 02:10

2 Answers2

4

NSLayoutConstraint(item:, attribute:, relatedBy:, toItem:, attribute:, multiplier:, constant:) has an item parameter typed as Any:

public convenience init(item view1: Any, attribute attr1: NSLayoutAttribute, relatedBy relation: NSLayoutRelation, toItem view2: Any?, attribute attr2: NSLayoutAttribute, multiplier: CGFloat, constant c: CGFloat)

But from the crash you can glean that the parameter can really only accept UIView or UILayoutGuide:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSLayoutConstraint for Optional(UIView: 0x7fa0fbd06650; frame = (0 0; 0 0); layer = CALayer: 0x60800003bb60): Constraint items must each be an instance of UIView, or UILayoutGuide.'

The compiler can't check for the type of item during compile time. It is defined to accept anything. But in the implementation details that are inaccessible to us, that method accepts only non-optional UIViews or UILayoutGuides.

So just add a guard statement:

let view: UIView? = UIView()
guard let view = view else { // Proceed only if unwrapped
  fatalError()
}
let _ = NSLayoutConstraint(item: view, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0.0, constant: 44.0)
Ben Morrow
  • 832
  • 6
  • 17
3

The reason why it crashes is UIView and UIView? are completely different types. UIView is Objective-C class, while UIView? is an Swift enumeration which can contain UIView. On the contrary, in Objective-C nullable is just a hint for compiler.

Alexander Vasenin
  • 11,437
  • 4
  • 42
  • 70