Is force cast really bad and should always avoid it?
Asked Answered
P

7

52

I started to use swiftLint and noticed one of the best practices for Swift is to avoid force cast. However I used it a lot when handling tableView, collectionView for cells :

let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as! MyOffersViewCell

If this is not the best practice, what's the right way to handle this? I guess I can use if let with as?, but does that mean for else condition I will need to return an empty cell? Is that acceptable?

if let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as? MyOffersViewCell {
      // code
} else {
      // code
}
Polanco answered 16/3, 2016 at 0:5 Comment(2)
I'd say using force unwrapping is acceptable. As long as you know what you're doing. But in your particular situation, using optional unwrapping will be better. You can check if the cell returned by dequeueReusableCellWithReuseIdentifier is the type of MyOffersViewCell. If so, do whatever you want, if not then just return the UITableViewCell, no problem with it.Lanna
Except that it's not UITableViewCell but a UICollectionViewCell which will crash if default initializer is used UICollectionViewCell()..Alegar
C
64

This question is probably opinion based, so take my answer with a grain of salt, but I wouldn't say that force downcast is always bad; you just need to consider the semantics and how that applies in a given situation.

as! SomeClass is a contract, it basically says "I guarantee that this thing is an instance of SomeClass". If it turns out that it isn't SomeClass then an exception will be thrown because you violated the contract.

You need to consider the context in which you are using this contract and what appropriate action you could take if you didn't use the force downcast.

In the example you give, if dequeueReusableCellWithIdentifier doesn't give you a MyOffersViewCell then you have probably misconfigured something to do with the cell reuse identifier and an exception will help you find that issue.

If you used a conditional downcast then you are going to get nil and have to handle that somehow - Log a message? Throw an exception? It certainly represents an unrecoverable error and something that you want to find during development; you wouldn't expect to have to handle this after release. Your code isn't going to suddenly start returning different types of cells. If you just let the code crash on the force downcast it will point straight to the line where the issue occurred.

Now, consider a case where you are accessing some JSON retrieved from a web service. There could be a change in the web service that is beyond your control so handling this more gracefully might be nice. Your app may not be able to function but at least you can show an alert rather than simply crashing:

BAD - Crashes if JSON isn't an array

 let someArray=myJSON as! NSArray 
 ...

Better - Handle invalid JSON with an alert

guard let someArray=myJSON as? NSArray else {
    // Display a UIAlertController telling the user to check for an updated app..
    return
}
Corene answered 16/3, 2016 at 0:29 Comment(1)
If you're using SwiftLint and simply want to silence the violation: let someArray = (myJSON as? NSArray)!Snowflake
P
25

Update

After using Swiftlint for a while, I am now a total convert to the Zero Force-Unwrapping Cult (in line with @Kevin's comment below).

There really isn't any situation where you need to force-unwrap an optional that you can't use if let..., guard let... else, or switch... case let... instead.

So, nowadays I would do this:

for media in mediaArray {
    if let song = media as? Song {
        // use Song class's methods and properties on song...

    } else if let movie = media as? Movie {
        // use Movie class's methods and properties on movie...
    }
}

...or, if you prefer the elegance and safety of an exhaustive switch statement over a bug-prone chain of if/elses, then:

switch media {
case let song as Song:
    // use Song class's methods and properties on song...
case let movie as Movie:    
    // use Movie class's methods and properties on movie...
default:
    // Deal with any other type as you see fit...
}

...or better, use flatMap() to turn mediaArray into two (possibly empty) typed arrays of types [Song] and [Movie] respectively. But that is outside the scope of the question (force-unwrap)...

Additionally, I won't force unwrap even when dequeuing table view cells. If the dequeued cell cannot be cast to the appropriate UITableViewCell subclass, that means there is something wrong with my storyboards, so it's not some runtime condition I can recover from (rather, a develop-time error that must be detected and fixed) so I bail with fatalError().


Original Answer (for the record)

In addition to Paulw11's answer, this pattern is completely valid, safe and useful sometimes:

if myObject is String {
   let myString = myObject as! String
}

Consider the example given by Apple: an array of Media instances, that can contain either Song or Movie objects (both subclasses of Media):

let mediaArray = [Media]()

// (populate...)

for media in mediaArray {
   if media is Song {
       let song = media as! Song
       // use Song class's methods and properties on song...
   }
   else if media is Movie {
       let movie = media as! Movie
       // use Movie class's methods and properties on movie...
   }
Pyroxenite answered 16/3, 2016 at 0:50 Comment(4)
I would much prefer an if let unwrapping instead of force casting there. if let myString = myObject as? String or if let song = media as? Song {} else if let movie = media as? Movie. While that pattern is safe, the optional unwrapping can be done without force-unwrappingCypress
Sure. I guess it's a matter of style/preference, and in fact that's what I end up doing all the time (I'm a huge if/let - guard/let/else fan). I just recalled this very example from some Apple documentation...Pyroxenite
I actually wish the Swift compiler has an option to prevent force cast/try so that I don't have to install a separate linter to enforce this.Creamcups
I don't agree, you are burying potential programmer issues because you don't allow force unwrapping. URL(string: "https://example.com")! is fine because it will always work as the string is a valid hard-coded URL. By doing a guard you are ignoring potential programmer mistakes. You want this code to crash if someone write something like URL(string: "https://example,com")! (notice the comma). That's what tests are suppsoed to catch. By doing a safe unwrap you are basically making an unnecessary muck of your code and your users will feel the pain.Entrechat
S
7

Others have written about a more general case, but I want to give my solution to this exact case:

guard let cell = tableView.dequeueReusableCell(
    withIdentifier: PropertyTableViewCell.reuseIdentifier,
    for: indexPath) as? PropertyTableViewCell
else {
    fatalError("DequeueReusableCell failed while casting")
}

Basically, wrap it around a guard statement and cast it optionally with as?.

Scalariform answered 20/7, 2018 at 23:30 Comment(2)
I would argue this is slightly less useful than the force downcast. You still get a crash but instead of the exception telling you something like "OtherCellClass cannot be downcast to PropertyTableViewCell", which tells you exactly what happened, you get "DequeueResuableCell failed while casting" and then you need to go and investigate whyCorene
@Corene I agree, there is no point using fatalError. You could use assertionFailure() instead and return empty cell; that way your production build is safe in case you accidentally changed identifier string or something.Shingles
U
6

"Force Cast" has its place, when you know that what you're casting to is of that type for example.

Say we know that myView has a subview that is a UILabel with the tag 1, we can go ahead and force down cast from UIView to UILabel safety:

myLabel = myView.viewWithTag(1) as! UILabel

Alternatively, the safer option is to use a guard.

guard let myLabel = myView.viewWithTag(1) as? UILabel else {
  ... //ABORT MISSION
}

The latter is safer as it obviously handles any bad cases but the former, is easier. So really it comes down to personal preference, considering whether its something that might be changed in the future or if you're not certain whether what you are unwrapping will be what you want to cast it to then in that situation a guard would always be the right choice.

To summarise: If you know exactly what it will be then you can force cast otherwise if theres the slightest chance it might be something else use a guard

Uticas answered 16/3, 2016 at 0:46 Comment(0)
Y
5

As described in some casting discussions, forcing the cast for tableView.dequeueReusableCell is ok and can/should be done.

As answered on the Swiftlint Github site you can use a simple way to turn it off for the table cell forced cast.

Link to Swiftlink issue 145

// swiftlint:disable force_cast
let cell = tableView.dequeueReusableCell(withIdentifier: "cellOnOff", for: indexPath) as! SettingsCellOnOff
// swiftlint:enable force_cast
Yves answered 30/9, 2020 at 9:14 Comment(0)
F
0

When you are working with your types and are sure that they have an expected type and always have values, it should force cast. If your apps crash you can easily find out you have a mistake on which part of UI, Dequeuing Cell, ...

But when you are going to cast types that you don't know that is it always the same type? Or is that always have value? You should avoid force unwrap

Like JSON that comes from a server that you aren't sure what type is that or one of that keys have value or not

Sorry for my bad English I’m trying to improve myself

Good luck🤞🏻

Fielding answered 21/7, 2018 at 2:37 Comment(0)
T
-3

In cases where you are really sure the object should be of the specified type it would be OK to down cast. However, I use the following global function in those cases to get a more meaningful result in the logs which is in my eyes a better approach:

public func castSafely<T>(_ object: Any, expectedType: T.Type) -> T {
    guard let typedObject = object as? T else {
        fatalError("Expected object: \(object) to be of type: \(expectedType)")
    }
    return typedObject
}

Example usage:

class AnalysisViewController: UIViewController {

    var analysisView: AnalysisView {
        return castSafely(self.view, expectedType: AnalysisView.self)
    }

    override func loadView() {
        view = AnalysisView()
    }
}
Transmittal answered 16/12, 2017 at 11:29 Comment(3)
Is this example really production-safe? I think it is better to give a fallback something instead of purposely crashing the app with fatalError().Onomastic
A force cast also crashes, the idea is to provide a more meaningful error instead of a generic bad instruction error.Transmittal
Yeah but your method is called castSafely and you crash the app? The name of the method threw me off...Onomastic

© 2022 - 2024 — McMap. All rights reserved.