Swift 4.0 vs Swift 3.0 - Diferenças e novos recursos

Publicados: 2021-10-05

O dia em que a Apple lança uma nova versão da conhecida linguagem Swift finalmente nos atingiu. O que devemos esperar dele? Enquanto tremíamos de expectativa, decidimos apresentar uma pequena visão geral das novas atualizações que estarão presentes na versão 4 do Swift .

3 pilares do Swift.

Como uma linguagem fantástica para escrever código, o Swift tem seus próprios benefícios e afirma-se que “sobreviveu” à linguagem Objective-C.

Você está convidado a ler sobre as principais diferenças entre Swift e Objective-C

O Swift é rápido , seguro e muito expressivo . Ele poderia ser usado para escrever software em telefones e tablets, desktops e servidores - aparentemente, em tudo que executa código. Ele convida você a brincar com ele - com o aplicativo Swift Playgrounds de aprender a codificar da Apple ou usando o Playgrounds no Xcode, você pode ver os resultados do seu trabalho imediatamente, sem necessidade de se preocupar em desenvolver e executar um aplicativo em primeiro lugar. Com cada nova versão aditiva, ele se torna melhor e mais rápido, e esse é o caso da versão Swift 4.
Pronto-estável?

Outro ótimo recurso que o Xcode 9 possui para o Swift 4 - você não precisa se preocupar muito com a migração que está por vir e descobrirá o porquê ao ler este artigo.

Falando nisso, vamos explorar brevemente os novos recursos dos bombons e do Swift 4 neste outono.

Começando

A linguagem em si não é muito útil sem um IDE útil, que é o Xcode no mundo dos desenvolvedores . Você pode baixar a versão mais recente do Xcode 9 na Mac App Store ou na página de Downloads no site do desenvolvedor da Apple, mas primeiro certifique-se de ter uma conta de desenvolvedor ativa. É bastante estável, então você pode substituir o Xcode 8 por ele para suas rotinas diárias de codificação.

Você também pode instalar várias versões do Xcode usando xcversion

Se você está iniciando um novo projeto - você está pronto para ir. Mas se você já tem um projeto escrito em Swift 3.x - você deve passar por um processo de migração.

Recomendamos experimentar primeiro no Playground - para se acostumar com os novos recursos.

Você notará links para propostas do Swift Evolution no formato 'SE -____' enquanto lê este artigo.

Migrando para Swift 4

A migração de uma versão principal do Swift para a próxima sempre foi muito intensa, especialmente do Swift 2.x para a 3.0. Normalmente, leva cerca de 1 a 2 dias por projeto, mas a migração para o Swift 4 é um pouco mais fácil e pode ser aprovada com muito mais rapidez.

Preparação pré-migração

O Xcode 9 oferece suporte não apenas ao Swift 4, mas também a uma versão de transição 3.2, portanto, seu projeto deve ser compilado sem dificuldades severas. Isso é possível porque o compilador Swift 4 e a ferramenta de migração suportam as duas versões da linguagem. Você pode especificar diferentes versões do Swift por destino, o que é muito útil se algumas bibliotecas de terceiros ainda não foram atualizadas ou se você tiver vários destinos em seu projeto. No entanto, não apenas a linguagem, mas o SDK também sofreu algumas alterações, então é muito provável que algumas atualizações tenham que ser aplicadas ao seu código conforme a Apple continua a aprimorar as APIs do SDK ...

Ferramenta de migração rápida

Como sempre, a Apple fornece a ferramenta de migração Swift incluída no Xcode, que pode ajudar na migração da versão anterior do Swift. Você pode iniciá-lo no Xcode indo em Edit -> Convert -> To Current Swift Syntax ... e selecionando quais alvos você deseja converter.

Em seguida, será perguntado a você qual preferência de inferência Objective-C você deseja aplicar:

Como as mudanças aditivas predominam no Swift 4, a ferramenta de migração do Swift gerenciará a maioria das mudanças para você.

CocoaPods

A maioria dos desenvolvedores  usa o gerenciador de dependência CocoaPods para seus projetos, pois o Swift Package Manager não é tão maduro quanto poderia ser, embora esteja melhorando muito rápido. Como mencionado acima, nem todas as bibliotecas de terceiros foram atualizadas para o Swift 4 ainda, então você pode ver erros ao compilar algumas delas. Uma possível solução para corrigir esse problema é especificar a versão 3.2 Swift para os pods que ainda não foram atualizados, adicionando um script post_install ao seu Podfile :

 old_swift_3_pods = [ 'PodName1', 'PodName2', ] post_install do |installer| installer.pods_project.targets.each do |target| if old_swift_3_pods.include? target.name target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '3.2' end end end end

Então corra

 pod install

Agora você pode compilar pods sem erros.

Vamos examinar as alterações e adições do Swift 4 API.

Alterações e adições de API

Cordas

String agora está em conformidade com o protocolo Collection graças à proposta SE-0163. Lembra do Swift 1.x?

Não há necessidade de propriedade de matriz de characters agora, pois você pode iterar diretamente em String :

 let string = "Hello, Mind Studios!" for character in string { print(character) }

Isso também significa que você pode usar quaisquer métodos e propriedades de Collection em String , como count , isEmpty , map() , filter() , index(of:) e muitos mais:

 string.count // No more `string.characters.count` string.isEmpty // false let index = string.index(of: " ") // 6 let reversedCollection = "abc".reversed() let reversedString = String(reversedCollection) // "cba" // String filtering let string = "ni123n456iniASijasod! 78a9-kasd aosd0" let numbersString = string.filter { Int(String($0)) != nil } // "1234567890"

Novo tipo de Substring

O Swift 4 traz um novo tipo de Substring que representa uma subsequência de String (conforme descrito em SE-0163 mencionado acima).

 // Split string into substrings let string = "Hello, Mind Studios!" let parts = string.split(separator: " ") // ["Hello,", "Mind", "Studios!"] type(of: parts.first!) // Substring.Type

String e Substring agora oferecem suporte ao novo StringProtocol que os torna quase idênticos e interoperáveis:

 var hello = parts.first! // Concatenate a String onto a Substring hello += " !" // "Hello, !" // Create a String from a Substring let helloDog = String(hello) // "Hello, !"

Nota importante

SE-0163 tem uma observação muito importante:

 Long-term storage of `Substring` instances is discouraged. A substring holds a reference to the entire storage of a larger string, not just to the portion it presents, even after the original string's lifetime ends. Long-term storage of a substring may therefore prolong the lifetime of elements that are no longer otherwise accessible, which can appear to be memory leakage.

O que significa que Substring deve ser usado como um armazenamento temporário para subsequência de String . Se você quiser passá-lo para alguns métodos ou outras classes - converta-o primeiro para String :

 let substring: Substring = ... // Substring let string = String(substring) // String someMethod(string)

De qualquer forma, o sistema de tipos do Swift ajudará você a não passar Substring algum lugar onde String é esperada (assumindo que você não está usando o novo StringProtocol como tipo de parâmetro).

Literais de string multilinhas

SE-0168 apresenta uma sintaxe simples para literais de string de várias linhas usando três aspas duplas """ que significa que a maioria dos formatos de texto (como JSON ou HTML) ou algum texto longo pode ser colado sem nenhum escape:

 let multilineString = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam mattis lorem et leo laoreet fermentum. Mauris pretium enim ac mi tempor viverra et fermentum nisl. Sed diam nibh, posuere non lectus at, ornare bibendum erat. Fusce mattis sem ac feugiat vulputate. Morbi at nunc maximus, vestibulum orci et, dictum neque. Vestibulum vulputate augue ac libero vulputate vestibulum. Nullam blandit et sapien non fermentum. Proin mollis nisl at vulputate euismod. """

Fuga de novas linhas em literais de string

SE-0182 adiciona a capacidade de escapar de novas linhas em literais de string de várias linhas com uma barra invertida no final da linha.

 let escapedNewline = """ Line 1, Line 2 \ next part of line 2, Line 3 """ print(escapedNewline)
 Line 1, Line 2 next part of line 2, Line 3

Suporte a Unicode aprimorado

O Swift 4 traz suporte para Unicode 9, o que significa que os problemas com a contagem de caracteres Unicode acabaram:

 "".count // 1, in Swift 3: 2 "".count // 1, in Swift 3: 2 "".count // 1, in Swift 3: 2 - person + skin tone "".count // 1, in Swift 3: 4 "".count // 3, in Swift 3: 1

Todas as mudanças e propostas implementadas destacadas acima (como muitas outras) estão tomando suas raízes de um conjunto amplamente descrito de recursos chamado de Manifesto de String.

Controle de acesso

O Swift 3 trouxe um elemento muito contraditório para o controle de acesso - modificador de acesso fileprivate que pode ser muito confuso.
Anteriormente, private modificador de nível de acesso private era usado para ocultar membros de tipo de outros tipos e membros privados só podiam ser acessados ​​por métodos e propriedades definidas na definição de tipo, deixando as mesmas extensões de tipo de lado, pois não podiam acessar esses membros.
fileprivate pode ser usado para compartilhar o acesso para membros de tipo, como propriedades e métodos, dentro do mesmo arquivo.
Na verdade, o uso de private levava a um problema quando extensões em algum tipo não tinham acesso a membros desse tipo, então usar fileprivate em tais circunstâncias era uma solução muito comum, o que levou a outro problema: outros tipos no mesmo arquivo pode acessar esses membros também.

O Swift 4 coloca as coisas em ordem, permitindo que extensões do tipo acessem membros private desse tipo no mesmo arquivo, conforme descrito em SE-0169:

 struct User { private let firstName: String private let lastName: String } extension User: CustomStringConvertible { var description: String { return "User: \(firstName) \(lastName)" } }

Dicionário e conjunto

Inicializando Dictionary com Sequence

Dictionary pode ser inicializado com Sequence agora, mas nem todas as sequências podem ser passadas neste inicializador, apenas aquelas contendo tuplas (Key, Value) , onde Key é o tipo de chave do dicionário e Value representa o tipo de valor do dicionário:

 let stocksIdentifiers = ["AAPL", "GOOGL", "NKE"] let stocksValues = [158.28, 940.13, 53.73] let pairs = zip(stocksIdentifiers, stocksValues) let stocksValuesDict = Dictionary(uniqueKeysWithValues: pairs) // ["GOOGL": 940.13, "NKE": 53.73, "AAPL": 158.28]

Aqui a função zip cria um par ( Tuple ) a partir de 2 sequências, você pode ler mais sobre esta função na documentação da Biblioteca Padrão do Swift.

Mesclando dicionários

Você pode especificar como as chaves duplicadas devem ser tratadas ao criar um dicionário a partir de uma sequência de Tuple passando um fechamento para o parâmetro uniquingKeysWith , que é usado para combinar valores de 2 chaves idênticas.

Exemplo 1:

 let duplicates = [("a", 1), ("b", 5), ("a", 3), ("b", 3)] let dictionary = Dictionary(duplicates, uniquingKeysWith: { (first, _) in return first }) // ["b": 5, "a": 1]

Aqui deixamos o primeiro valor ignorando todos os próximos valores com a mesma chave.

Exemplo 2:

Contando quantas vezes cada caractere aparece na string.

 let string = "Hello!" let pairs = Array(zip(string, repeatElement(1, count: string.count))) let counts = Dictionary(pairs, uniquingKeysWith: +) // ["H": 1, "e": 1, "o": 1, "l": 2, "!": 1]

Exemplo 3:

Usando o método merge :

 let values = ["a": 1, "b": 5] var additionalValues = ["b": 3, "c": 2, "a": 3] additionalValues.merge(values, uniquingKeysWith: +) // ["b": 8, "c": 2, "a": 4]

Subscrito com valor padrão

Anteriormente, uma prática comum era usar o operador de coalescência nil para fornecer um valor padrão caso o valor fosse nulo.

Swift 3:

 let dict = ["a": 1, "b": 5] dict["c"] ?? 0 // 0

Swift 4 introduz um novo valor default nos subscritos (parte de SE-0165):

 let dict = ["a": 1, "b": 5] dict["c", default: 0] // 0, equals to `dict["c"] ?? 0` in Swift 3

Você também pode modificar um dicionário enquanto o subscreve com o valor padrão:

 let string = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam mattis lorem et leo laoreet fermentum. Mauris pretium enim ac mi tempor viverra et fermentum nisl. Sed diam nibh, posuere non lectus at, ornare bibendum erat. Fusce mattis sem ac feugiat vulputate. Morbi at nunc maximus, vestibulum orci et, dictum neque. Vestibulum vulputate augue ac libero vulputate vestibulum. Nullam blandit et sapien non fermentum. Proin mollis nisl at vulputate euismod. """ var wordsCountByLine = [Int: Int]() let lines = string.split(separator: "\n") for (index, line) in lines.enumerated() { let lineWordsCount = line.split(separator: " ").count wordsCountByLine[index, default: 0] += lineWordsCount } print(wordsCountByLine) // [2: 10, 4: 15, 5: 7, 6: 6, 7: 6, 0: 8, 1: 7, 3: 10]

Mapa e filtro específicos do dicionário

Elementos de sequência de agrupamento

Tipos associados restritos em protocolos

A proposta SE-0142 introduz a adição de cláusulas condicionais às declarações de tipo associadas.

 extension Sequence where Element: Numeric { var sum: Element { var result: Element = 0 for element in self { result += element } return result } } [1,2,3,4].sum

Arquivamento e serialização (codificação / decodificação)

Anteriormente, para serializar algum tipo personalizado, você teria que usar o protocolo NSCoding antigo e bem conhecido. O problema é que os tipos de não classe, como struct e enum não podem estar em conformidade com esse protocolo, então os desenvolvedores não tiveram nada a fazer a não ser usar hacks como fornecer camada adicional de compatibilidade criando uma classe aninhada que poderia estar em conformidade com o NSCoding .

O Swift 4 tem uma solução muito conveniente para este problema graças ao SE-0166 - uma introdução Codable protocolo Codable :

 struct Employee: Codable { let name: String let age: Int let role: Role enum Role: String, Codable { case manager case developer case admin } } struct Company { let name: String let officeLocation: Location? let employees: [Employee] } struct Location : Codable { let latitude: Double let longitude: Double }

Em um caso simples como este, tudo que você precisa é adicionar conformidade com o protocolo Codable a todos os seus tipos personalizados, o compilador fará toda a mágica para você. É isso!

Codable é um typealias para uma composição de Decodable & Encodable protocolos, para que possa declarar, por exemplo, só Decodable conformidade protocolo se você quiser decodificar sua instância tipo de dados JSON.

Codificação

Se você deseja serializar ou desserializar um valor Codable - você deve usar um codificador ou objeto decodificador. O Swift 4 já vem com um conjunto de codificadores / decodificadores para JSON e listas de propriedades, bem como novos CocoaError s para diferentes tipos de erros que podem ser lançados durante a codificação / decodificação. NSKeyedArchiver e NSKeyedUnarchiver também suportam tipos Codable .

 let employee = Employee(name: "Peter", age: 27, role: .manager) let company = Company(name: "Awesome Company", officeLocation: nil, employees: [employee]) let encoder = JSONEncoder() let companyData = try encoder.encode(company) let string = String(data: companyData, encoding: .utf8)! print(string) >>> { "name" : "Awesome Company", "employees" : [ { "name" : "Peter", "age" : 27, "role" : "manager" } ] }

Um pedaço de bolo, não é?

Decodificação

O decodificador é usado para desserializar o tipo Codable personalizado dos Data . Ele não sabe qual tipo decodificar dos próprios dados, então você deve especificar qual tipo decodificar, por exemplo, Employee ou [Employee] :

 let decoder = JSONDecoder() let jsonData = """ [ { "name" : "Peter", "age" : 27, "role" : "manager" }, { "name" : "Alex", "age" : 26, "role" : "developer" }, { "name" : "Eugene", "age" : 30, "role" : "admin" } ] """.data(using: .utf8)! let employees = try decoder.decode([Employee].self, from: jsonData)
 If one of `Codable` type instances fails to decode, then whole collection will fail to decode.

Nomes de chave personalizados

Na maioria dos casos, os nomes que usamos em tipos Swift personalizados não correspondem às chaves nos dados JSON que representam esse tipo. Para criar um mapeamento entre nomes de propriedades de tipo personalizado e chaves JSON, você pode criar um enum aninhado chamado CodingKeys que deve estar em conformidade com o protocolo CodingKey :

 struct Country: Decodable { let id: String let name: String let phoneCode: String private enum CodingKeys: String, CodingKey { case id = "alpha3" case name case phoneCode = "phone_code" } }

Decodificação Personalizada

Se você tiver um caso complexo, pode implementar seu inicializador personalizado a partir de um protocolo Decodable :

 struct Transaction { let id: Int let action: String let source: String let amount: Int let state: TransactionState let createdAt: Date let authorName: String enum TransactionState: String, Decodable { case done case canceled case processed } } extension Transaction: Decodable { private enum CodingKeys: String, CodingKey { case id case action = "action_name" case source = "source_name" case amount case state case createdAt = "created_at" case author } private enum AuthorKeys: String, CodingKey { case fullName = "full_name" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(Int.self, forKey: .id) actionName = try container.decode(String.self, forKey: .action) sourceName = try container.decode(String.self, forKey: .source) let createdAtValue = try container.decode(Double.self, forKey: .createdAt) createdAt = Date(timeIntervalSince1970: createdAtValue) state = try container.decode(TransactionState.self, forKey: .state) amount = try container.decodeIfPresent(Int.self, forKey: .amount) ?? 0 do { let authorContainer = try container.nestedContainer(keyedBy: AuthorKeys.self, forKey: .author) authorName = try authorContainer.decode(String.self, forKey: .fullName) } catch { authorName = "" } } }

Codificação de valor-chave

Um dos recursos úteis que o Swift 4 traz são os Smart KeyPaths descritos em SE-0161. Ao contrário do Swift 3 #keyPath() , que não é fortemente tipado e funciona apenas para membros Objective-C, o Swift 4 KeyPath é uma classe genérica, o que significa que os caminhos-chave agora são fortemente tipados. Vamos mergulhar em alguns exemplos:

 struct User { var username: String }

A forma geral do caminho da chave \<Type>.<path> , em que <Type> é um nome de tipo e <path> é uma cadeia de uma ou mais propriedades, por exemplo, \User.username :

 let user = User(username: "max") let username = user[keyPath: \User.username] // "max"

Você também pode escrever um novo valor por este caminho de chave se ele for mutável:

 var user = User(username: "max") user[keyPath: \User.username] = "alex" // "alex"

Os caminhos da chave não estão limitados a um nível de hierarquia:

 struct Comment { let content: String var author: User } let max = User(username: "max") let comment = Comment(content: "Nice post!", author: max) let authorUsername = comment[keyPath: \Comment.author.username] // "max"

Os caminhos da chave podem ser armazenados em uma variável:

 let authorKeyPath = \Comment.author let usernameKeyPath = authorKeyPath.appending(path: \.username) let authorUsername = comment[keyPath: usernameKeyPath] // "max"

Você também pode usar caminhos de chave para propriedades opcionais e calculadas:

 struct Post { let title: String var comments: [Comment] var topComment: Comment? { return comments.first } } let max = User(username: "max") let alex = User(username: "alex") var post = Post(title: "What's new in Swift 4", comments: []) let topCommentAuthorUsernameKeyPath = \Post.topComment?.author.username post[keyPath: topCommentAuthorUsernameKeyPath] // nil let comment = Comment(content: "", author: alex) let anotherComment = Comment(content: "Nice post!", author: max) post.comments = [comment, anotherComment] post[keyPath: topCommentAuthorUsernameKeyPath] // "alex"

Apesar de SE-0161 destacar subscritos de suporte em caminhos-chave, eles ainda não foram implementados:

 post.comments[keyPath: \.[0].content] // error: key path support for subscript components is not implemented let firstCommentAuthorKeyPath = \Post.comments[0].author // error: key path support for subscript components is not implemented

KVO

Além de novos caminhos de chave, a API de observação de valor-chave também foi atualizada no Swift 4.

 New KVO APIs depend on Objective-C runtime and works for `NSObject` subclasses only, so it can't be used for Swift structs and classes which don't inherit `NSObject`. In order to observe property it should be marked as `@objc dynamic var`.
 class User: NSObject { @objc dynamic var name: String var username: String init(name: String, username: String) { self.name = name self.userName = userName super.init() } } let user = User(name: "Max", username: "max") let nameObservation = user.observe(\.name, options: [.new, .old]) { user, change in // NSKeyValueObservation if let oldValue = change.oldValue, let newValue = change.newValue { print("fullName has changed from \(oldValue) to \(newValue)") } else { print("fullName is now \(user.name)") } } user.name = "Alex" // name has changed from Max to Alex

Chame o método invalidate() se quiser parar a observação

 nameObservation.invalidate() user.name = "Elina" // observer isn't get called

Ele também é interrompido quando definido, portanto, certifique-se de armazená-lo na propriedade ou em outro lugar se quiser preservá-lo.

Faixas Unilaterais

SE-0172 apresenta intervalos "unilaterais", criados por meio de versões de prefixo / postfix dos operadores de intervalo existentes e um novo protocolo RangeExpression para simplificar a criação de métodos que usam diferentes tipos de intervalos.

Seqüências infinitas

Você pode usar um intervalo unilateral para construir uma sequência infinita:

 let letters = ["a", "b", "c", "d"] let numberedLetters = Array(zip(1..., letters)) // [(1, "a"), (2, "b"), (3, "c"), (4, "d")]
 let string = "Hello, Mind Studios!" let index = string.index(of: ",")! string[..<index] // "Hello" string[...index] // "Hello,"

Usando intervalos de um lado na correspondência de padrões

 let value = 5 switch value { case 1...: print("greater than zero") case 0: print("zero") case ..<0: print("less than zero") default: break }

Subscrições genéricas

SE-0148 Subscripts agora podem ter argumentos genéricos e tipos de retorno

 struct JSON { let data: [String: Any] subscript<T>(key: String) -> T? { return data[key] as? T } } let jsonDictionary: [String: Any] = [ "name": "Ukraine", "flag": "", "population": 42_500_000 ] let json = JSON(data: jsonDictionary) let population: Int? = json["population"] // 42500600
 extension Dictionary where Value == String { subscript<T: RawRepresentable>(key: Key) -> T? where T.RawValue == Value { guard let string = self[key] else { return nil } return T(rawValue: string) } } enum Color: String { case red case green case blue } let dictionary = [1: "red"] let color: Color? = dictionary[1] // red

Limitando a inferência de Objective-C

O Swift 4 minimiza a inferência @objc limitando-a apenas aos casos em que a declaração deve estar disponível para Objective-C (SE-0160).
Isso diminui o tamanho binário do seu aplicativo ao não compilar o código Objective-C redundante se você não usá-lo, e oferece mais controle para quando @objc será inferido. As classes derivadas de NSObject não inferem mais @objc.

Mas existem algumas situações em que o código Swift continuará a ter uma inferência implícita:

  • Declarações que têm um atributo @objc

  • Declarações que satisfazem um requisito de um protocolo @objc

  • Declarações que têm atributos @IBAction, @IBInspectable, @IBOutlet, @NSManaged, @GKInspectable

Para habilitar a inferência @objc para uma classe inteira, você pode usar o novo atributo @objcmembers.
Para desabilitar a inferência @objc para uma extensão ou função específica - adicione o novo atributo @nonobjc.

Composição de classes e protocolos

No Swift 4, agora podemos compor protocolos junto com outros tipos de Swift:

 User & Codable & CustomStringConvertible typealias MyType = User & Codable & CustomStringConvertible

Benefícios do Swift 4.

As vantagens do Swift 4 são realmente enormes, como costuma acontecer quando a Apple lança uma nova versão em um idioma. Além do melhor desempenho do idioma, também estabilizou bastante o processo de migração. Voltando nossas mentes ao processo de migração do Swift 2.2 para 3.0, lembramos o processo complexo de transferência de todas as dependências. As alterações do Swift 4.0 nos permitem deixar as bibliotecas de terceiros sem realmente “realocá-las” - você só precisa atualizar o próprio Swift.

Ainda em relação às melhorias do Swift 4.0 vs 3.0, o tamanho dos arquivos binários compilados foi alterado, o que resultou na diminuição do tamanho do aplicativo; por exemplo, aplicativo móvel usado para pesar 20 MB, e na versão mais recente do Swift levará cerca de 17 MB. E há uma diferença básica entre o Swift 4 e o Swift 3 - a correção do bug aconteceu e a linguagem se tornou um pouco mais rápida.

Já se passaram anos desde que o Swift está em uso e continua a evoluir a cada atualização que vem. A cada nova linguagem, novas perspectivas de desenvolvimento, antes desconhecidas, surgem e estamos ansiosos para explorar novos horizontes iOS.

Não perca nosso artigo sobre MVP vs MVC vs MVVM vs VIPER para desenvolvimento iOS.

Escrito por Max Mashkov e Elina Bessarabova .