swift - libro - ¿La fuerza es realmente mala y siempre se debe evitar?
fahrenheit 451 ray bradbury (6)
"Force Cast" tiene su lugar, cuando sabes que lo que estás lanzando es de ese tipo, por ejemplo.
Digamos que sabemos que myView
tiene una subvista que es una UILabel
con la etiqueta 1
, podemos seguir adelante y forzar el lanzamiento desde UIView
a UILabel
safety:
myLabel = myView.viewWithTag(1) as! UILabel
Alternativamente, la opción más segura es usar un protector.
guard let myLabel = myView.viewWithTag(1) as? UILabel else {
... //ABORT MISSION
}
El último es más seguro ya que obviamente maneja cualquier mal caso, pero el primero es más fácil. Así que realmente se trata de preferencias personales, considerando si es algo que podría cambiarse en el futuro o si no está seguro de si lo que está desenvolviendo será lo que desea lanzar, entonces en esa situación un guardia siempre sería la elección correcta.
Para resumir: si sabe exactamente lo que será, entonces puede forzar el lanzamiento de otra manera si existe la mínima posibilidad de que sea otra cosa usar un guardia.
Comencé a usar swiftLint y noté que una de las mejores prácticas para Swift es evitar el lanzamiento forzado. Sin embargo, lo usé mucho al manejar tableView, collectionView para celdas:
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as! MyOffersViewCell
Si esta no es la mejor práctica, ¿cuál es la forma correcta de manejar esto? Supongo que puedo usar if let with as ?, pero, ¿significa eso para otra condición que necesitaré para devolver una celda vacía? ¿Es eso aceptable?
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as? MyOffersViewCell {
// code
} else {
// code
}
Cuando está trabajando con sus tipos y está seguro de que tienen un tipo esperado y siempre tienen valores, debe forzar el lanzamiento. Si sus aplicaciones se bloquean, puede descubrir fácilmente que tiene un error en qué parte de la interfaz de usuario, la ...
Pero cuando va a emitir tipos que no sabe, ¿es siempre el mismo tipo? ¿O es que siempre tiene valor? Debes evitar la fuerza de desenvolver
Al igual que JSON que proviene de un servidor, no está seguro de qué tipo es eso o una de esas claves tiene valor o no
Perdón por mi mal inglés. Estoy tratando de mejorar.
Buena suerte
En los casos en que esté realmente seguro de que el objeto debe ser del tipo especificado, sería correcto realizar una conversión descendente. Sin embargo, utilizo la siguiente función global en esos casos para obtener un resultado más significativo en los registros, lo que en mi opinión es un mejor enfoque:
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
}
Ejemplo de uso:
class AnalysisViewController: UIViewController {
var analysisView: AnalysisView {
return castSafely(self.view, expectedType: AnalysisView.self)
}
override func loadView() {
view = AnalysisView()
}
}
Esta pregunta es probablemente basada en la opinión, así que tome mi respuesta con un grano de sal, pero no diría que la fuerza hacia abajo siempre es mala; solo necesitas considerar la semántica y cómo se aplica eso en una situación dada.
as! SomeClass
as! SomeClass
es un contrato, básicamente dice "Garantizo que esto es una instancia de SomeClass". Si resulta que no es SomeClass, se lanzará una excepción porque violó el contrato.
Debe considerar el contexto en el que está utilizando este contrato y qué medidas apropiadas podría tomar si no usara la reducción de fuerza.
En el ejemplo que da, si dequeueReusableCellWithIdentifier
no le da un MyOffersViewCell
entonces probablemente haya configurado mal algo con el identificador de reutilización de celda y una excepción lo ayudará a encontrar ese problema.
Si usó un downcast condicional, entonces obtendrá nulo y tendrá que manejarlo de alguna manera. ¿Registrar un mensaje? Lanzar una excepción? Sin duda representa un error irrecuperable y algo que desea encontrar durante el desarrollo; no esperaría tener que manejar esto después del lanzamiento. Tu código no va a comenzar a devolver de repente diferentes tipos de celdas. Si simplemente deja que el código se bloquee en el downcast forzado, apuntará directamente a la línea donde ocurrió el problema.
Ahora, considere un caso en el que está accediendo a algún JSON recuperado de un servicio web. Podría haber un cambio en el servicio web que está fuera de su control, por lo que manejar esto con más gracia podría ser bueno. Es posible que su aplicación no pueda funcionar, pero al menos puede mostrar una alerta en lugar de simplemente bloquearse:
BAD - Se bloquea si JSON no es una matriz
let someArray=myJSON as! NSArray
...
Mejor - Manejar JSON inválido con una alerta
guard let someArray=myJSON as? NSArray else {
// Display a UIAlertController telling the user to check for an updated app..
return
}
Otros han escrito sobre un caso más general, pero quiero dar mi solución a este caso exacto:
guard let cell = tableView.dequeueReusableCell(
withIdentifier: PropertyTableViewCell.reuseIdentifier,
for: indexPath) as? PropertyTableViewCell
else {
fatalError("DequeueReusableCell failed while casting")
}
Básicamente, envuélvelo alrededor de una declaración de guard
y conviértalo opcionalmente con as?
.
Actualizar
Después de usar Swiftlint por un tiempo, ahora soy un converso total al Culto de Desenvolvimiento de Fuerza Cero (en línea con el comentario de @ Kevin a continuación).
Realmente no hay ninguna situación en la que necesite forzar y desempaquetar un opcional que no puede usar if let...
, guard let... else
, o switch... case let...
lugar.
Entonces, hoy en día haría esto:
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...
}
}
... o, si prefiere la elegancia y seguridad de una declaración exhaustiva sobre una cadena propensa a errores de if/else
s, entonces:
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...
}
... o mejor, use flatMap()
para convertir mediaArray
en dos arreglos tipificados (posiblemente vacíos) de tipos [Song]
y [Movie]
respectivamente. Pero eso está fuera del alcance de la pregunta (desenvolver por la fuerza) ...
Además, no forzaré el desenvolvimiento incluso cuando se retiren de la celda de vista de tabla. Si la celda eliminada de la cola no se puede convertir a la subclase UITableViewCell
adecuada, eso significa que hay algo incorrecto en mis guiones gráficos, por lo que no es una condición de tiempo de ejecución de la que pueda recuperarme (más bien, un error de tiempo de desarrollo que debe detectarse y solucionarse), por lo que fatalError()
con fatalError()
.
Respuesta original (para el registro)
Además de la respuesta de Paulw11, este patrón es completamente válido, seguro y útil a veces:
if myObject is String {
let myString = myObject as! String
}
Considere el ejemplo dado por Apple: una matriz de instancias de Media
, que puede contener objetos de Song
o de Movie
(ambas subclases de medios):
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...
}