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 escritasPergunta 1Considere 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 respostatutorial1.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 2Você 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 respostaSim, 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
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 3Esse 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 respostaO 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 4Este 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 respostaO 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 oraisPergunta 1O que é
opcional e quais problemas eles resolvem?
a respostaopcional 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 2Liste brevemente as principais diferenças entre
estrutura e
classe .
a respostaClasses suportam herança, mas estruturas não.
Classes são um tipo de referência, estruturas são um tipo de valor.
Pergunta 3O que são
genéricos e para que servem?
a respostaNo 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")
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 4Em alguns casos, não será possível evitar os opcionais implicitamente
desembrulhados . Quando e porque?
a respostaOs 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 5Quais são algumas maneiras de implantar opcional? Classifique-os em termos de segurança.
var x : String? = "Test"
Dica: apenas 7 maneiras.
a respostaO 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 escritasPergunta 1Qual é a diferença entre
nil e
.none ?
a respostaNã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 2Aqui 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 respostaThermometerStruct 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 3O que esse código produzirá e por quê?
var thing = "cars" let closure = { [thing] in print("I love \(thing)") } thing = "airplanes" closure()
a respostaSerá 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()
Pergunta 4Esta é 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])
Reescreva esta função como uma extensão Array para que você possa usá-la assim:
[1, 2, 3, 3].countUniques()
nota do tradutorAlgo 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 5Aqui 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 respostaA 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 6Reescreva o método da pergunta 5 usando a
instrução if let .
a respostaA 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 oraisPergunta 1No Objective-C, você declara uma constante assim:
const int number = 0;
E assim em Swift:
let number = 0
Qual a diferença?
a respostaNo
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 2Para 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 respostastatic 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() }
Pergunta 3É possível adicionar uma
propriedade armazenada a um tipo usando a
extensão ? Como ou por que não?
a respostaNã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 4O que é um protocolo no Swift?
a respostaUm 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 escritasPergunta 1Suponha 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 respostaSwift 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 2Swift 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 respostaA 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 3O 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 respostaEm 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 4O 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 respostaO 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 oraisPergunta 1Os fechamentos são um tipo de referência ou valor?
a respostaOs 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 2Você 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 respostaHá um inicializador para isso:
UInt(bitPattern: Int)
E use:
let myNegative = UInt(bitPattern: -1)
Pergunta 3Descreva referências circulares no Swift? Como eles podem ser consertados?
a respostaAs 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 4Swift 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 respostaEsquecemos a palavra-chave
indireta , que permite opções de enumeração recursiva semelhantes:
enum List<T> { indirect case node(T, List<T>) }