swift - ¿Por qué crear "Opcionales implícitamente sin envolver", ya que eso implica que sabes que hay un valor?
design-patterns optional (8)
Antes de que pueda describir los casos de uso de los opcionales implícitamente sin envolver, ya debe entender qué opcionales y opcionales implícitamente sin envolver están en Swift. Si no lo haces, te recomiendo que primero leas mi artículo sobre opciones.
Cuándo usar un opcional implícitamente sin envolver
Hay dos razones principales por las que uno podría crear un opcional implícitamente sin envolver. Todo tiene que ver con la definición de una variable a la que nunca se accederá cuando nil
porque de lo contrario, el compilador Swift siempre te obligará a desenvolver explícitamente un Opcional.
1. Una constante que no se puede definir durante la inicialización
Cada constante miembro debe tener un valor en el momento en que se complete la inicialización. A veces, una constante no se puede inicializar con su valor correcto durante la inicialización, pero aún se puede garantizar que tenga un valor antes de acceder.
El uso de una variable opcional soluciona este problema porque un opcional se inicializa automáticamente con nil
y el valor que eventualmente contendrá seguirá siendo inmutable. Sin embargo, puede ser un dolor estar constantemente desenvolviendo una variable que usted sabe con seguridad que no es nula. Los opcionales implícitamente sin envolver logran los mismos beneficios que los opcionales con el beneficio agregado que uno no tiene que desenvolver explícitamente en todas partes.
Un gran ejemplo de esto es cuando una variable miembro no puede inicializarse en una subclase UIView hasta que se carga la vista:
class MyView: UIView {
@IBOutlet var button: UIButton!
var buttonOriginalWidth: CGFloat!
override func awakeFromNib() {
self.buttonOriginalWidth = self.button.frame.size.width
}
}
Aquí, no puede calcular el ancho original del botón hasta que se cargue la vista, pero sabe que se awakeFromNib
a awakeFromNib
antes que a cualquier otro método en la vista (que no sea la inicialización). En lugar de forzar que el valor se desenvuelva explícitamente sin sentido en toda su clase, puede declararlo como un opcional implícitamente sin envolver.
2. Cuando tu aplicación no puede recuperarse de una variable que es nil
Esto debería ser extremadamente raro, pero si su aplicación no puede continuar ejecutándose si una variable es nil
cuando se accede, sería una pérdida de tiempo molestarse en probarla para nada. Normalmente, si tiene una condición que debe ser absolutamente cierta para que su aplicación continúe ejecutándose, usaría una assert
. Una Opcional implícitamente sin envolver tiene una aserción para nil incorporada directamente en ella. Incluso entonces, a menudo es bueno desenvolver lo opcional y usar una afirmación más descriptiva si es nula.
Cuándo no usar una opción implícitamente sin envolver
1. Variables de miembros calculadas con pereza
A veces tiene una variable miembro que nunca debería ser nula, pero no se puede establecer en el valor correcto durante la inicialización. Una solución es usar una opción implícitamente sin envolver, pero una mejor manera es usar una variable perezosa:
class FileSystemItem {
}
class Directory : FileSystemItem {
lazy var contents : [FileSystemItem] = {
var loadedContents = [FileSystemItem]()
// load contents and append to loadedContents
return loadedContents
}()
}
Ahora, el contents
variable miembro no se inicializa hasta la primera vez que se accede. Esto le da a la clase la oportunidad de entrar en el estado correcto antes de calcular el valor inicial.
Nota: Esto puede parecer contradecir el # 1 desde arriba. Sin embargo, hay que hacer una distinción importante. El buttonOriginalWidth
anterior debe configurarse durante viewDidLoad para evitar que alguien cambie el ancho de los botones antes de acceder a la propiedad.
2. Por todas partes
En la mayoría de los casos, se deben evitar los opcionales implícitamente sin envolver, ya que, si se usan erróneamente, toda la aplicación se bloqueará cuando se acceda a ella mientras sea nil
. Si alguna vez no está seguro de si una variable puede ser nula, siempre use la opción opcional normal. Desenvolver una variable que nunca es nil
ciertamente no duele mucho.
¿Por qué crearía un "Opcional implícitamente sin envolver" frente a la creación de una variable o constante regular? Si sabe que se puede desenvolver con éxito, ¿por qué crear un opcional en primer lugar? Por ejemplo, ¿por qué es esto:
let someString: String! = "this is the string"
Va a ser más útil que:
let someString: String = "this is the string"
Si "opcionales indican que una constante o variable puede tener" ningún valor ", pero" a veces queda claro a partir de la estructura de un programa que un opcional siempre tendrá un valor después de que ese valor se establece por primera vez ", ¿cuál es el punto de ¿Haciendo que sea opcional en primer lugar? Si sabe que un opcional siempre va a tener un valor, ¿no lo hace eso no es opcional?
Apple ofrece un gran ejemplo en The Swift Programming Language -> Conteo automático de referencias -> Resolución de ciclos de referencia fuertes entre instancias de clase -> Referencias sin propiedad y propiedades opcionales implícitamente sin envolver
class Country {
let name: String
var capitalCity: City! // Apple finally correct this line until 2.0 Prerelease (let -> var)
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
El inicializador para
City
se llama desde dentro del inicializador paraCountry
. Sin embargo, el inicializador paraCountry
no se puede pasar al inicializador deCity
hasta que una nueva instancia deCountry
esté completamente inicializada, como se describe en Inicialización de dos fases .Para hacer frente a este requisito, declara la propiedad
capitalCity
delCountry
como una propiedad opcional implícitamentecapitalCity
.
Considere el caso de un objeto que puede tener cero propiedades mientras se está construyendo y configurando, pero es inmutable y no nula después (NSImage se trata a menudo de esta manera, aunque en su caso aún es útil mutar a veces). Los opcionales implícitamente desempaquetados limpiarían su código en gran medida, con una pérdida de seguridad relativamente baja (siempre y cuando la única garantía se mantenga, sería segura).
(Editar) Sin embargo, para ser claros, las opciones regulares son casi siempre preferibles.
La justificación de los opcionales implícitos es más fácil de explicar si se analiza primero la justificación del desenvolvimiento forzado.
Reemplazo forzado de un opcional (implícito o no), usando el! operador, significa que está seguro de que su código no tiene errores y el opcional ya tiene un valor donde se está envolviendo. Sin el ! Operador, probablemente solo haría una afirmación con un enlace opcional:
if let value = optionalWhichTotallyHasAValue {
println("/(value)")
} else {
assert(false)
}
que no es tan bonito como
println("/(value!)")
Ahora, los opcionales implícitos le permiten expresar tener un opcional que espera tener siempre un valor cuando se desenvuelve, en todos los flujos posibles. Por lo tanto, va un paso más allá para ayudarlo: ¡relajando el requisito de escribir el! para desenvolver cada vez, y asegurarse de que el tiempo de ejecución seguirá generando errores en caso de que sus suposiciones sobre el flujo sean incorrectas.
Las opciones implícitamente desempaquetadas son útiles para presentar una propiedad como no opcional cuando realmente necesita ser opcional bajo las coberturas. Esto suele ser necesario para "atar el nudo" entre dos objetos relacionados que necesitan una referencia a la otra. Tiene sentido cuando ninguna de las referencias es realmente opcional, pero una de ellas debe ser nula mientras se inicializa el par.
Por ejemplo:
// These classes are buddies that never go anywhere without each other
class B {
var name : String
weak var myBuddyA : A!
init(name : String) {
self.name = name
}
}
class A {
var name : String
var myBuddyB : B
init(name : String) {
self.name = name
myBuddyB = B(name:"/(name)''s buddy B")
myBuddyB.myBuddyA = self
}
}
var a = A(name:"Big A")
println(a.myBuddyB.name) // prints "Big A''s buddy B"
Cualquier instancia de B
siempre debe tener una referencia válida de myBuddyA
, por lo que no queremos que el usuario la trate como opcional, pero necesitamos que sea opcional para poder construir una B
antes de que tengamos una A
para referirnos.
¡SIN EMBARGO! Este tipo de requisito de referencia mutua es a menudo una indicación de acoplamiento apretado y diseño deficiente. Si confía en las opciones implícitamente sin envolver, probablemente debería considerar la refactorización para eliminar las dependencias cruzadas.
Los ejemplos simples de una línea (o de varias líneas) no cubren muy bien el comportamiento de los opcionales. Sí, si declara una variable y le asigna un valor de inmediato, no tiene sentido alguno.
El mejor caso que he visto hasta ahora es la configuración que ocurre después de la inicialización del objeto, seguida del uso que está "garantizado" para seguir esa configuración, por ejemplo, en un controlador de vista:
class MyViewController: UIViewController {
var screenSize: CGSize?
override func viewDidLoad {
super.viewDidLoad()
screenSize = view.frame.size
}
@IBAction printSize(sender: UIButton) {
println("Screen size: /(screenSize!)")
}
}
Sabemos que se printSize
a printSize
después de que se cargue la vista; es un método de acción conectado a un control dentro de esa vista, y nos aseguramos de no llamarla de otra manera. ¡Así que podemos ahorrarnos algún tipo de comprobación / enlace opcional con el !
. Swift no puede reconocer esa garantía (al menos hasta que Apple resuelva el problema de la detención), así que le dices al compilador que existe.
Sin embargo, esto rompe el tipo de seguridad hasta cierto punto. Anyplace tiene una opción implícitamente sin envolver, es un lugar donde su aplicación puede fallar si su "garantía" no siempre se cumple, por lo que es una función para usar con moderación. Además, usando !
Todo el tiempo hace que parezca que estás gritando, y a nadie le gusta eso.
Los opcionales implícitamente desenvueltos son un compromiso pragmático para hacer que el trabajo en un entorno híbrido que debe interactuar con los marcos de Cocoa existentes y sus convenciones sea más agradable, al mismo tiempo que permite la migración paso a paso hacia un paradigma de programación más seguro, sin punteros nulos, implementado por el compilador Swift.
El libro Swift, en el capítulo Lo básico , en la sección Opcionales implícitamente sin envolver dice:
Los opcionales implícitamente desenvueltos son útiles cuando se confirma que el valor de un opcional existe inmediatamente después de que el opcional se define por primera vez y definitivamente se puede suponer que existe en cada punto posterior. El uso principal de las opciones desempaquetadas implícitamente en Swift es durante la inicialización de la clase, como se describe en Referencias sin propiedad y Propiedades opcionales desempaquetadas implícitamente .
...
Puede pensar que un opcional implícitamente desempaquetado da permiso para que el opcional se desenvuelva automáticamente cada vez que se use. En lugar de colocar un signo de exclamación después del nombre de la opción cada vez que lo usa, coloca un signo de exclamación después del tipo de la opción cuando lo declara.
Esto se reduce a casos de uso en los que el carácter no nil
de las propiedades se establece mediante una convención de uso y el compilador no puede imponerlo durante la inicialización de la clase. Por ejemplo, las propiedades de UIViewController
que se inicializan desde NIB o Storyboards, donde la inicialización se divide en fases separadas, pero después de viewDidLoad()
puede asumir que las propiedades generalmente existen. De lo contrario, para satisfacer al compilador, tenía que usar el desenvolvimiento forzado , el enlace opcional o el encadenamiento opcional solo para ocultar el propósito principal del código.
La parte anterior del libro de Swift también se refiere al capítulo de conteo automático de referencias :
Sin embargo, hay un tercer escenario, en el que ambas propiedades siempre deben tener un valor, y ninguna de las dos debe ser
nil
una vez que se completa la inicialización. En este escenario, es útil combinar una propiedad sin dueño en una clase con una propiedad opcional implícitamente desenvuelta en la otra clase.Esto permite acceder a ambas propiedades directamente (sin desempaquetar opcional) una vez que se completa la inicialización, mientras se evita un ciclo de referencia.
Esto se reduce a las peculiaridades de no ser un lenguaje recolectado en la basura, por lo tanto, la interrupción de los ciclos de retención depende de usted como programador y los opcionales implícitamente desenvueltos son una herramienta para ocultar esta peculiaridad.
Esto cubre la pregunta "¿Cuándo usar opciones opcionales no envueltas implícitamente en su código?". Como desarrollador de aplicaciones, los encontrará principalmente en firmas de métodos de bibliotecas escritas en Objective-C, que no tienen la capacidad de expresar tipos opcionales.
De Usar Swift con Cocoa y Objective-C, sección Trabajar con cero :
Debido a que Objective-C no ofrece ninguna garantía de que un objeto sea no nulo, Swift hace que todas las clases en tipos de argumentos y tipos de retorno sean opcionales en las API de Objective-C importadas. Antes de usar un objeto Objective-C, debe verificar que no falte.
En algunos casos, puede estar absolutamente seguro de que un método o propiedad de Objective-C nunca devuelve una referencia de objeto
nil
. Para hacer más conveniente trabajar con objetos en este escenario especial, Swift importa tipos de objetos como opciones implícitamente desenvueltas . Los tipos opcionales implícitamente desenvueltos incluyen todas las características de seguridad de los tipos opcionales. Además, puede acceder al valor directamente sin verificar si esnil
o sin desenvolverlo usted mismo. Cuando accede al valor en este tipo de tipo opcional sin desenvolverlo de manera segura primero, el opcional implícitamente desenvuelto comprueba si falta el valor. Si falta el valor, se produce un error de tiempo de ejecución. Como resultado, siempre debe comprobar y desenvolver un mensaje opcional implícitamente sin envolver, a menos que esté seguro de que no puede faltar el valor.
... y más allá de aquí yace
Si lo sabe con certeza, un valor devuelto de un opcional en lugar de nil
, los opcionales implícitamente sin envolver se utilizan para capturar directamente esos valores de los opcionales y no opcionales no.
//Optional string with a value
let optionalString: String? = "This is an optional String"
//Declaration of an Implicitly Unwrapped Optional String
let implicitlyUnwrappedOptionalString: String!
//Declaration of a non Optional String
let nonOptionalString: String
//Here you can catch the value of an optional
implicitlyUnwrappedOptionalString = optionalString
//Here you can''t catch the value of an optional and this will cause an error
nonOptionalString = optionalString
Así que esta es la diferencia entre el uso de
let someString : String!
y let someString : String