Un opcional en Swift es un tipo que puede contener un valor o ningún valor. Los opcionales se escriben agregando a ?
a cualquier tipo:
var name: String? = "Bertie"
Los opcionales (junto con los genéricos) son uno de los conceptos Swift más difíciles de entender. Debido a cómo se escriben y usan, es fácil hacerse una idea equivocada de lo que son. Compare la opción anterior para crear una cadena normal:
var name: String = "Bertie" // No "?" after String
Desde la sintaxis, parece que una Cadena opcional es muy similar a una Cadena ordinaria. No es. Una cadena opcional no es una cadena con alguna configuración "opcional" activada. No es una variedad especial de String. Una cadena y una cadena opcional son tipos completamente diferentes.
Esto es lo más importante que debes saber: un opcional es un tipo de contenedor. Una cadena opcional es un contenedor que puede contener una cadena. Un Int opcional es un contenedor que puede contener un Int. Piense en un opcional como una especie de paquete. Antes de abrirlo (o "desenvolverlo" en el idioma de los opcionales) no sabrá si contiene algo o nada.
Puede ver cómo se implementan los opcionales en la Biblioteca estándar de Swift escribiendo "Opcional" en cualquier archivo Swift y haciendo clic en él. Aquí está la parte importante de la definición:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
Opcional es solo uno enum
que puede ser uno de dos casos: .none
o .some
. Si es así .some
, hay un valor asociado que, en el ejemplo anterior, sería el String
"Hola". Un opcional usa Generics para dar un tipo al valor asociado. El tipo de una cadena opcional no es String
, es Optional
, o más precisamente Optional<String>
.
Todo lo que Swift hace con las opciones es mágico para que el código de lectura y escritura sea más fluido. Desafortunadamente, esto oscurece la forma en que realmente funciona. Revisaré algunos de los trucos más tarde.
Nota: Hablaré mucho sobre variables opcionales, pero también está bien crear constantes opcionales. Marco todas las variables con su tipo para facilitar la comprensión de los tipos de tipos que se crean, pero no es necesario que lo haga en su propio código.
Cómo crear opcionales
Para crear un opcional, agregue un ?
después del tipo que desea ajustar. Cualquier tipo puede ser opcional, incluso sus propios tipos personalizados. No puede haber un espacio entre el tipo y el ?
.
var name: String? = "Bob" // Create an optional String that contains "Bob"
var peter: Person? = Person() // An optional "Person" (custom type)
// A class with a String and an optional String property
class Car {
var modelName: String // must exist
var internalName: String? // may or may not exist
}
Usando opcionales
Puede comparar un opcional nil
para ver si tiene un valor:
var name: String? = "Bob"
name = nil // Set name to nil, the absence of a value
if name != nil {
print("There is a name")
}
if name == nil { // Could also use an "else"
print("Name has no value")
}
Esto es un poco confuso. Implica que un opcional es una cosa u otra. Es nulo o es "Bob". Esto no es cierto, lo opcional no se transforma en otra cosa. Compararlo con cero es un truco para hacer que el código sea más fácil de leer. Si un opcional es igual a cero, esto solo significa que la enumeración está configurada actualmente en.none
.
Solo los opcionales pueden ser nulos
Si intenta establecer una variable no opcional en nula, obtendrá un error.
var red: String = "Red"
red = nil // error: nil cannot be assigned to type 'String'
Otra forma de ver las opciones es como complemento de las variables normales de Swift. Son una contraparte de una variable que tiene un valor garantizado. Swift es un lenguaje cuidadoso que odia la ambigüedad. La mayoría de las variables se definen como no opcionales, pero a veces esto no es posible. Por ejemplo, imagine un controlador de vista que carga una imagen desde un caché o desde la red. Puede o no tener esa imagen en el momento en que se crea el controlador de vista. No hay forma de garantizar el valor de la variable de imagen. En este caso, deberías hacerlo opcional. Comienza comonil
y cuando se recupera la imagen, lo opcional obtiene un valor.
El uso de un opcional revela la intención de los programadores. En comparación con Objective-C, donde cualquier objeto podría ser nulo, Swift necesita que tenga claro cuándo puede faltar un valor y cuándo se garantiza que exista.
Para usar un opcional, lo "desenvuelves"
Un opcional String
no se puede utilizar en lugar de un real String
. Para usar el valor envuelto dentro de un opcional, debe desenvolverlo. La forma más sencilla de desenvolver un opcional es agregar un !
después del nombre opcional. Esto se llama "desenvolvimiento forzado". Devuelve el valor dentro del opcional (como el tipo original) pero si el opcional es nil
, provoca un bloqueo en tiempo de ejecución. Antes de desenvolver, debe asegurarse de que haya un valor.
var name: String? = "Bob"
let unwrappedName: String = name!
print("Unwrapped name: \(unwrappedName)")
name = nil
let nilName: String = name! // Runtime crash. Unexpected nil.
Comprobación y uso de un opcional
Debido a que siempre debe verificar cero antes de desenvolver y usar un opcional, este es un patrón común:
var mealPreference: String? = "Vegetarian"
if mealPreference != nil {
let unwrappedMealPreference: String = mealPreference!
print("Meal: \(unwrappedMealPreference)") // or do something useful
}
En este patrón, verifica que hay un valor presente, luego, cuando está seguro de que lo está, fuerza a desenvolverlo en una constante temporal para usar. Debido a que esto es algo muy común, Swift ofrece un acceso directo usando "if let". Esto se llama "enlace opcional".
var mealPreference: String? = "Vegetarian"
if let unwrappedMealPreference: String = mealPreference {
print("Meal: \(unwrappedMealPreference)")
}
Esto crea una constante temporal (o variable si la reemplaza let
con var
) cuyo alcance solo está dentro de las llaves del if. Debido a que tener que usar un nombre como "unwrappedMealPreference" o "realMealPreference" es una carga, Swift le permite reutilizar el nombre de la variable original, creando uno temporal dentro del alcance del paréntesis
var mealPreference: String? = "Vegetarian"
if let mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // separate from the other mealPreference
}
Aquí hay un código para demostrar que se usa una variable diferente:
var mealPreference: String? = "Vegetarian"
if var mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // mealPreference is a String, not a String?
mealPreference = "Beef" // No effect on original
}
// This is the original mealPreference
print("Meal: \(mealPreference)") // Prints "Meal: Optional("Vegetarian")"
El enlace opcional funciona comprobando si el opcional es igual a cero. Si no lo hace, desenvuelve lo opcional en la constante proporcionada y ejecuta el bloque. En Xcode 8.3 y versiones posteriores (Swift 3.1), intentar imprimir una opción como esta provocará una advertencia inútil. Usa los opcionales debugDescription
para silenciarlo:
print("\(mealPreference.debugDescription)")
¿Para qué son opcionales?
Los opcionales tienen dos casos de uso:
- Cosas que pueden fallar (esperaba algo pero no obtuve nada)
- Cosas que ahora no son nada pero que podrían ser algo más tarde (y viceversa)
Algunos ejemplos concretos:
- Una propiedad que puede estar allí o no, como
middleName
o spouse
en una Person
clase
- Un método que puede devolver un valor o nada, como buscar una coincidencia en una matriz
- Un método que puede devolver un resultado u obtener un error y no devolver nada, como intentar leer el contenido de un archivo (que normalmente devuelve los datos del archivo) pero el archivo no existe
- Delegar propiedades, que no siempre tienen que establecerse y generalmente se establecen después de la inicialización
- Para
weak
propiedades en clases. Lo que señalan se puede configurar nil
en cualquier momento
- Un gran recurso que podría tener que liberarse para recuperar memoria
- Cuando necesita una forma de saber cuándo se ha establecido un valor (datos aún no cargados> los datos) en lugar de usar un dataLoaded separado
Boolean
Las opciones no existen en Objective-C pero hay un concepto equivalente, que devuelve nil. Los métodos que pueden devolver un objeto pueden devolver nil en su lugar. Esto se toma como "la ausencia de un objeto válido" y a menudo se usa para decir que algo salió mal. Solo funciona con objetos Objective-C, no con primitivas o tipos C básicos (enumeraciones, estructuras). Objective-C a menudo se había especializado tipos para representar la ausencia de estos valores ( NSNotFound
que en realidad es NSIntegerMax
, kCLLocationCoordinate2DInvalid
para representar un inválido de coordenadas, -1
o también se utilizan algún valor negativo). El codificador debe conocer estos valores especiales, por lo que deben documentarse y aprenderse para cada caso. Si un método no puede tomarse nil
como parámetro, esto debe documentarse. En el objetivo-C,nil
era un puntero al igual que todos los objetos se definían como punteros, pero nil
apuntaban a una dirección específica (cero). En Swift, nil
es un literal que significa la ausencia de cierto tipo.
Comparado a nil
Solía poder usar cualquier opcional como Boolean
:
let leatherTrim: CarExtras? = nil
if leatherTrim {
price = price + 1000
}
En las versiones más recientes de Swift tienes que usar leatherTrim != nil
. ¿Por qué es esto? El problema es que un Boolean
puede ser envuelto en un opcional. Si tienes Boolean
así:
var ambiguous: Boolean? = false
tiene dos tipos de "falso", uno donde no hay valor y otro donde tiene un valor pero el valor es false
. Swift odia la ambigüedad, por lo que ahora siempre debe verificar una opción opcional nil
.
¿Te preguntarás cuál es el objetivo de un opcional Boolean
? Al igual que con otras opciones, el .none
estado podría indicar que el valor aún se desconoce. Puede haber algo en el otro extremo de una llamada de red que demore un poco en sondear. Los booleanos opcionales también se denominan " booleanos de tres valores "
Trucos rápidos
Swift utiliza algunos trucos para permitir que funcionen los opcionales. Considere estas tres líneas de código opcional de aspecto ordinario;
var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }
Ninguna de estas líneas debe compilarse.
- La primera línea establece una cadena opcional utilizando un literal de cadena, dos tipos diferentes. Incluso si esto fuera un
String
tipo diferente
- La segunda línea establece una cadena opcional en cero, dos tipos diferentes
- La tercera línea compara una cadena opcional con cero, dos tipos diferentes
Revisaré algunos de los detalles de implementación de las opciones que permiten que estas líneas funcionen.
Crear un opcional
El uso ?
para crear un opcional es azúcar sintáctico, habilitado por el compilador Swift. Si desea hacerlo a lo largo, puede crear una opción como esta:
var name: Optional<String> = Optional("Bob")
Esto llama Optional
al primer inicializador, public init(_ some: Wrapped)
que infiere el tipo asociado del opcional del tipo utilizado entre paréntesis.
La forma aún más larga de crear y configurar un opcional:
var serialNumber:String? = Optional.none
serialNumber = Optional.some("1234")
print("\(serialNumber.debugDescription)")
Establecer un opcional para nil
Puede crear un opcional sin valor inicial, o crear uno con el valor inicial de nil
(ambos tienen el mismo resultado).
var name: String?
var name: String? = nil
Permitir que los opcionales sean iguales nil
está habilitado por el protocolo ExpressibleByNilLiteral
(anteriormente nombrado NilLiteralConvertible
). La opción se crea con Optional
la segunda inicializador 's, public init(nilLiteral: ())
. Los documentos dicen que no debes usar ExpressibleByNilLiteral
para nada excepto los opcionales, ya que eso cambiaría el significado de nulo en tu código, pero es posible hacerlo:
class Clint: ExpressibleByNilLiteral {
var name: String?
required init(nilLiteral: ()) {
name = "The Man with No Name"
}
}
let clint: Clint = nil // Would normally give an error
print("\(clint.name)")
El mismo protocolo le permite configurar un opcional ya creado para nil
. Aunque no se recomienda, puede usar el inicializador literal nulo directamente:
var name: Optional<String> = Optional(nilLiteral: ())
Comparando un opcional a nil
Los opcionales definen dos operadores especiales "==" y "! =", Que puede ver en la Optional
definición. La primera le ==
permite verificar si alguna opción es igual a cero. Dos opciones diferentes que se establecen en .none siempre serán iguales si los tipos asociados son los mismos. Cuando compara con cero, detrás de escena Swift crea un opcional del mismo tipo asociado, establecido en .none y luego lo usa para la comparación.
// How Swift actually compares to nil
var tuxedoRequired: String? = nil
let temp: Optional<String> = Optional.none
if tuxedoRequired == temp { // equivalent to if tuxedoRequired == nil
print("tuxedoRequired is nil")
}
El segundo ==
operador le permite comparar dos opciones. Ambos tienen que ser del mismo tipo y ese tipo debe ajustarse Equatable
(el protocolo que permite comparar cosas con el operador "==" normal). Swift (presumiblemente) desenvuelve los dos valores y los compara directamente. También maneja el caso donde uno o ambos de los opcionales son .none
. Tenga en cuenta la distinción entre comparar con el nil
literal.
Además, le permite comparar cualquier Equatable
tipo con una envoltura opcional de ese tipo:
let numberToFind: Int = 23
let numberFromString: Int? = Int("23") // Optional(23)
if numberToFind == numberFromString {
print("It's a match!") // Prints "It's a match!"
}
Detrás de escena, Swift envuelve lo no opcional como opcional antes de la comparación. Funciona también con literales ( if 23 == numberFromString {
)
Dije que hay dos ==
operadores, pero en realidad hay un tercero que le permite colocar nil
el lado izquierdo de la comparación.
if nil == name { ... }
Nombramientos opcionales
No existe una convención Swift para nombrar tipos opcionales de manera diferente a los tipos no opcionales. Las personas evitan agregar algo al nombre para mostrar que es opcional (como "optionalMiddleName" o "possibleNumberAsString") y dejan que la declaración muestre que es un tipo opcional. Esto se vuelve difícil cuando quieres nombrar algo para mantener el valor de un opcional. El nombre "middleName" implica que es un tipo String, por lo que cuando extrae el valor String de él, a menudo puede terminar con nombres como "actualMiddleName" o "unwrappedMiddleName" o "realMiddleName". Utilice el enlace opcional y reutilice el nombre de la variable para evitar esto.
La definición oficial
De "The Basics" en el lenguaje de programación Swift :
Swift también introduce tipos opcionales, que manejan la ausencia de un valor. Los opcionales dicen "hay un valor y es igual a x" o "no hay ningún valor". Las opciones son similares a usar nil con punteros en Objective-C, pero funcionan para cualquier tipo, no solo para clases. Los opcionales son más seguros y expresivos que los punteros nulos en Objective-C y están en el corazón de muchas de las características más poderosas de Swift.
Los opcionales son un ejemplo del hecho de que Swift es un lenguaje de tipo seguro. Swift lo ayuda a ser claro acerca de los tipos de valores con los que puede trabajar su código. Si parte de su código espera una Cadena, el tipo de seguridad le impide pasar un Int por error. Esto le permite detectar y corregir errores lo antes posible en el proceso de desarrollo.
Para terminar, aquí hay un poema de 1899 sobre opciones:
Ayer, en la escalera
, conocí a un hombre que no estaba allí.
No estaba allí otra vez hoy
. Deseo, deseo que se vaya
Antigonish.
Más recursos: