3

I'm populating the cells of a UITableView using the following code:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! as UITableViewCell
    var someString:String
    cell.textLabel?.text = someString
    return cell
}

Playing around some more, I found I can accomplish the same task with this slightly different code:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as UITableViewCell!
    var someString:String
    cell?.textLabel?.text = someString
    return cell!
}

Xcode compiles and executes both pieces of code just fine. As a Swift newbie, it's been difficult absorbing optionals, and this kind of example just confuses me more. Can someone explain what's going on here? Is the code equivalent? And is one optional style more desirable than the other?

steff
  • 108
  • 1
  • 10

3 Answers3

4

They are similar but they are not the same.

What as does is to let the compiler know that you want to use an object as another.

In your example, tableView.dequeueReusableCell returns UITableViewCell?, which is an optional that can contain either nothing or UITableViewCell.

Optionals can be unwrapped in a few ways, either with if/let:

if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") {
  // cell is a UITableViewCell here, not an optional
}
// cell doesn't exist here anymore

Using a guard

guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") else {
// cell is nil so it's not even created. We HAVE to exit here
return 
}
// cell is a cell is UITableView here, no optional

Or by forcing it, which is not safe and can cause your app to crash. (Personally I avoid these as much as I can, and only use them with IBOutlets)

let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
// cell will be UITableViewCell!, which is an optional that will
// crash if you try to use it and it's nil

Now back to your question, you have a UITableViewCell?, which is a regular optional.

In your first example, you are force-unwrapping it first with !, and then casting it to UITableViewCell. This will result in a UITableViewCell that is actually a force-unwrapped-optional, which will crash your app if it's nil and you try to use it.

In your second example, you are telling the compiler you want to use UITableViewCell? as UITableViewCell!, which will result in the same scenario.

In neither example you need to use ? or ! when using cell, since it's already force-unwrapped.

I would, however, suggest you use a guard to avoid using force-unwrapped-optionals. This is how I handle custom cells:

guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? CustomCell else {
  fatalError("CustomCell is not in the table view")
  // Crash with a custom message instead of a generic one
}

Note that I'm using tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath), this method doesn't return an optional, so if you want to use a cell that doesn't have a custom class, you can use this and don't have to worry about unwrapping.

EmilioPelaez
  • 18,758
  • 6
  • 46
  • 50
3

Ok let's start splitting by parts your question:

dequeueReusableCell(withIdentifier:)

This method handles the reuse of the cells inside the table view. If no cell is available for reuse and you did not register a class or nib file, this method returns nil.

In your first case, you're unwrapping the optional before make the cast:

let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! as UITableViewCell

So then you can call:

cell.textLabel?.text = someString

Without the need to unwrapping the value for the optional cell returned for the method dequeueReusableCell(withIdentifier:).

In your second case, you're not making a force-unwrapping of the value of the function dequeueReusableCell(withIdentifier:).

So then you need to refer to the cell using the chaining optional or force-unwrapping the optional(chaining optional it's the recommended way avoiding runtime exception if the optional is nil) as you did before.

But you can avoid all that just calling:

let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! UITableViewCell

Of course, you need to be sure that the casting should work(for custom cells I mean). So at the end, you're playing with optionals and you're upcasting and downcasting using the as operator.

You can read more about the differences of upcasting and downcasting What's the difference between "as?", "as!", and "as"?

I strongly recommend you read about the UITableView Programming Guider for iOS, you can learn a lot from there.

I hope this help you.

Community
  • 1
  • 1
Victor Sigler
  • 23,243
  • 14
  • 88
  • 105
2

You are into implicitly unwrapping vs. forced unwrapping an optional. You define an Optional as implicitly unwrapped when you define it's type as

let x: String!

It tells the compiler to automatically unwrap that value, as if it's not an optional at all. Another example could be:

@IBOutlet weak var someLabel: UILabel!

This is common if you define outlets as implicitly unwrapped optionals as it will be instantiated by Interface Builder. However, if you forget to connect this outlet to the Interface Builder, you'll get a runtime error.

For forced unwrapping, it consists adding a ! after your optional value. This means the compiler will unwrap it without checking if it's a nil or not at all.

Unlike implicitly unwrapping, forced unwrapping is used on existing value i.e. you are sure that there will be a certain value and it won't return a nil at all.

A general suggestion, never use ! except for IBOutlets . You may use if let or guard let all over your code. Don't ever think that it's never going to return a nil value, because you never know!

amagain
  • 2,042
  • 2
  • 20
  • 36