El lenguaje de programación
Swift tiene solo cuatro años, pero ya se está convirtiendo en el principal lenguaje de desarrollo para iOS. Desarrollado para la versión 5.0, Swift se ha convertido en un lenguaje complejo y poderoso que cumple con un paradigma tanto orientado a objetos como funcional. Y con cada nueva versión, agrega aún más funciones.
¿Pero qué tan bien conoces a Swift? En este artículo, encontrará preguntas de muestra para una entrevista de Swift.
Puede usar estas preguntas para entrevistar a los candidatos para evaluar sus conocimientos o puede evaluar los suyos. Si no sabe la respuesta, no se preocupe: hay una respuesta para cada pregunta.
Las preguntas se dividen en tres grupos:
- Principiante : para principiantes. Leyó un par de libros y aplicó Swift en sus propias aplicaciones.
- Intermedio : adecuado para aquellos que están realmente interesados en el idioma. Ya has leído mucho al respecto y a menudo experimentas.
- Avanzado : adecuado para los desarrolladores más avanzados: aquellos a quienes les gusta meterse en la jungla de la sintaxis y usar técnicas avanzadas.
Hay dos tipos de preguntas para cada nivel:
- escrito : adecuado para probar por correo electrónico, ya que sugieren escribir código.
- oral : se puede usar cuando se habla por teléfono o en persona, ya que hay suficiente respuesta en palabras.
A medida que lea este artículo, mantenga abierto el
patio de recreo para poder consultar el código de la pregunta. Todas las respuestas se han probado en
Xcode 10.2 y
Swift 5 .
Principiante
preguntas escritasPregunta 1Considere el siguiente código:
struct Tutorial { var difficulty: Int = 1 } var tutorial1 = Tutorial() var tutorial2 = tutorial1 tutorial2.difficulty = 2
¿Cuáles son los valores de
tutorial1.difficulty y
tutorial2.difficulty ? ¿Habría alguna diferencia si el
Tutorial fuera una clase? Por qué
la respuestala dificultad tutorial1.d es 1 y la dificultad tutorial2.d es 2.
En Swift, las estructuras son tipos de valor. Se copian, no se hace referencia. La siguiente línea copia el
tutorial1 y lo asigna al
tutorial2 :
var tutorial2 = tutorial1
Los cambios en el
tutorial2 no afectan el
tutorial1 .
Si Tutorial fuera una clase,
tutorial1.difficulty y
tutorial2.difficulty serían iguales a 2. Las clases en Swift son tipos de referencia. Cuando cambie la propiedad tutorial1, verá el mismo cambio para el tutorial2, y viceversa.
Pregunta 2Usted declaró
view1 con
var y
view2 con
let . ¿Cuál es la diferencia y se compila la última línea?
import UIKit var view1 = UIView() view1.alpha = 0.5 let view2 = UIView() view2.alpha = 0.5
la respuestaSí, se compila la última línea.
view1 es una variable y puede asignar su valor a una nueva instancia de UIView. Con
let , solo puede asignar un valor una vez, por lo que el siguiente código no se compila:
view2 = view1
Sin embargo, UIView es una clase con semántica referencial, por lo que puede cambiar las propiedades de view2, lo que significa que el código se
compilará .
Pregunta 3Este código ordena la matriz alfabéticamente. Simplifique el cierre tanto como sea posible.
var animals = ["fish", "cat", "chicken", "dog"] animals.sort { (one: String, two: String) -> Bool in return one < two } print(animals)
la respuestaSwift
determina automáticamente el tipo de parámetros de cierre y el tipo de retorno, por lo que puede
eliminarlos :
animals.sort { (one, two) in return one < two }
Puede reemplazar los nombres de los parámetros con la notación
$ i :
animals.sort { return $0 < $1 }
Los cierres que consisten en una sola declaración pueden no contener la palabra clave
return . El valor de la última declaración ejecutada se convierte en el resultado de retorno del cierre:
animals.sort { $0 < $1 }
Finalmente, dado que Swift sabe que los elementos de la matriz se ajustan al protocolo
Equatable , simplemente puede escribir:
animals.sort(by: <)
Upd:
hummingbirddj simplificó aún más:
En este caso, puedes acortar aún más: animals.sort()
- Ordena ascendente, funciona para tipos que implementan Comparable.
Pregunta 4Este código crea dos clases:
Dirección y
Persona . También se crean dos instancias de la clase
Persona (
Ray y
Brian ).
class Address { var fullAddress: String var city: String init(fullAddress: String, city: String) { self.fullAddress = fullAddress self.city = city } } class Person { var name: String var address: Address init(name: String, address: Address) { self.name = name self.address = address } } var headquarters = Address(fullAddress: "123 Tutorial Street", city: "Appletown") var ray = Person(name: "Ray", address: headquarters) var brian = Person(name: "Brian", address: headquarters)
Supongamos que
Brian se ha mudado a una nueva dirección y desea actualizar su registro de la siguiente manera:
brian.address.fullAddress = "148 Tutorial Street"
Esto compila y se ejecuta sin errores. Pero, si verifica ahora la dirección de
Ray , verá que él también se
"mudó" .
¿Qué pasó aquí y cómo podemos solucionarlo?
la respuestaLa dirección es una clase y tiene semántica de referencia . Por lo tanto, la sede es la misma instancia de la clase compartida por Ray y Brian. Cambiar la sede cambiará la dirección de ambos.
Para solucionar esto, puede crear una nueva instancia de la clase Address y asignarla a Brian, o declarar Address como una estructura en lugar de una clase .
preguntas oralesPregunta 1¿Qué es
opcional y qué problemas resuelven?
la respuestaopcional permite que una variable de cualquier tipo presente una situación " sin valor ". En Objective-C, "sin valor" solo estaba disponible en los tipos de referencia que utilizan el valor especial nulo . Los tipos de valor, como int o float , no tenían esta capacidad.
Swift ha extendido el concepto de "sin valor" a los tipos de valor. La variable opcional puede contener un valor o nulo , lo que indica la ausencia de un valor.
Pregunta 2Enumere brevemente las principales diferencias entre
estructura y
clase .
la respuestaLas clases admiten herencia, pero las estructuras no.
Las clases son un tipo de referencia, las estructuras son un tipo de valor.
Pregunta 3¿Qué son los
genéricos y para qué sirven?
la respuestaEn Swift, puede usar
genéricos en clases, estructuras y enumeraciones.
Los genéricos solucionan el problema de la duplicación de código. Si tiene un método que acepta parámetros de un tipo, a veces tiene que duplicar el código para trabajar con parámetros de otro tipo.
Por ejemplo, en este código, la segunda función es un "clon" de la primera, excepto que tiene parámetros de cadena, no enteros.
func areIntEqual(_ x: Int, _ y: Int) -> Bool { return x == y } func areStringsEqual(_ x: String, _ y: String) -> Bool { return x == y } areStringsEqual("ray", "ray")
Usando genéricos, combina dos funciones en una y al mismo tiempo garantiza la seguridad de los tipos:
func areTheyEqual<T: Equatable>(_ x: T, _ y: T) -> Bool { return x == y } areTheyEqual("ray", "ray") areTheyEqual(1, 1)
Como está probando la igualdad, está restringiendo los tipos a aquellos que se ajustan al protocolo
Equatable . Este código proporciona el resultado deseado y evita la transferencia de parámetros del tipo incorrecto.
Pregunta 4En algunos casos, no será posible evitar las opciones implícitamente
desenvueltas . Cuando y por que
la respuestaLas razones más comunes para usar opciones no envueltas implícitamente son:
- cuando no puede inicializar una propiedad que no es nula en el momento de la creación. Un ejemplo típico es la salida en Interface Builder, que siempre se inicializa después de su propietario. En este caso especial, si todo está configurado correctamente en Interface Builder, tiene la garantía de que la salida no es nula antes de usarla.
- para resolver el problema del bucle de referencias fuertes , cuando dos instancias de clases se refieren entre sí y se requiere una referencia no nula a otra instancia. En este caso, marca el enlace en un lado como no propietario , y en el otro lado usa la expansión opcional implícita.
Pregunta 5¿Cuáles son algunas formas de implementar opcional? Califíquelos en términos de seguridad.
var x : String? = "Test"
Sugerencia: solo 7 formas.
la respuestaEl desenvolvimiento forzado no es seguro.
let a: String = x!
La implementación implícita al declarar una variable no es segura.
var a = x!
La encuadernación opcional es segura.
if let a = x { print("x was successfully unwrapped and is = \(a)") }
El encadenamiento opcional es seguro.
let a = x?.count
El operador de fusión nula es seguro.
let a = x ?? ""
La declaración de la
Guardia es segura.
guard let a = x else { return }
Patrón opcional - seguro.
if case let a? = x { print(a) }
Intermedio
preguntas escritasPregunta 1¿Cuál es la diferencia entre
nil y
.none ?
la respuestaNo hay diferencia,
Opcional.none (brevemente
.none ) y
nil son equivalentes.
De hecho, la siguiente declaración devolverá
verdadero :
nil == .none
Usar
nil es más generalmente aceptado y recomendado.
Pregunta 2Aquí hay un modelo de termómetro en forma de clase y estructura. El compilador se queja de la última línea. ¿Qué hay de malo allí?
public class ThermometerClass { private(set) var temperature: Double = 0.0 public func registerTemperature(_ temperature: Double) { self.temperature = temperature } } let thermometerClass = ThermometerClass() thermometerClass.registerTemperature(56.0) public struct ThermometerStruct { private(set) var temperature: Double = 0.0 public mutating func registerTemperature(_ temperature: Double) { self.temperature = temperature } } let thermometerStruct = ThermometerStruct() thermometerStruct.registerTemperature(56.0)
la respuestaThermometerStruct se declara correctamente con una función de mutación para cambiar la variable interna. El compilador se queja de que está llamando al método registerTemperature de la instancia que se creó con let , por lo que esta instancia es inmutable. Cambiar let a var arreglará el error de compilación.
En las estructuras, debe marcar los métodos que modifican las variables internas como mutantes , pero no puede invocar estos métodos utilizando una instancia inmutable.
Pregunta 3¿Qué generará este código y por qué?
var thing = "cars" let closure = { [thing] in print("I love \(thing)") } thing = "airplanes" closure()
la respuestaSe imprimirá: me encantan los autos. La lista de captura creará una copia de la variable cuando se declare el cierre. Esto significa que la variable capturada no cambiará su valor, incluso después de asignar un nuevo valor.
Si omite la lista de captura en el cierre, el compilador usará el enlace, no la copia. La llamada de cierre reflejará el cambio en la variable:
var thing = "cars" let closure = { print("I love \(thing)") } thing = "airplanes" closure()
Pregunta 4Esta es una función que cuenta el número de valores únicos en una matriz:
func countUniques<T: Comparable>(_ array: Array<T>) -> Int { let sorted = array.sorted() let initial: (T?, Int) = (.none, 0) let reduced = sorted.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) } return reduced.1 }
Utiliza ordenado, por lo que solo usa tipos que se ajustan al protocolo comparable.
Puedes llamarlo así:
countUniques([1, 2, 3, 3])
Vuelva a escribir esta función como una extensión de matriz para que pueda usarla así:
[1, 2, 3, 3].countUniques()
nota del traductorAlgo demasiado doloroso es una función monstruosa. Por qué no
func countUniques<T: Hashable>(_ array: Array<T>) -> Int { return Set(array).count }
la respuesta extension Array where Element: Comparable { func countUniques() -> Int { let sortedValues = sorted() let initial: (Element?, Int) = (.none, 0) let reduced = sortedValues.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) } return reduced.1 } }
Pregunta 5Aquí hay una función que divide dos dobles opcionales. Hay tres condiciones que deben cumplirse:
- el dividendo no debe ser nulo
- el divisor no debe ser nulo
- el divisor no debe ser 0
func divide(_ dividend: Double?, by divisor: Double?) -> Double? { if dividend == nil { return nil } if divisor == nil { return nil } if divisor == 0 { return nil } return dividend! / divisor! }
Vuelva a escribir esta función utilizando la declaración de
protección y no utilizando el desenvolvimiento forzado.
la respuestaLa declaración de
guardia introducida en Swift 2.0 proporciona una salida si no se cumple la condición. Aquí hay un ejemplo:
guard dividend != nil else { return nil }
También puede usar la declaración de protección para el
enlace opcional , después de lo cual la variable expandida estará disponible
fuera de la declaración de protección :
guard let dividend = dividend else { return .none }
Entonces puedes reescribir la función de esta manera:
func divide(_ dividend: Double?, by divisor: Double?) -> Double? { guard let dividend = dividend else { return nil } guard let divisor = divisor else { return nil } guard divisor != 0 else { return nil } return dividend / divisor }
Preste atención a la ausencia de desempaque forzado, ya que ya desempaquetamos el dividendo y el divisor y los colocamos en variables inmutables no opcionales.
También tenga en cuenta que el resultado de las opciones desempaquetadas en la declaración de protección también está disponible fuera de la declaración de protección.
Puede simplificar aún más la función agrupando declaraciones de guardia:
func divide(_ dividend: Double?, by divisor: Double?) -> Double? { guard let dividend = dividend, let divisor = divisor, divisor != 0 else { return nil } return dividend / divisor }
Pregunta 6Reescribe el método de la pregunta 5 usando la
declaración if let .
la respuestaLa instrucción if let le permite desempaquetar opciones y usar este valor dentro de este bloque de código. Fuera de él, estos valores no estarán disponibles.
func divide(_ dividend: Double?, by divisor: Double?) -> Double? { if let dividend = dividend, let divisor = divisor, divisor != 0 { return dividend / divisor } else { return nil } }
preguntas oralesPregunta 1En Objective-C, declaras una constante como esta:
const int number = 0;
Y así en Swift:
let number = 0
Cual es la diferencia
la respuestaEn
Objective-C, una constante se inicializa
en tiempo de compilación con un valor que debería conocerse en este punto.
Un valor inmutable creado con
let es una constante definida
en tiempo de ejecución . Puede inicializarlo con una expresión estática o dinámica. Por lo tanto, podemos hacer esto:
let higherNumber = number + 5
Tenga en cuenta que dicha asignación solo se puede hacer una vez.
Pregunta 2Para declarar una propiedad o función estática para los tipos de valor, se utiliza el modificador
estático . Aquí hay un ejemplo para la estructura:
struct Sun { static func illuminate() {} }
Y para las clases es posible usar modificadores
estáticos o de
clase . El resultado es el mismo, pero hay una diferencia. Descríbelo
la respuestastatic hace que una propiedad o función sea estática y
no se superponga . El uso de la clase
anulará una propiedad o función.
Aquí el compilador jurará ante un intento de anular
illuminate () :
class Star { class func spin() {} static func illuminate() {} } class Sun : Star { override class func spin() { super.spin() }
Pregunta 3¿Es posible agregar una
propiedad almacenada a un tipo usando la
extensión ? ¿Cómo o por qué no?
la respuestaNo, esto no es posible. Podemos usar la extensión para agregar un nuevo comportamiento a un tipo existente, pero no podemos cambiar el tipo en sí o su interfaz. Para almacenar la nueva propiedad almacenada, necesitamos memoria adicional, y la extensión no puede hacer esto.
Pregunta 4¿Qué es un protocolo en Swift?
la respuestaUn protocolo es un tipo que define un esquema de métodos, propiedades, etc. Una clase, estructura o enumeración puede tomar un protocolo para implementar todo esto. El protocolo en sí no implementa la funcionalidad, pero la define.
Avanzado
preguntas escritasPregunta 1Supongamos que tenemos una estructura que define el modelo de un termómetro:
public struct Thermometer { public var temperature: Double public init(temperature: Double) { self.temperature = temperature } }
Para crear una instancia, escribimos:
var t: Thermometer = Thermometer(temperature:56.8)
Pero algo como esto sería mucho más conveniente:
var thermometer: Thermometer = 56.8
¿Es esto posible? Como?
la respuestaSwift define protocolos que le permiten inicializar un tipo usando literales por asignación. Aplicar el protocolo apropiado y proporcionar un inicializador público permitirá la inicialización usando literales. En el caso del termómetro, implementamos
ExpressibleByFloatLiteral :
extension Thermometer: ExpressibleByFloatLiteral { public init(floatLiteral value: FloatLiteralType) { self.init(temperature: value) } }
Ahora podemos crear una instancia como esta:
var thermometer: Thermometer = 56.8
Pregunta 2Swift tiene un conjunto de operadores predefinidos para operaciones aritméticas y lógicas. También le permite crear sus propios operadores, tanto unarios como binarios.
Defina e implemente su propio operador de exponenciación (^^) de acuerdo con los siguientes requisitos:
- toma dos int como parámetros
- devuelve el resultado de elevar el primer parámetro a la potencia del segundo
- procesa correctamente el orden de las operaciones algebraicas
- ignora posibles errores de desbordamiento
la respuestaLa creación de un nuevo operador se lleva a cabo en dos etapas: anuncio e implementación.
La declaración utiliza la palabra clave del
operador para especificar el tipo (unario o binario), para especificar la secuencia de caracteres del nuevo operador, su asociatividad y la precedencia de la ejecución.
Aquí el operador es ^^ y su tipo es infijo (binario). La asociatividad es correcta.
Swift no tiene una antigüedad predefinida para la exponenciación. En álgebra, la exponenciación debe calcularse antes de la multiplicación / división. Por lo tanto, creamos un orden de ejecución personalizado colocando la exponenciación más alta que la multiplicación.
Este anuncio:
precedencegroup ExponentPrecedence { higherThan: MultiplicationPrecedence associativity: right } infix operator ^^: ExponentPrecedence
Esta es la implementación:
func ^^(base: Int, exponent: Int) -> Int { let l = Double(base) let r = Double(exponent) let p = pow(l, r) return Int(p) }
Pregunta 3El siguiente código define la estructura de
Pizza y el protocolo
Pizzeria con una extensión para la implementación predeterminada del método
makeMargherita () :
struct Pizza { let ingredients: [String] } protocol Pizzeria { func makePizza(_ ingredients: [String]) -> Pizza func makeMargherita() -> Pizza } extension Pizzeria { func makeMargherita() -> Pizza { return makePizza(["tomato", "mozzarella"]) } }
Ahora definimos el restaurante
Lombardis :
struct Lombardis: Pizzeria { func makePizza(_ ingredients: [String]) -> Pizza { return Pizza(ingredients: ingredients) } func makeMargherita() -> Pizza { return makePizza(["tomato", "basil", "mozzarella"]) } }
El siguiente código crea dos instancias de
Lombardis . ¿Cuál de ellos hace margarita con albahaca?
let lombardis1: Pizzeria = Lombardis() let lombardis2: Lombardis = Lombardis() lombardis1.makeMargherita() lombardis2.makeMargherita()
la respuestaEn ambos. El protocolo
Pizzeria declara el método
makeMargherita () y proporciona una implementación predeterminada. La implementación de
Lombardis anula el método predeterminado. Como declaramos el método en el protocolo en dos lugares, se llamará a la implementación correcta.
Pero, ¿qué
pasa si el protocolo no declara el método
makeMargherita () , y la extensión aún proporciona la implementación predeterminada, como esta:
protocol Pizzeria { func makePizza(_ ingredients: [String]) -> Pizza } extension Pizzeria { func makeMargherita() -> Pizza { return makePizza(["tomato", "mozzarella"]) } }
En este caso, solo lombardis2 tendría pizza con albahaca, mientras que lombardis1 no tendría pizza, porque usaría el método definido en extensión.
Pregunta 4El siguiente código no se compila. ¿Puedes explicar qué le pasa? Sugerir soluciones al problema.
struct Kitten { } func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") } print(k) }
Sugerencia: hay tres formas de corregir el error.
la respuestaEl bloque
else de la declaración de
guardia requiere una opción de salida, ya sea usando
return , lanzando una excepción o llamando a
@noreturn . La solución más simple es agregar una
declaración de devolución .
func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") return } print(k) }
Esta solución arrojará una excepción:
enum KittenError: Error { case NoKitten } struct Kitten { } func showKitten(kitten: Kitten?) throws { guard let k = kitten else { print("There is no kitten") throw KittenError.NoKitten } print(k) } try showKitten(kitten: nil)
Finalmente, aquí está la llamada a
fatalError () , la función
@noreturn .
struct Kitten { } func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") fatalError() } print(k) }
preguntas oralesPregunta 1¿Son los cierres un tipo de referencia o tipo de valor?
la respuestaLos cierres son un tipo de referencia. Si asigna un cierre a una variable y luego lo copia a otra variable, copie el enlace al mismo cierre y su lista de captura.
Pregunta 2Utiliza el tipo
UInt para almacenar un entero sin signo. Implementa un inicializador para convertir de un todo con un signo:
init(_ value: Int)
Sin embargo, el siguiente código no se compila si especifica un valor negativo:
let myNegative = UInt(-1)
Los enteros con signo, por definición, no pueden ser negativos. Sin embargo, es posible utilizar la representación de un número negativo en la memoria para traducirlo a sin signo.
¿Cómo puedo convertir un entero negativo a UInt mientras mantengo su representación en la memoria?
la respuestaHay un inicializador para esto:
UInt(bitPattern: Int)
Y uso:
let myNegative = UInt(bitPattern: -1)
Pregunta 3¿Describir referencias circulares en Swift? ¿Cómo se pueden arreglar?
la respuestaLas referencias circulares se producen cuando dos instancias contienen una referencia fuerte entre sí, lo que conduce a una pérdida de memoria debido al hecho de que ninguna de estas instancias puede liberarse. Una instancia no se puede liberar mientras todavía haya fuertes referencias a ella, pero una instancia contiene a la otra.
Esto se puede resolver reemplazando el enlace en un lado especificando la palabra clave débil o no propiedad .
Pregunta 4Swift te permite crear enumeraciones recursivas. Aquí hay un ejemplo de dicha enumeración que contiene una variante de nodo con dos tipos asociativos, T y List:
enum List<T> { case node(T, List<T>) }
Habrá un error de compilación. ¿Qué extrañamos?
la respuestaOlvidamos la palabra clave
indirecta , que permite opciones de enumeración recursiva similares:
enum List<T> { indirect case node(T, List<T>) }