Reputation: 7210
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:
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?mailComposeViewController == nil
since it's not optional.Upvotes: 5
Views: 2139
Reputation: 32782
Another approach would be to declare a function similar to this:
@inline(never) func isNil<T>(value: T?) -> Bool {
value == nil
}
Without the @inline(never)
you are at risk in Release builds, as the compiler can choose to inline the function body, and afterwards see that value == nil
can, theoretically, never be true, thus also removing the check, making the production code behave like you would've never written the code for checking against nils.
Upvotes: 0
Reputation: 534893
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).
Upvotes: 6
Reputation: 64002
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.)
Upvotes: 1
Reputation: 15512
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.
Upvotes: 0