简体   繁体   中英

Best way to check non-optional values for nil in Swift

In Swift, it's rare but possible to end up with a value of a non-optional type that has a nil value. As explained in answers to this question , this can be caused by bad Objective-C code bridged to Swift:

- (NSObject * _Nonnull)someObject {
    return nil;
}

Or by bad Swift code:

class C {}
let x: C? = nil
let y: C = unsafeBitCast(x, to: C.self)

In practice, I've run into this with the MFMailComposeViewController API in MessageUI . The following creates a non-optional MFMailComposeViewController , but if the user has not set up an email account in Mail, the following code crashes with EXC_BAD_ACCESS :

let mailComposeViewController = MFMailComposeViewController()
print("\(mailComposeViewController)")

The debugger shows the value of mailComposeViewController like this:

mailComposeViewController = (MFMailComposeViewController) 0x0000000000000000

I have a couple of questions here:

  1. I note that the documentation for unsafeBitCast(_:to:) says it "breaks the guarantees of the Swift type system," but is there a place in Swift documentation that explains that these guarantees can be broken, and how/when?
  2. What's the best, most idiomatic way to check for this case? The compiler won't let me check whether mailComposeViewController == nil since it's not optional.

Even Apple's APIs sometimes return nil for a type that is not marked in the API as Optional. The solution is to assign to an Optional.

For example, for a while traitCollectionDidChange returned a UITraitCollection even though it could in fact be nil . You couldn't check it for nil because Swift won't let you check a non-Optional for nil .

The workaround was to assign the returned value immediately to a UITraitCollection? and check that for nil . That sort of thing should work for whatever your use case is as well (though your mail example is not a use case, because you're doing it wrong from the get-go).

I have same issue but I use suggestion from the Apple docs.

From the documentation:

Before presenting the mail compose view controller, always call the the canSendMail() method to see if the current device is configured to send email. If the user's device is not set up for the delivery of email, you can notify the user or simply disable the email dispatch features in your application. You should not attempt to use this interface if the canSendMail() method returns false.

if !MFMailComposeViewController.canSendMail() {
    print("Mail services are not available")
    return
}

Considering usage of the unsafeBitCast(_:to:) .

The main problem is that in this method we cast a pointer to a value from Swift without any validation if that pointer can conform to the type we are expecting, and from my point of view it is quite dangerous because it will produce crash.

There is probably a better way to do this with actual Swift pointer types, but one option might be to just peek at the address:

func isNull(_ obj: AnyObject) -> Bool {
    let address = unsafeBitCast(obj, to: Int.self)
    return address == 0x0
}

( Int is documented as machine word sized.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM