Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly check if non-Optional return value is valid?

Tags:

swift

I've run into an odd case when trying to check a return value, and I'm wondering how to do this "properly", in the Swift sense.

I have an NSStatusItem (named item), and I'm trying to assign the NSStatusItem an NSImage. When I make the NSImage, since I pass it a string value for the image name, I want to make sure that the NSImage is actually valid (what if I mistype the image name string?).

The first thing I tried was this:

if let image: NSImage? = NSImage(named: "CorrectIconName") {
    item.image = image
}

But this gives the error "Bound value in a conditional binding must be of Optional type". I thought that saying image: NSImage? made it clear that it was Optional, but I guess not.

I changed that to this:

let image: NSImage? = NSImage(named: "CorrectIconName")
if image {
     item.image = image
}

Which works totally fine. But I don't get why this works, while the first example doesn't. It seems more-or-less to be the exact same thing. And since the first one didn't compile, I thought I'd try some other routes...

Since NSImage(named:) does return an NSImage and not an NSImage?, I thought I'd see what happened if I assigned the return value of the constructor directly to item:

item.image = NSImage(named: "CorrectIconName")

Which works, but doesn't allow for the error checking I want to do. If the string is wrong, the NSStatusItem gets nil for an image, which leads to me having an invisible status bar item.

Next, I tried this:

let image: NSImage = NSImage(named: "CorrectIconName")
if image {
    item.image = image
}

But this gives the error "Type 'NSImage' does not confirm to protocol 'LogicValue'", which I guess means you aren't allowed to check if it's nil or not with an if statement.

However, you can check whether it is nil by doing the following:

let image: NSImage = NSImage(named: "CorrectIconName")
if image != nil {
    item.image = image
}

So, here's the question: how exactly is one supposed to check a return value if it isn't Optional?

like image 716
nickjwallin Avatar asked Jun 26 '14 20:06

nickjwallin


People also ask

How can I tell if an optional value is nil Swift?

You can use if statement and compare optional with nil to find out whether a optional contains a value or not. You can use the comparison operator "equal to" operator ( == ) or the "not equal to" operator ( != ) in the if statement.

How do you check if a variable has a value in Swift?

You can use if let. if let is a special structure in Swift that allows you to check if an Optional holds a value, and in case it does – do something with the unwrapped value. But for Strings you can also use . isEmpty() If you have initialized it to "" .


2 Answers

It actually is Optional, the compiler just isn't showing it to you.

In Apple's documentation about working with Objective-C objects, it says that all objects imported from Objective-C APIs are actually implicitly unwrapped optionals (like we would manually declare with !):

In some cases, you might be absolutely certain that an Objective-C method or property never returns a nil object reference. To make objects in this special scenario more convenient to work with, Swift imports object types as implicitly unwrapped optionals. Implicitly unwrapped optional types include all of the safety features of optional types. In addition, you can access the value directly without checking for nil or unwrapping it yourself. [source]

Unfortunately, the compiler/syntax checker doesn't treat them as such. Therefore, the correct way of checking would be to declare image as the type that the NSImage initializer is actually returning, an implicitly unwrapped optional NSImage:

let image: NSImage! = NSImage(named: "CorrectIconName")
if image {
    // do something
}

Alternate method (via @vacawama):

if let image = NSImage(named: "CorrectIconName") as NSImage! {
    // do something
}
like image 116
Nate Cook Avatar answered Sep 19 '22 14:09

Nate Cook


There is a bit of a mismatch between Objective-C initializers which may return nil and Swift's init semantics. If you invoke SomeObject(...) it's defined by the language to create an instance of SomeObject NOT SomeObject?

There are a few examples of initializers in the Cocoa/Foundation frameworks that, from a Swift perspective, generate an NSSomething? not the implied NSSomething

There are myriad way for Apple to address this without compromising the Swift type system, but until they do, there are several ways for you to "Do the Right Thing."

Re-wrap the object in a conditional - this works but is clunky:

var maybeImage: NSImage? = NSImage(named: "CorrectIconName")
if let image = maybeImage {

}

Better is to make your own function for loading NSImage

func loadImageNamed(name: String) -> NSImage? {
    return NSImage(named: name)
}

func doSomethingWithAnImage() {
    if let image = loadImageNamed( "CorrectIconName") {
        ....
    }
}

Alternatively you can extend NSImage.

extension NSImage {
    class func imageWithName(name: String) -> NSImage? {
        return NSImage(named: name)
    }
}

Now you can do the natural thing.

if let image = NSImage.imageWithName("CorrectIconName") {
}

This seems to be the right thing for the Swift/Cocoa API and I hope Apple does something similar in the future.

like image 34
Bruce1q Avatar answered Sep 22 '22 14:09

Bruce1q