Swift 4.0 vs Swift 3.0 - Diferencias y nuevas funciones

Publicado: 2021-10-05

El día que Apple lanza una nueva versión del conocido lenguaje Swift finalmente nos ha llegado. ¿Qué debemos esperar de él? Mientras temblamos de anticipación, hemos decidido presentar una pequeña descripción general de las nuevas actualizaciones que estarán presentes en la versión Swift 4 .

3 piedras angulares de Swift.

Como un lenguaje fantástico para escribir código, Swift tiene sus propios beneficios y se afirma que "sobrevive" al lenguaje Objective-C.

Le invitamos a leer sobre las principales diferencias entre Swift y Objective-C

Swift es rápido , seguro para escribir y muy expresivo . Podría usarse para escribir software en teléfonos y tabletas, computadoras de escritorio y servidores, aparentemente, en todo lo que ejecuta código. Te da la bienvenida para que juegues con él: con la aplicación Swift Playgrounds para aprender a codificar de Apple o usando Playgrounds en Xcode, puedes ver los resultados de tu trabajo de inmediato, sin necesidad de esforzarte por desarrollar y ejecutar una aplicación en primer lugar. Con cada nueva versión aditiva, se vuelve mejor y más rápido, y ese es el caso de la versión Swift 4.
¿Preparado listo?

Otra gran característica que posee Xcode 9 para Swift 4: no necesita preocuparse mucho por la próxima migración y descubrirá por qué mientras lee este artículo.

Hablando de eso, exploremos brevemente las nuevas características de bonbons y Swift 4 este otoño.

Empezando

El lenguaje en sí no es muy útil sin un IDE útil, que es Xcode en el mundo de los desarrolladores. Puede descargar la última versión de Xcode 9 desde Mac App Store o en la página de Descargas en el sitio de desarrolladores de Apple, pero primero asegúrese de tener una cuenta de desarrollador activa. Es bastante estable, por lo que puede reemplazar Xcode 8 con él para sus rutinas de codificación diarias.

También puede instalar múltiples versiones de Xcode usando xcversion

Si está comenzando un nuevo proyecto, está listo para comenzar. Pero si ya tiene un proyecto escrito en Swift 3.x, debe pasar por un proceso de migración.

Recomendamos probar primero en el patio de juegos, para acostumbrarse a usar las nuevas funciones.

Notará enlaces a las propuestas de Swift Evolution en el formato 'SE -____' mientras lee este artículo.

Migración a Swift 4

La migración de una versión principal de Swift a la siguiente siempre ha sido bastante intensa, especialmente de Swift 2.xa 3.0. Por lo general, toma entre 1 y 2 días por proyecto, pero la migración a Swift 4 es un poco más fácil y se puede pasar mucho más rápido.

Preparación previa a la migración

Xcode 9 admite no solo Swift 4, sino también una versión de transición 3.2, por lo que su proyecto debería compilarse sin grandes dificultades. Esto es posible porque el compilador Swift 4 y la herramienta de migración admiten ambas versiones del lenguaje. Puede especificar diferentes versiones de Swift por objetivo, esto es muy útil si algunas bibliotecas de terceros aún no se actualizaron o si tiene varios objetivos en su proyecto. Sin embargo, no solo el idioma, sino que el SDK también tiene algunos cambios, por lo que es muy probable que se deban aplicar algunas actualizaciones a su código a medida que Apple continúa mejorando las API del SDK ...

Herramienta de migración rápida

Como siempre, Apple proporciona la herramienta de migración Swift incluida en Xcode que puede ayudar a migrar desde la versión anterior de Swift. Puede iniciarlo en Xcode yendo a Editar -> Convertir -> A la sintaxis Swift actual ... y seleccionando qué objetivos desea convertir.

Luego se le preguntará qué preferencia de inferencia de Objective-C desea aplicar:

Como los cambios aditivos dominan en Swift 4, la herramienta de migración Swift administrará la mayoría de los cambios por usted.

CocoaPods

La mayoría de los desarrolladores utilizan el administrador de dependencias de CocoaPods para sus proyectos, ya que Swift Package Manager no es tan maduro como podría ser, aunque está mejorando muy rápido. Como se mencionó anteriormente, no todas las bibliotecas de terceros se actualizaron a Swift 4 todavía, por lo que podría ver errores al compilar algunas de ellas. Una posible solución para solucionar este problema es especificar Swift versión 3.2 para aquellos pods que aún no se actualizaron agregando un script post_install a su 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

Entonces corre

 pod install

Ahora puede compilar pods sin errores.

Examinemos los cambios y adiciones de la API de Swift 4.

Cambios y adiciones de API

Instrumentos de cuerda

String ahora se ajusta al protocolo Collection gracias a la propuesta SE-0163. ¿Recuerdas Swift 1.x?

No hay necesidad de characters propiedad de matriz ahora como se puede iterar sobre String directamente:

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

Esto también significa que puede usar cualquier método y propiedad de Collection en String , como count , isEmpty , map() , filter() , index(of:) y muchos más:

 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"

Nuevo tipo de Substring

Swift 4 trae un nuevo tipo de Substring que representa una subsecuencia de String (como se describe en SE-0163 mencionado anteriormente).

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

Tanto String como Substring ahora admiten el nuevo StringProtocol que los hace casi idénticos e interoperables:

 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 tiene una nota muy 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.

Lo que significa que Substring está destinado a usarse como almacenamiento temporal para String subsecuencia de String . Si desea pasarlo a algunos métodos u otras clases, conviértalo a String primero:

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

De todos modos, el sistema de tipos de Swift le ayudará a no pasar Substring algún lugar donde se espera String (asumiendo que no está utilizando el nuevo StringProtocol como tipo de parámetro).

Literales de cadena de varias líneas

SE-0168 introduce una sintaxis simple para cadenas literales de varias líneas con tres comillas dobles """ que significa que la mayoría de los formatos de texto (como JSON o HTML) o algunos textos largos se pueden pegar sin ningún 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. """

Escapar de líneas nuevas en literales de cadena

SE-0182 agrega la capacidad de escapar de líneas nuevas en literales de cadena de varias líneas con una barra invertida al final de la línea.

 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

Compatibilidad mejorada con Unicode

Swift 4 ofrece soporte para Unicode 9, lo que significa que los problemas con el recuento de caracteres Unicode ahora han desaparecido:

 "".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

Todos los cambios y las propuestas implementadas resaltadas anteriormente (como muchos más) están tomando sus raíces de un conjunto de características ampliamente descritas llamado String Manifesto.

Control de acceso

Swift 3 trajo un elemento muy contradictorio al control de acceso: el modificador de acceso fileprivate que puede ser realmente confuso.
Anteriormente, private modificador de nivel de acceso private se usaba para ocultar miembros de tipo de otros tipos y solo se podía acceder a los miembros privados mediante métodos y propiedades definidos en la definición de tipo, dejando a un lado las mismas extensiones de tipo, ya que no podían acceder a esos miembros.
fileprivate podría usarse para compartir el acceso de miembros de tipo, como propiedades y métodos, dentro del mismo archivo.
De hecho, el uso de private condujo a un problema cuando las extensiones de algún tipo no tenían acceso a miembros de ese tipo, por lo que usar fileprivate en tales circunstancias era una solución muy común, lo que ha llevado a otro problema: otros tipos en el El mismo archivo también podría acceder a esos miembros.

Swift 4 pone las cosas en orden al permitir que las extensiones del tipo accedan a miembros private de ese tipo en el mismo archivo que se describe en SE-0169:

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

Diccionario y conjunto

Inicializar Dictionary con Sequence

Dictionary se puede inicializar con Sequence ahora, pero no todas las secuencias se pueden pasar en este inicializador, solo aquellas que contienen tuplas (Key, Value) , donde Key es el tipo de clave del Diccionario y Value representa el tipo de valor del Diccionario:

 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]

Aquí la función zip crea un par ( Tuple ) a partir de 2 secuencias; puede leer más sobre esta función en la documentación de la biblioteca estándar de Swift.

Fusión de diccionarios

Puede especificar cómo se deben manejar las claves duplicadas al crear un diccionario a partir de una secuencia de Tuple s pasando un cierre al parámetro uniquingKeysWith , que se utiliza para combinar valores de 2 claves idénticas.

Ejemplo 1:

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

Aquí dejamos el primer valor ignorando todos los valores siguientes con la misma clave.

Ejemplo 2:

Contando cuántas veces aparece cada carácter en una cadena.

 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]

Ejemplo 3:

Usando el método de 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]

Subíndice con valor predeterminado

Anteriormente, una práctica común era utilizar el operador coalescente nulo para dar un valor predeterminado en caso de que el valor fuera nulo.

Rápido 3:

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

Swift 4 introduce un nuevo valor default en subíndices (parte de SE-0165):

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

También puede mutar un diccionario mientras lo suscribe con el valor predeterminado:

 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 y filtro específicos del diccionario

Agrupar elementos de secuencia

Tipos asociados restringidos en protocolos

La propuesta SE-0142 introduce la adición de cláusulas condicionales a las declaraciones de tipo asociadas.

 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

Archivado y serialización (codificación / decodificación)

Anteriormente, para serializar algún tipo personalizado, tendría que usar el protocolo NSCoding antiguo y conocido. El problema es que los tipos que no son de clase, como struct y enum , no pueden ajustarse a este protocolo, por lo que los desarrolladores no tenían nada que hacer más que usar trucos como proporcionar una capa adicional de compatibilidad mediante la creación de una clase anidada que podría ajustarse a NSCoding .

Swift 4 tiene una solución muy conveniente a este problema gracias a SE-0166, una introducción del 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 }

En un caso simple como este, todo lo que necesita es agregar la Codable protocolo Codable a todos sus tipos personalizados, el compilador hará toda la magia por usted. ¡Eso es todo!

Codable es un typealias para una composición de Decodable y Encodable protocolos, para que pueda declarar, por ejemplo, sólo Decodable conformidad protocolo si desea decodificar la instancia de tipo de datos JSON.

Codificación

Si desea realizar una serie o deserializar un Codable valor - usted tiene que utilizar y codificador o decodificador objeto. Swift 4 ya viene con un conjunto de codificadores / decodificadores para JSON y listas de propiedades, así como nuevos CocoaError s para diferentes tipos de errores que podrían producirse durante la codificación / decodificación. NSKeyedArchiver y NSKeyedUnarchiver también admiten 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" } ] }

Un pedazo de pastel, ¿no?

Descodificación

El decodificador se utiliza para deserializar el tipo Codable personalizado de los Data . No sabe qué tipo decodificar de los datos en sí, por lo que debe especificar qué tipo decodificar, por ejemplo, Employee o [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.

Nombres de claves personalizados

En la mayoría de los casos, los nombres que usamos en los tipos Swift personalizados no coinciden con las claves de los datos JSON que representan este tipo. Para crear una asignación entre los nombres de propiedades de tipo personalizado y las claves JSON, puede crear una enumeración anidada denominada CodingKeys que debe ajustarse al 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" } }

Decodificación personalizada

Si tiene un caso complejo, puede implementar su inicializador personalizado desde un 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 = "" } } }

Codificación de valor clave

Una de las funciones útiles que trae Swift 4 son Smart KeyPaths descritas en SE-0161. A diferencia de Swift 3 #keyPath() , que no está fuertemente tipado y funciona solo para miembros de Objective-C, Swift 4 KeyPath es una clase genérica, lo que significa que las rutas clave ahora están fuertemente tipadas. Veamos algunos ejemplos:

 struct User { var username: String }

La forma general de la ruta de la clave \<Type>.<path> , donde <Type> es un nombre de tipo y <path> es una cadena de una o más propiedades, por ejemplo, \User.username :

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

También puede escribir un nuevo valor con esta ruta clave si es mutable:

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

Las rutas clave no se limitan a un nivel de jerarquía:

 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"

Las rutas clave se pueden almacenar en una variable:

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

También puede utilizar rutas clave para propiedades opcionales y 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"

A pesar de que SE-0161 destaca subíndices de soporte en rutas clave, aún no se han implementado:

 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

Además de las nuevas rutas clave, la API de observación de valores clave también se ha actualizado en 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

Llame al método invalidate() si desea detener la observación

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

También se detiene cuando se desinfecta, así que asegúrese de guardarlo en una propiedad o en otro lugar si desea conservarlo.

Rangos unilaterales

SE-0172 introduce rangos "unilaterales", creados a través de versiones de prefijo / sufijo de los operadores de rango existentes, y un nuevo protocolo RangeExpression para simplificar la creación de métodos que toman diferentes tipos de rangos.

Secuencias infinitas

Puede usar un rango unilateral para construir una secuencia 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,"

Uso de rangos de una cara en la coincidencia de patrones

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

Subíndices genéricos

SE-0148 Los subíndices ahora pueden tener argumentos genéricos y 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

Limitar la inferencia de Objective-C

Swift 4 minimiza la inferencia de @objc limitándola a solo aquellos casos en los que la declaración debe estar disponible para Objective-C (SE-0160).
Esto disminuye el tamaño binario de su aplicación al no compilar código Objective-C redundante si no lo usa, y le da más control sobre cuándo se inferirá @objc. Las clases derivadas de NSObject ya no infieren @objc.

Pero hay algunas situaciones en las que el código Swift seguirá teniendo una inferencia implícita:

  • Declaraciones que tienen un atributo @objc

  • Declaraciones que satisfacen un requisito de un protocolo @objc

  • Declaraciones que tienen atributos @IBAction, @IBInspectable, @IBOutlet, @NSManaged, @GKInspectable

Para habilitar la inferencia de @objc para una clase completa, puede usar el nuevo atributo @objcmembers.
Para deshabilitar la inferencia de @objc para una extensión o función específica, agregue el nuevo atributo @nonobjc.

Composición de clases y protocolos

En Swift 4 ahora podemos componer protocolos junto con otros tipos de Swift:

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

Beneficios de Swift 4.

Las ventajas de Swift 4 son realmente enormes, como suele suceder cuando Apple lanza una nueva versión de idioma. Además del rendimiento mejorado del idioma, también ha estabilizado enormemente el proceso de migración. Volviendo a pensar en el proceso de migración de Swift 2.2 a 3.0, recordamos el perplejo proceso de transferir todas las dependencias. Los cambios de Swift 4.0 nos permiten dejar las bibliotecas de terceros sin realmente "reubicarlas"; solo necesita actualizar Swift.

También con respecto a las mejoras de Swift 4.0 vs 3.0, se cambió el tamaño de los archivos binarios compilados, lo que resultó en una disminución del tamaño de la aplicación; por ejemplo, la aplicación móvil solía pesar 20 MB, y en la versión más reciente de Swift ocupará alrededor de 17 MB. Y hay una diferencia básica entre Swift 4 y Swift 3: se ha solucionado el error y el lenguaje se ha vuelto un poco más rápido.

Han pasado años desde que Swift ha estado en uso y continúa evolucionando con cada actualización que viene. Con cada nuevo idioma, se renuevan nuevas perspectivas de desarrollo, desconocidas antes, surgen y esperamos explorar nuevos horizontes de iOS.

No se pierda nuestro artículo sobre MVP vs MVC vs MVVM vs VIPER para el desarrollo de iOS.

Escrito por Max Mashkov y Elina Bessarabova .