Home > other >  Why does randomElement() return ?? and not?
Why does randomElement() return ?? and not?

Time:05-22

I'm learning Swift and some iOS development. Here is my code which I'm trying to figure out why XCode is complaining.

private func rollMyDice() {
    let diceRollImages = [
        UIImage(named: "DiceOne"),
        UIImage(named: "DiceTwo"),
        UIImage(named: "DiceThree"),
        UIImage(named: "DiceFour"),
        UIImage(named: "DiceFive"),
        UIImage(named: "DiceSix")
    ]

    diceOneImageView.image = diceRollImages.randomElement()
    diceTwoImageView.image = diceRollImages.randomElement()
}

So in this case, diceOneImageView.image = diceRollImages.randomElement() will complain that it cannot assign UIImage?? to UIImage?.

ImageView.image is of type UIImage?. What I don't understand is why is randomElement() here is returning UIImage??.

What does UIImage?? mean? I thought ?? was the nil coalescsing operator, so I'm not sure why it's part of some return type.

Also reading the documentation on randomElement(), it should return ?. So in this case, I would expect diceRollImages.randomElement() to return UIImage? which should suit diceOneImageView.image.

What is happening here? I know I can fix it by using ! or using nil coalescing etc. to make it work. Just don't get what's going on.

CodePudding user response:

Note that UIImage(named:) is a failable initialiser, and so the expressions UIImage(named: "DiceOne") etc are of type UIImage?. This makes the array diceRollImages of type [UIImage?]. Each Element of the array is UIImage?, alternatively written as Optional<UIImage>.

As you may know, Optional is just an enum with two cases, .some and .none (aka nil). Each element of the array can be:

  • .some(image), if a UIImage is created successfully from the name
  • .none, if there is no images with that name

randomElement is declared to return Element? (Optional<Element>), because the array could have no elements, and hence, cannot give you a random element. randomElement returns:

  • .none, when the array is empty
  • .some(elementOfTheArray), when the array is non-empty and elementOfTheArray is a random element in array.

Recall that Element is Optional<UIImage> in the case of diceRollImages, and that the array elements (elementOfTheArray) could have values .some(image) or .none.

Therefore, we can say that randomElement returns a value of type Optional<Optional<UIImage>>, aka UIImage??, and it could be one of 3 things:

  • .none when the array is empty
  • .some(.some(image)) when the array is non-empty and a successfully-created image in the array is randomly selected
  • .some(.none) when the array is non-empty and a unsuccessfully-created image in the array is random selected

Since you are hardcoding the array of images, you know that the array is not empty, and so it is safe to force-unwrap the outer layer of the optional:

diceOneImageView.image = diceRollImages.randomElement()!

CodePudding user response:

The answer has been given already in the comments: UIImage(named:) returns (optional) UIImage? and the result of calling randomElement() on an optional is a double optional ??

This is a good example where force unwrapping is welcome.

The images are part of the application bundle which is immutable at runtime and the app is useless if one of them is missing.

Declare diceRollImages

let diceRollImages = [
    UIImage(named: "DiceOne")!,
    UIImage(named: "DiceTwo")!,
    UIImage(named: "DiceThree")!,
    UIImage(named: "DiceFour")!,
    UIImage(named: "DiceFive")!,
    UIImage(named: "DiceSix")!
]

If the code crashes nevertheless it reveals a design mistake which can be fixed immediately.

You can even force unwrap randomElement()! because the array is a constant and is clearly not empty.

  • Related