Entrevista: Swift. Perguntas e Respostas

A linguagem de programação Swift tem apenas quatro anos, mas já está se tornando a principal linguagem de desenvolvimento para iOS. Desenvolvendo para a versão 5.0, o Swift se tornou uma linguagem complexa e poderosa que atende tanto a paradigmas orientados a objetos quanto a funcionais. E a cada nova versão, ele adiciona ainda mais recursos.

Mas quão bem você realmente conhece Swift? Neste artigo, você encontrará exemplos de perguntas para uma entrevista Swift.

Você pode usar essas perguntas para entrevistar os candidatos para testar seus conhecimentos ou testar seus próprios conhecimentos. Se você não souber a resposta, não se preocupe: há uma resposta para cada pergunta.

As perguntas são divididas em três grupos:

  • Iniciante : para iniciantes. Você leu alguns livros e aplicou o Swift em seus próprios aplicativos.
  • Intermediário : adequado para quem está realmente interessado no idioma. Você já leu muito sobre isso e frequentemente experimenta.
  • Avançado : adequado para os desenvolvedores mais avançados - aqueles que gostam de entrar na selva de sintaxe e usar técnicas avançadas.

Existem dois tipos de perguntas para cada nível:

  • escrito : adequado para testes por e-mail, pois sugerem a escrita de código.
  • oral : pode ser usado ao falar ao telefone ou pessoalmente, pois há respostas suficientes em palavras.

Ao ler este artigo, mantenha o playground aberto para poder verificar o código da pergunta. Todas as respostas foram testadas no Xcode 10.2 e Swift 5 .

  • Iniciante


    perguntas escritas
    Pergunta 1
    Considere o seguinte código:

    struct Tutorial { var difficulty: Int = 1 } var tutorial1 = Tutorial() var tutorial2 = tutorial1 tutorial2.difficulty = 2 

    Quais são os valores de tutorial1.dificuldade e tutorial2.dificuldade ? Haveria alguma diferença se o Tutorial fosse uma aula? Porque

    a resposta
    tutorial1.dificuldade é 1 e tutorial2.dificuldade é 2.

    No Swift, estruturas são tipos de valor. Eles são copiados, não referenciados. A linha a seguir copia o tutorial1 e o atribui ao tutorial2 :

     var tutorial2 = tutorial1 

    Alterações no tutorial2 não afetam o tutorial1 .

    Se Tutorial fosse uma classe, tutorial1.dificuldade e tutorial2.dificuldade seriam iguais a 2. Classes no Swift são tipos de referência. Quando você altera a propriedade tutorial1, verá a mesma alteração no tutorial2 - e vice-versa.


    Questão 2
    Você declarou view1 com var e view2 com let . Qual é a diferença e a última linha é compilada?

     import UIKit var view1 = UIView() view1.alpha = 0.5 let view2 = UIView() view2.alpha = 0.5 //   ? 

    a resposta
    Sim, a última linha é compilada. view1 é uma variável e você pode atribuir seu valor a uma nova instância do UIView. Usando let , você pode atribuir um valor apenas uma vez, para que o seguinte código não seja compilado:

     view2 = view1 // : view2 is immutable 

    No entanto, o UIView é uma classe com semântica referencial, portanto, você pode alterar as propriedades do view2 - o que significa que o código será compilado .

    Pergunta 3
    Esse código classifica a matriz em ordem alfabética. Simplifique o fechamento, tanto quanto possível.

     var animals = ["fish", "cat", "chicken", "dog"] animals.sort { (one: String, two: String) -> Bool in return one < two } print(animals) 

    a resposta
    O Swift determina automaticamente o tipo de parâmetros de fechamento e o tipo de retorno, para que você possa removê-los :

     animals.sort { (one, two) in return one < two } 

    Você pode substituir os nomes de parâmetros usando a notação $ i :

     animals.sort { return $0 < $1 } 

    Os fechamentos que consistem em uma única instrução podem não conter a palavra-chave return . O valor da última instrução executada se torna o resultado do retorno do fechamento:

     animals.sort { $0 < $1 } 

    Por fim, como Swift sabe que os elementos da matriz estão em conformidade com o protocolo Equatable , você pode simplesmente escrever:

     animals.sort(by: <) 


    Upd: hummingbirddj simplificou ainda mais:
    Nesse caso, você pode ainda mais curto:
      animals.sort() 
    - Classifica em ordem crescente, funciona para tipos que implementam Comparável.


    Pergunta 4
    Este código cria duas classes: Endereço e Pessoa . Também são criadas duas instâncias da classe Person ( Ray e 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) 

    Suponha que Brian tenha se mudado para um novo endereço e você queira atualizar o registro dele da seguinte maneira:

     brian.address.fullAddress = "148 Tutorial Street" 

    Isso compila e executa sem erros. Mas, se você verificar agora o endereço de Ray , verá que ele também "se mudou" .

    O que aconteceu aqui e como podemos corrigi-lo?

    a resposta
    O endereço é uma classe e tem semântica de referência . Assim, sede é a mesma instância da classe compartilhada por ray e brian. Mudar a sede mudará o endereço de ambos.
    Para corrigir isso, você pode criar uma nova instância da classe Address e atribuí-la a Brian, ou declarar Address como uma estrutura em vez de uma classe .

    perguntas orais
    Pergunta 1
    O que é opcional e quais problemas eles resolvem?

    a resposta
    opcional permite que uma variável de qualquer tipo apresente uma situação " sem valor ". No Objetivo-C, "nenhum valor" estava disponível apenas em tipos de referência usando o valor nulo especial. Tipos de valor, como int ou float , não tinham esse recurso.
    Swift estendeu o conceito de "sem valor" para tipos de valor. A variável opcional pode conter um valor ou nulo , indicando a ausência de um valor.


    Questão 2
    Liste brevemente as principais diferenças entre estrutura e classe .

    a resposta
    Classes suportam herança, mas estruturas não.
    Classes são um tipo de referência, estruturas são um tipo de valor.


    Pergunta 3
    O que são genéricos e para que servem?

    a resposta
    No Swift, você pode usar genéricos em classes, estruturas e enumerações.

    Os genéricos corrigem o problema da duplicação de código. Se você possui um método que aceita parâmetros de um tipo, às vezes é necessário duplicar o código para trabalhar com parâmetros de outro tipo.

    Por exemplo, neste código, a segunda função é um "clone" da primeira, exceto que ela possui parâmetros de sequência, não inteiros.

     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, você combina duas funções em uma e ao mesmo tempo garante a segurança do tipo:

     func areTheyEqual<T: Equatable>(_ x: T, _ y: T) -> Bool { return x == y } areTheyEqual("ray", "ray") areTheyEqual(1, 1) 

    Como você está testando a igualdade, está restringindo tipos àqueles que estão em conformidade com o protocolo Equatable . Este código fornece o resultado desejado e impede a transferência de parâmetros do tipo errado.


    Pergunta 4
    Em alguns casos, não será possível evitar os opcionais implicitamente desembrulhados . Quando e porque?

    a resposta
    Os motivos mais comuns para usar opcionais implicitamente desembrulhados são:

    • quando você não pode inicializar uma propriedade que não é nula no momento da criação. Um exemplo típico é a saída no Interface Builder, que é sempre inicializada após o proprietário. Nesse caso especial, se tudo estiver configurado corretamente no Interface Builder, você garante que a tomada não é nula antes de usá-la.
    • para resolver o problema do loop de referências fortes , quando duas instâncias de classes se referem uma à outra e é necessária uma referência não nula para outra instância. Nesse caso, você marca o link de um lado como não proprietário e, do outro lado, usa expansão opcional implícita.


    Questão 5
    Quais são algumas maneiras de implantar opcional? Classifique-os em termos de segurança.

     var x : String? = "Test" 

    Dica: apenas 7 maneiras.

    a resposta
    O desembrulhamento forçado não é seguro.

     let a: String = x! 

    A implantação implícita ao declarar uma variável é insegura.

     var a = x! 

    A ligação opcional é segura.

     if let a = x { print("x was successfully unwrapped and is = \(a)") } 

    O encadeamento opcional é seguro.

     let a = x?.count 

    Nenhum operador coalescente é seguro.

     let a = x ?? "" 

    A declaração da Guarda está segura.

     guard let a = x else { return } 

    Padrão opcional - seguro.

     if case let a? = x { print(a) } 




  • Intermediário


    perguntas escritas
    Pergunta 1
    Qual é a diferença entre nil e .none ?

    a resposta
    Não há diferença, Optional.none (brevemente .none ) e nil são equivalentes.
    De fato, a seguinte declaração retornará true :

     nil == .none 


    O uso de zero é geralmente aceito e recomendado.


    Questão 2
    Aqui está um modelo de termômetro na forma de classe e estrutura. O compilador reclama da última linha. O que há de errado aí?

     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) 

    a resposta
    ThermometerStruct está declarado corretamente com uma função de mutação para alterar a variável interna. O compilador reclama que você está chamando o método registerTemperature da instância que foi criada com let , portanto, essa instância é imutável. Alterar let para var corrigirá o erro de compilação.

    Nas estruturas, você deve marcar métodos que modificam variáveis ​​internas como mutantes , mas não pode invocar esses métodos usando uma instância imutável.


    Pergunta 3
    O que esse código produzirá e por quê?

     var thing = "cars" let closure = { [thing] in print("I love \(thing)") } thing = "airplanes" closure() 


    a resposta
    Será impresso: eu amo carros. A lista de captura criará uma cópia da variável quando o fechamento for declarado. Isso significa que a variável capturada não alterará seu valor, mesmo após a atribuição de um novo valor.

    Se você omitir a lista de captura no fechamento, o compilador usará o link, não a cópia. A chamada de fechamento refletirá a alteração na variável:

     var thing = "cars" let closure = { print("I love \(thing)") } thing = "airplanes" closure() // Prints: "I love airplanes" 



    Pergunta 4
    Esta é uma função que conta o número de valores exclusivos em uma 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 } 

    Ele usa classificados, portanto, usa apenas tipos que estão em conformidade com o protocolo Comparável.

    Você pode chamar assim:

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


    Reescreva esta função como uma extensão Array para que você possa usá-la assim:

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


    nota do tradutor
    Algo muito doloroso é uma função monstruosa. Porque não:

     func countUniques<T: Hashable>(_ array: Array<T>) -> Int { return Set(array).count } 


    a resposta
     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 } } 



    Questão 5
    Aqui está uma função que divide duas duplas opcionais. Existem três condições que devem ser atendidas:
    • dividendo não deve ser nulo
    • o divisor não deve ser nulo
    • o divisor não deve 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! } 

    Reescreva esta função usando a declaração de guarda e não usando o desembrulho forçado.
    a resposta
    A declaração de guarda introduzida no Swift 2.0 fornece uma saída se a condição não for atendida. Aqui está um exemplo:

     guard dividend != nil else { return nil } 


    Você também pode usar a instrução guard para ligação opcional , após o qual a variável expandida estará disponível fora da instrução guard :

     guard let dividend = dividend else { return .none } 


    Então você pode reescrever a função assim:

     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 } 

    Observe a falta de descompactação forçada, pois já descompactamos o dividendo e o divisor e os colocamos em variáveis ​​imutáveis ​​não opcionais.

    Observe também que o resultado dos opcionais descompactados na declaração de guarda também está disponível fora da declaração de guarda.

    Você pode simplificar ainda mais a função agrupando instruções de guarda:

     func divide(_ dividend: Double?, by divisor: Double?) -> Double? { guard let dividend = dividend, let divisor = divisor, divisor != 0 else { return nil } return dividend / divisor } 



    Pergunta 6
    Reescreva o método da pergunta 5 usando a instrução if let .

    a resposta
    A instrução if let permite descompactar opcionais e usar esse valor dentro deste bloco de código. Fora dele, esses valores não estarão disponíveis.

     func divide(_ dividend: Double?, by divisor: Double?) -> Double? { if let dividend = dividend, let divisor = divisor, divisor != 0 { return dividend / divisor } else { return nil } } 




    perguntas orais
    Pergunta 1
    No Objective-C, você declara uma constante assim:

     const int number = 0; 

    E assim em Swift:

     let number = 0 

    Qual a diferença?

    a resposta
    No Objective-C, uma constante é inicializada no tempo de compilação com um valor que deve ser conhecido neste momento.

    Um valor imutável criado com let é uma constante definida em tempo de execução . Você pode inicializá-lo com uma expressão estática ou dinâmica. Portanto, podemos fazer isso:

     let higherNumber = number + 5 


    Observe que essa tarefa só pode ser feita uma vez.


    Questão 2
    Para declarar uma propriedade ou função estática para tipos de valor, o modificador estático é usado. Aqui está um exemplo para a estrutura:

     struct Sun { static func illuminate() {} } 

    E para as classes é possível usar modificadores estáticos ou de classe . O resultado é o mesmo, mas há uma diferença. Descreva-o.

    a resposta
    static torna uma propriedade ou função estática e sem sobreposição . O uso da classe substituirá uma propriedade ou função.

    Aqui, o compilador jurará na tentativa de substituir o 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() } } 



    Pergunta 3
    É possível adicionar uma propriedade armazenada a um tipo usando a extensão ? Como ou por que não?

    a resposta
    Não, isso não é possível. Podemos usar a extensão para adicionar um novo comportamento a um tipo existente, mas não podemos alterar o tipo em si ou sua interface. Para armazenar a nova propriedade armazenada, precisamos de memória adicional e a extensão não pode fazer isso.


    Pergunta 4
    O que é um protocolo no Swift?

    a resposta
    Um protocolo é um tipo que define um esboço de métodos, propriedades etc. Uma classe, estrutura ou enumeração pode usar um protocolo para implementar tudo isso. O protocolo em si não implementa a funcionalidade, mas a define.



  • Avançado


    perguntas escritas
    Pergunta 1
    Suponha que tenhamos uma estrutura que define o modelo de um termômetro:

     public struct Thermometer { public var temperature: Double public init(temperature: Double) { self.temperature = temperature } } 

    Para criar uma instância, escrevemos:

     var t: Thermometer = Thermometer(temperature:56.8) 

    Mas algo assim seria muito mais conveniente:

     var thermometer: Thermometer = 56.8 

    Isso é possível? Como

    a resposta
    Swift define protocolos que permitem inicializar um tipo usando literais por atribuição. Aplicar o protocolo apropriado e fornecer um inicializador público permitirá a inicialização usando literais. No caso do termômetro, implementamos ExpressibleByFloatLiteral :

     extension Thermometer: ExpressibleByFloatLiteral { public init(floatLiteral value: FloatLiteralType) { self.init(temperature: value) } } 


    Agora podemos criar uma instância como esta:

     var thermometer: Thermometer = 56.8 



    Questão 2
    Swift tem um conjunto de operadores predefinidos para operações aritméticas e lógicas. Também permite que você crie seus próprios operadores, unários e binários.

    Defina e implemente seu próprio operador de exponenciação (^^) de acordo com os seguintes requisitos:
    • leva dois int como parâmetros
    • retorna o resultado do aumento do primeiro parâmetro para a potência do segundo
    • processa corretamente a ordem das operações algébricas
    • ignora possíveis erros de estouro


    a resposta
    A criação de um novo operador ocorre em dois estágios: anúncio e implementação.

    A declaração usa a palavra-chave operator para especificar o tipo (unário ou binário), para especificar a sequência de caracteres do novo operador, sua associatividade e antiguidade de execução.

    Aqui o operador é ^^ e seu tipo é infix (binário). Associatividade está certa.

    Swift não tem antiguidade predefinida para exponenciação. Na álgebra, a exponenciação deve ser calculada antes da multiplicação / divisão. Assim, criamos uma ordem de execução personalizada, colocando a exponenciação maior que a multiplicação.

    Este anúncio:

     precedencegroup ExponentPrecedence { higherThan: MultiplicationPrecedence associativity: right } infix operator ^^: ExponentPrecedence 


    Esta é a implementação:

     func ^^(base: Int, exponent: Int) -> Int { let l = Double(base) let r = Double(exponent) let p = pow(l, r) return Int(p) } 



    Pergunta 3
    O código a seguir define a estrutura do Pizza e o protocolo Pizzeria com uma extensão para a implementação padrão do 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"]) } } 


    Agora definimos o restaurante Lombardis :

     struct Lombardis: Pizzeria { func makePizza(_ ingredients: [String]) -> Pizza { return Pizza(ingredients: ingredients) } func makeMargherita() -> Pizza { return makePizza(["tomato", "basil", "mozzarella"]) } } 

    O código a seguir cria duas instâncias do Lombardis . Qual deles faz margarita com manjericão?

     let lombardis1: Pizzeria = Lombardis() let lombardis2: Lombardis = Lombardis() lombardis1.makeMargherita() lombardis2.makeMargherita() 


    a resposta
    Em ambos. O protocolo Pizzeria declara o método makeMargherita () e fornece uma implementação padrão. A implementação do Lombardis substitui o método padrão. Como declaramos o método no protocolo em dois lugares, a implementação correta será chamada.

    Mas e se o protocolo não declarasse o método makeMargherita () e a extensão ainda fornecesse a implementação padrão, assim:

     protocol Pizzeria { func makePizza(_ ingredients: [String]) -> Pizza } extension Pizzeria { func makeMargherita() -> Pizza { return makePizza(["tomato", "mozzarella"]) } } 

    Nesse caso, apenas lombardis2 teria pizza com manjericão, enquanto lombardis1 não teria pizza, porque usaria o método definido em extensão.


    Pergunta 4
    O código a seguir não compila. Você pode explicar o que há de errado com ele? Sugira soluções para o problema.

     struct Kitten { } func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") } print(k) } 

    Dica: existem três maneiras de corrigir o erro.


    a resposta
    O bloco else da declaração de guarda requer uma opção de saída, usando return , lançando uma exceção ou chamando @noreturn . A solução mais simples é adicionar uma declaração de retorno .

     func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") return } print(k) } 

    Esta solução lançará uma exceção:

     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, aqui está a chamada para fatalError () , a função @noreturn .

     struct Kitten { } func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") fatalError() } print(k) } 




    perguntas orais
    Pergunta 1
    Os fechamentos são um tipo de referência ou valor?

    a resposta
    Os fechamentos são um tipo de referência. Se você atribuir um fechamento a uma variável e copiá-lo para outra variável, copie o link para o mesmo fechamento e sua lista de capturas.


    Questão 2
    Você usa o tipo UInt para armazenar um número inteiro não assinado. Ele implementa um inicializador para converter de um todo com um sinal:

     init(_ value: Int) 

    No entanto, o código a seguir não será compilado se você especificar um valor negativo:

     let myNegative = UInt(-1) 

    Inteiros assinados, por definição, não podem ser negativos. No entanto, é possível usar a representação de um número negativo na memória para convertê-lo em um número não assinado.

    Como posso converter um número inteiro negativo para UInt, mantendo sua representação na memória?

    a resposta
    Há um inicializador para isso:

     UInt(bitPattern: Int) 


    E use:

     let myNegative = UInt(bitPattern: -1) 



    Pergunta 3
    Descreva referências circulares no Swift? Como eles podem ser consertados?

    a resposta
    As referências circulares ocorrem quando duas instâncias contêm uma forte referência uma à outra, o que leva a um vazamento de memória devido ao fato de que nenhuma dessas instâncias pode ser liberada. Uma instância não pode ser liberada enquanto ainda houver fortes referências a ela, mas uma instância mantém a outra.

    Isso pode ser resolvido substituindo o link de um lado, especificando a palavra-chave fraca ou sem dono .


    Pergunta 4
    Swift permite criar enumerações recursivas. Aqui está um exemplo dessa enumeração que contém uma variante de Nó com dois tipos associativos, T e Lista:

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

    Haverá um erro de compilação. Do que perdemos?

    a resposta
    Esquecemos a palavra-chave indireta , que permite opções de enumeração recursiva semelhantes:

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




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


All Articles