Entrevista: Swift. Preguntas y respuestas

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 escritas
    Pregunta 1
    Considere 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 respuesta
    la 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 2
    Usted 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 respuesta
    Sí, 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 // : view2 is immutable 

    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 3
    Este 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 respuesta
    Swift 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 4
    Este 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 respuesta
    La 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 orales
    Pregunta 1
    ¿Qué es opcional y qué problemas resuelven?

    la respuesta
    opcional 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 2
    Enumere brevemente las principales diferencias entre estructura y clase .

    la respuesta
    Las 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 respuesta
    En 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") // true areIntEqual(1, 1) // true 

    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 4
    En algunos casos, no será posible evitar las opciones implícitamente desenvueltas . Cuando y por que

    la respuesta
    Las 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 respuesta
    El 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 escritas
    Pregunta 1
    ¿Cuál es la diferencia entre nil y .none ?

    la respuesta
    No 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 2
    Aquí 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 respuesta
    ThermometerStruct 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 respuesta
    Se 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() // Prints: "I love airplanes" 



    Pregunta 4
    Esta 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]) //  3 


    Vuelva a escribir esta función como una extensión de matriz para que pueda usarla así:

     [1, 2, 3, 3].countUniques() //   3 


    nota del traductor
    Algo 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 5
    Aquí 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 respuesta
    La 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 6
    Reescribe el método de la pregunta 5 usando la declaración if let .

    la respuesta
    La 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 orales
    Pregunta 1
    En Objective-C, declaras una constante como esta:

     const int number = 0; 

    Y así en Swift:

     let number = 0 

    Cual es la diferencia

    la respuesta
    En 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 2
    Para 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 respuesta
    static 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() } // error: class method overrides a 'final' class method override static func illuminate() { super.illuminate() } } 



    Pregunta 3
    ¿Es posible agregar una propiedad almacenada a un tipo usando la extensión ? ¿Cómo o por qué no?

    la respuesta
    No, 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 respuesta
    Un 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 escritas
    Pregunta 1
    Supongamos 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 respuesta
    Swift 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 2
    Swift 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 respuesta
    La 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 3
    El 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 respuesta
    En 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 4
    El 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 respuesta
    El 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 orales
    Pregunta 1
    ¿Son los cierres un tipo de referencia o tipo de valor?

    la respuesta
    Los 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 2
    Utiliza 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 respuesta
    Hay 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 respuesta
    Las 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 4
    Swift 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 respuesta
    Olvidamos la palabra clave indirecta , que permite opciones de enumeración recursiva similares:

     enum List<T> { indirect case node(T, List<T>) } 




Source: https://habr.com/ru/post/449410/


All Articles