Swift és un nou llenguatge de programació per a aplicacions d'iOS, OS X, watchOS i tvOS que es basa en el millor de C i Objective-C, sense les limitacions de compatibilitat de C. Swift adopta patrons de programació segurs i afegeix característiques modernes perquè la programació sigui més fàcil, més flexible i més divertida. Fer taula rasa amb Swift, amb el suport de Cocoa i Touch framework, és una oportunitat per re-imaginar com funciona el desenvolupament de programari.
Swift ha sorgit en base a anys d'elaboració. Apple va posar les bases de Swift mitjançant l'avanç del nostre compilador, depurador i la infraestructura de framework existent. S'ha simplificat la gestió de memòria amb l'Automatic Reference Counting (ARC). S'ha modernitzat i estandarditzat a tot arreu. Objective-C mateix ha evolucionat per suportar blocs, literals de col·lecció i mòduls, la qual cosa permet l'adopció de les tecnologies de framework lingüístiques modernes sense interrupció. Gràcies a aquest treball preliminar, podem introduir un nou llenguatge per al futur del desenvolupament de programari d'Apple.
Swift resulta familiar als desenvolupadors d'Objective-C. Adopta la llegibilitat dels paràmetres amb nom d'Objective-C i el poder del model d'objectes dinàmic d'Objective-C. Proporciona un accés transparent als frameworks de Cocoa existents i una interoperabilitat de combinació directa amb el codi d'Objective-C. Construint sobre aquesta base comuna, Swift introdueix moltes funcions noves i unifica les parts procedimentals i orientades a objectes del llenguatge.
Swift és amable per als nous programadors. És el primer llenguatge de programació de sistemes de qualitat industrial que és tan expressiu i agradable com un llenguatge de script. És compatible amb playgrounds, una característica innovadora que permet als programadors experimentar amb el codi Swift i veure els resultats immediatament, sense la sobrecàrrega de construir i executar una aplicació.
Swift combina el millor del pensament dels llenguatges moderns amb la saviesa de la cultura d'enginyeria més àmplia d'Apple. El compilador està optimitzat per al rendiment i el llenguatge està optimitzat per al desenvolupament, sense renunciar a cap dels dos. Està dissenyat per escalar des d'un "hola, món" fins a un sistema operatiu complet. Tot això fa de Swift una inversió de futur i sòlida per als desenvolupadors i per a Apple.
Swift és una manera fantàstica d'escriure aplicacions per a iOS, OS X, watchOS i tvOS, i seguirà evolucionant amb noves característiques i capacitats.
Tour de Swift
La tradició suggereix que el primer programa en un nou llenguatge ha d'imprimir les paraules "Hola, món!" a la pantalla. En Swift, això es pot fer en una sola línia:
- print("Hello, world!")*
Valors simples.
Si heu escrit codi en C o Objective-C, aquesta sintaxi us resultarà familiar; en Swift, aquesta línia de codi és un programa complet. No cal importar una biblioteca independent per a funcionalitats com l'entrada/sortida o la gestió de cadenes. El codi escrit en l'àmbit global s'utilitza com a punt d'entrada per al programa, per la qual cosa no cal una funció main(). Tampoc cal escriure un punt i coma al final de cada declaració.
Aquest tour us dona prou informació per començar a escriure codi en Swift, mostrant com dur a terme una varietat de tasques de programació.
Utilitzem 'let' per fer una constant i 'var' per fer una variable. El valor d'una constant no té per què ser conegut en temps de compilació, però cal assignar-li un valor exactament una vegada. Això significa que podeu utilitzar constants per anomenar un valor que es determina una vegada, però s'utilitza en molts llocs.
Per exemple:
var myVariable = 42 myVariable = 50 let myConstant = 42
Una constant o variable ha de tenir el mateix tipus que el valor que li vulgueu assignar. No obstant això, no sempre s'ha d'escriure el tipus de forma explícita. Proporcionar un valor en crear una constant o variable permet al compilador inferir el seu tipus. En l'exemple anterior, el compilador infereix que myVariable és un número enter, ja que el seu valor inicial és un número enter.
Si el valor inicial no proporciona prou informació (o si no hi ha valor inicial), especifiqueu el tipus escrivint-lo després de la variable, separat per dos punts.
let implicitInteger = 70 let implicitDouble = 70.0 let explicitDouble: Double = 70
Els valors no es converteixen implícitament a un altre tipus. Si necessitem convertir un valor a un tipus diferent, crearem una instància del tipus desitjat.
let label = "The width is " let width = 94 let widthLabel = label + String(width)
Hi ha una manera encara més senzilla d'incloure valors en cadenes: escriure el valor entre parèntesis, i escriure una barra invertida (\) abans del parèntesi. Per exemple:
let apples = 3 let oranges = 5 let appleSummary = "I have \(apples) apples." let fruitSummary = "I have \(apples + oranges) pieces of fruit."
Creeu matrius i diccionaris utilitzant corxets ([]), i accediu als seus elements escrivint l'índex o una clau entre parèntesis. Es permet una coma després de l'últim element.
var shoppingList = ["catfish", "water", "tulips", "blue paint"] shoppingList[1] = "bottle of water" var occupations = [ "Malcolm": "Captain", "Kaylee": "Mechanic", ] occupations["Jayne"] = "Public Relations"
Per crear una matriu o diccionari buit, utilitzeu la sintaxi d'inicialització:
let emptyArray = [String]() let emptyDictionary = [String: Float]()
Si la informació de tipus es pot deduir, es pot escriure com una matriu buida [] i un diccionari buit com [:] - per exemple, quan s'estableix un nou valor per a una variable o quan es passa un argument a una funció.
shoppingList = [] occupations = [:]
Flux de control
Utilitzeu if i switch per fer condicionals i l'ús de for-in, for, while i repeat-while per fer bucles. Els parèntesis al voltant de la variable de condició o de bucle són opcionals. Es requereixen claus al voltant del cos.
let individualScores = [75, 43, 103, 87, 12] var teamScore = 0 for score in individualScores { if score > 50 { teamScore += 3 } else { teamScore += 1 } } print(teamScore)
En una sentència if, el condicional ha de ser una expressió booleana, això significa que el codi if puntuació{...} és un error, no una comparació implícita a zero.
Es pot utilitzar if i let per treballar amb valors que podrien faltar. Aquests valors es representen com a opcionals. Un valor opcional o bé conté un valor o conté nil per indicar que falta un valor. Escriviu un signe d'interrogació (?) després del tipus d'un valor per marcar-lo com a opcional.
var optionalString: String? = "Hello" print(optionalString == nil) var optionalName: String? = "John Appleseed" var greeting = "Hello!" if let name = optionalName { greeting = "Hello, \(name)" }
Si optionalName és nil, el condicional és fals i el codi entre claus s'omet. En cas contrari, el valor opcional es desempaqueta i s'assigna a la constant després de let, la qual cosa fa que el valor desempaquetat estigui disponible a l'interior del bloc de codi.
Una altra forma de gestionar els valors opcionals és proporcionar un valor per defecte utilitzant l'operador ??. Si el valor opcional no està present, s'utilitza el valor per defecte en el seu lloc.
let nickName: String? = nil let fullName: String = "John Appleseed" let informalGreeting = "Hi \(nickName ?? fullName)"
Els switches admeten qualsevol tipus de dades i una àmplia varietat d'operadors de comparació, no es limiten als nombres enters i a les proves d'igualtat.
let vegetable = "red pepper" switch vegetable { case "celery": print("Add some raisins and make ants on a log.") case "cucumber", "watercress": print("That would make a good tea sandwich.") case let x where x.hasSuffix("pepper"): print("Is it a spicy \(x)?") default: print("Everything tastes good in soup.") }
Es pot utilitzar let en un patró per assignar a una constant el valor que ha coincidit. Després d'executar el codi dins del case de la sentència switch que ha coincidit, el programa surt de la sentència switch. L'execució no continua al següent cas, així que no cal fer un break explíci al final del codi de cada cas. S'utilitza for-in per iterar sobre els elements d'un diccionari, proporcionant un parell de noms per utilitzar per a cada parell clau-valor. Els diccionaris són una col·lecció desordenada, per la qual cosa les seves claus i valors s'iteren en un ordre arbitrari.
let interestingNumbers = [ "Prime": [2, 3, 5, 7, 11, 13], "Fibonacci": [1, 1, 2, 3, 5, 8], "Square": [1, 4, 9, 16, 25], ] var largest = 0 for (kind, numbers) in interestingNumbers { for number in numbers { if number > largest { largest = number } } } print(largest)*
Utilitzarem while per repetir un bloc de codi fins que una condició canviï. La condició d'un bucle pot estar al final, assegurant que el bucle s'executa almenys una vegada.
var n = 2 while n < 100 { n = n 2 } print(n) var m = 2 repeat { m = m 2 } while m < 100 print(m)
Podem mantenir un índex en un bucle, ja sigui mitjançant l'ús de ..< per fer un rang d'índexs o escrivint una inicialització, condició i increment explícits. Aquests dos bucles fan el mateix:
*var firstForLoop = 0 for i in 0..<4 { firstForLoop += i } print(firstForLoop)
var secondForLoop = 0 for var i = 0; i < 4; ++i { secondForLoop += i } print(secondForLoop)*
Utilitzem ..< per fer un rang que omet el seu valor superior, i utilitzem ... per fer un rang que inclou tots dos valors.
Funcions i closures
Utilitzarem func per declarar una funció. Per cridar una funció necessitarem posar el seu nom amb una llista d'arguments entre parèntesis. Utilitzarem -> per separar els noms dels paràmetres i tipus del tipus de retorn de la funció.
func greet(name: String, day: String) -> String { return "Hello \(name), today is \(day)." } greet("Bob", day: "Tuesday")
Utilitzarem una tupla per fer un valor compost, per exemple, per retornar diversos valors d'una funció. Els elements d'una tupla es poden referenciar per nom o per número.
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) { var min = scores[0] var max = scores[0] var sum = 0 for score in scores { if score > max { max = score } else if score < min { min = score } sum += score } return (min, max, sum) } let statistics = calculateStatistics([5, 3, 100, 3, 9]) print(statistics.sum) print(statistics.2)
Les funcions també poden prendre un nombre variable d'arguments, recollint-los en una matriu.
func sumOf(numbers: Int...) -> Int { var sum = 0 for number in numbers { sum += number } return sum } sumOf() sumOf(42, 597, 12)*
Les funcions es poden anellar. Les funcions anellades tenen accés a les variables que han estat declarades a la funció externa. Podeu utilitzar les funcions anellades per organitzar el codi d'una funció que sigui llarg o complex.
func returnFifteen() -> Int { var y = 10 func add() { y += 5 } add() return y } returnFifteen()*
Les funcions són un tipus de primera classe. Això vol dir que una funció pot retornar una altra funció com el seu valor.
func makeIncrementer() -> ((Int) -> Int) { func addOne(number: Int) -> Int { return 1 + number } return addOne } var increment = makeIncrementer() increment(7)*
Una funció pot tenir una altra funció com un dels seus arguments.
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool { for item in list { if condition(item) { return true } } return false } func lessThanTen(number: Int) -> Bool { return number < 10 } var numbers = [20, 19, 7, 12] hasAnyMatches(numbers, condition: lessThanTen)*
Les funcions són en realitat un cas especial de les closures: blocs de codi que se poden cridar més tard. El codi d'una closure té accés a coses com les variables i funcions que estaven disponibles en l'àmbit on es va crear la closure, fins i tot si la closure està en un àmbit diferent quan s'executa, com ja hem vist en l'exemple anterior amb funcions anellades. Podem escriure una closure sense nom envoltant el codi amb claus ({}). S'utilitza in per separar els arguments i el tipus de retorn del cos.
numbers.map({ (number: Int) -> Int in let result = 3 number return result })
Teniu diverses opcions per escriure les closures de manera més concisa. Quan el tipus d'una closure ja és conegut, com el callback per a un delegat, es pot ometre el tipus dels seus paràmetres, el tipus de retorn, o totes dues coses. Les closures d'una sola sentència retornen implícitament el valor de la seva única sentència.
let mappedNumbers = numbers.map({ number in 3 number }) print(mappedNumbers)
Es pot fer referència als paràmetres per número en lloc de pel seu nom; aquest enfocament és especialment útil en closures molt curtes. Una closure passada com a últim argument d'una funció pot aparèixer immediatament després dels parèntesis. Quan la closure és l'únic argument d'una funció, es poden ometre els parèntesis per complet.
let sortedNumbers = numbers.sort { $0 > $1 } print(sortedNumbers)*
Objectes i classes
Utilitzarem class seguit del nom de la classe per crear una classe. Una declaració de propietat en una classe s'escriu de la mateixa manera que una declaració de constant o variable, excepte que és en el context d'una classe. De la mateixa manera, les declaracions de mètodes i funcions s'escriuen de la mateixa manera.
class Shape { var numberOfSides = 0 func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } }
Creeu una instància d'una classe posant parèntesis després del nom de la classe. Utilitzeu la sintaxi amb punt per accedir a les propietats i mètodes de la instància.
var shape = Shape() shape.numberOfSides = 7 var shapeDescription = shape.simpleDescription()
A aquesta versió de la classe Shape li falta una cosa important: un inicialitzador per configurar la classe quan es crea una instància. Utilitzem init per crear-ne un.
class NamedShape { var numberOfSides: Int = 0 var name: String init(name: String) { self.name = name } func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } }
Observeu com s'utilitza self per distingir la propietat del nom, del nom de l'argument de l'inicialitzador. Els arguments de l'inicialitzador es passen com una crida a la funció quan es crea una instància de la classe. Cada propietat necessita un valor assignat, ja sigui en la seva declaració (com passa amb numberOfSides) o en l'inicialitzador (com passa amb name).
Utilitzeu deinit per crear un deinicialitzador si cal realitzar alguna tasca de neteja abans que s'alliberi la memòria de l'objecte.
Les subclasses inclouen el nom de la seva superclasse després del nom de la classe, separats per dos punts. No hi ha cap requisit perquè les classes facin subclasse de qualsevol classe arrel estàndard, així que podeu incloure o ometre una superclasse segons sigui necessari.
Els mètodes d'una subclasse que sobreescriuen la implementació de la superclasse estan marcats amb override; la sobreescriptura d'un mètode per accident és detectada pel compilador com un error si no ho posem. El compilador també detecta mètodes amb anul·lació que en realitat no sobreescriuen cap mètode de la superclasse.
class Square: NamedShape { var sideLength: Double init(sideLength: Double, name: String) { self.sideLength = sideLength super.init(name: name) numberOfSides = 4 } func area() -> Double { return sideLength sideLength } override func simpleDescription() -> String { return "A square with sides of length \(sideLength)." } } let test = Square(sideLength: 5.2, name: "my test square") test.area() test.simpleDescription()
A més de les propietats simples que s'emmagatzemen, les propietats poden tenir un getter i un setter.
class EquilateralTriangle: NamedShape { var sideLength: Double = 0.0 init(sideLength: Double, name: String) { self.sideLength = sideLength super.init(name: name) numberOfSides = 3 }
var perimeter: Double { get { return 3.0 sideLength } set { sideLength = newValue / 3.0 } } override func simpleDescription() -> String { return "An equilateral triangle with sides of length \(sideLength)." } } var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle") print(triangle.perimeter) triangle.perimeter = 9.9 print(triangle.sideLength)
En el setter per a perimeter, el nou valor té el nom implícit newValue.
Podem proporcionar un nom explícit entre parèntesis després de set. Observem que l'inicialitzador per a la classe de triangle equilàter té tres etapes diferents:
- Mitjançant la configuració de les propietats que declara la subclasse.
- Cridant l'inicialitzador de la superclasse.
- Canviant el valor de les propietats definides per la superclasse. Qualsevol altra configuració que utilitzi mètodes getters o setters també es pot fer en aquest moment.
Si no cal calcular la propietat, però tot i així cal proporcionar el codi que s'executa abans i després d'establir un nou valor, utilitzeu willSet i didSet. El codi que proporcioneu s'executa quan el valor canvia fora d'un inicialitzador. Per exemple, la classe de sota s'assegura que la longitud del costat del seu triangle sigui sempre la mateixa que la longitud del costat del seu quadrat.
class TriangleAndSquare { var triangle: EquilateralTriangle { willSet { square.sideLength = newValue.sideLength } } var square: Square { willSet { triangle.sideLength = newValue.sideLength } } init(size: Double, name: String) { square = Square(sideLength: size, name: name) triangle = EquilateralTriangle(sideLength: size, name: name) } } var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape") print(triangleAndSquare.square.sideLength) print(triangleAndSquare.triangle.sideLength) triangleAndSquare.square = Square(sideLength: 50, name: "larger square") print(triangleAndSquare.triangle.sideLength)*
Quan es treballa amb valors opcionals, podem escriure ? abans d'operacions com mètodes, propietats i subíndexs. Si el valor abans de ? és nil, s'ignora tot el que hi ha després del ? i el valor de toda l'expressió és nil. En cas contrari, el valor opcional es desempaqueta, i tot el que hi ha després del ? actua sobre el valor desempaquetat. En tots dos casos, el valor de tota l'expressió és un valor opcional.*
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") let sideLength = optionalSquare?.sideLength*
Enumeracions i estructures
Utilitzeu enum per crear una enumeració. Al igual que les classes i tots els altres tipus amb nom, les enumeracions poden tenir mètodes associats.
enum Rank: Int { case Ace = 1 case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten case Jack, Queen, King func simpleDescription() -> String { switch self { //this en Java case .Ace: return "ace" case .Jack: return "jack" case .Queen: return "queen" case .King: return "king" default: return String(self.rawValue) } } } let ace = Rank.Ace let aceRawValue = ace.rawValue
En l'exemple anterior, el tipus del valor brut de l'enumeració és Int, per la qual cosa només cal especificar el primer valor. La resta de valors s'assignen en ordre. També podeu utilitzar cadenes o nombres de punt flotant com el tipus brut d'una enumeració. Utilitzeu la propietat rawValue per accedir al valor brut d'un cas d'enumeració. Utilitzeu l'inicialitzador init?(rawValue:) per crear una instància d'una enumeració a partir d'un valor brut.
if let convertedRank = Rank(rawValue: 3) { let threeDescription = convertedRank.simpleDescription() }
Els valors dels casos d'una enumeració són valors reals, no només una altra manera d'escriure els seus valors bruts. De fet, en els casos on no hi ha un valor brut significatiu, no cal proporcionar-ne cap.
enum Suit { case Spades, Hearts, Diamonds, Clubs func simpleDescription() -> String { switch self { case .Spades: return "spades" case .Hearts: return "hearts" case .Diamonds: return "diamonds" case .Clubs: return "clubs" } } } let hearts = Suit.Hearts let heartsDescription = hearts.simpleDescription()let heart = Suit.Spades let heartsDescription = heart.simpleDescription()*
Observem les dues formes en què es fa referència al cas de cors de l'enumeració a dalt: quan s'assigna un valor a la constant hearts, es fa referència al cas d'enumeració Suit.Hearts pel seu nom complet perquè la constant no té un tipus explíci especificat. A l'interior del switch, es fa referència al cas d'enumeració mitjançant la forma abreujada .Hearts perquè el tipus de self ja és conegut com a Suit. Podeu utilitzar la forma abreujada en qualsevol moment que el tipus del valor ja sigui conegut.
Utilitzeu struct per crear una estructura. Les estructures admeten molts dels mateixos comportaments que les classes, inclosos mètodes i inicialitzadors. Una de les diferències més importants entre les estructures i les classes és que les estructures quan es passen pel codi es copien sempre, mentre que les classes es passen per referència.
struct Card { var rank: Rank var suit: Suit func simpleDescription() -> String { return "The \(rank.simpleDescription()) of \(suit.simpleDescription())" }} let threeOfSpades = Card(rank: .Three, suit: .Spades) let threeOfSpadesDescription = threeOfSpades.simpleDescription()*
Una instància d'un cas d'enumeració pot tenir valors associats a la instància. Les instàncies de la mateixa enumeració poden tenir diferents valors associats a elles. Vosaltres proporcioneu els valors associats quan creeu la instància. Els valors associats i els valors bruts són diferents: el valor brut d'un cas d'enumeració és el mateix per a totes les seves instàncies, i es proporciona el valor brut quan es defineix l'enumeració.
Per exemple, considerem el cas de sol·licitar l'hora de sortida i posta de sol des d'un servidor. El servidor respon o bé amb la informació o bé amb informació d'error.
enum ServerResponse { case Result(String, String) case Error(String) } let success = ServerResponse.Result("6:00 am", "8:09 pm") let failure = ServerResponse.Error("Out of cheese.") switch success { case let .Result(sunrise, sunset): print("Sunrise is at \(sunrise) and sunset is at \(sunset).") case let .Error(error): print("Failure... \(error)")}*
Observeu com s'extreuen les hores de sortida i posta de sol a partir del valor de resposta del servidor com a part de la cerca del valor en els casos del switch.
Protocols i extensions
Utilitzeu protocol per declarar un protocol. protocol ExampleProtocol { var simpleDescription: String { get } mutating func adjust() }
Les classes, enumeracions i estructures poden adoptar totes protocols.
class SimpleClass: ExampleProtocol { var simpleDescription: String = "A very simple class." var anotherProperty: Int = 69105 func adjust() { simpleDescription += " Now 100% adjusted." } } var a = SimpleClass() a.adjust() let aDescription = a.simpleDescription struct SimpleStructure: ExampleProtocol { var simpleDescription: String = "A simple structure" mutating func adjust() { simpleDescription += " (adjusted)" } } var b = SimpleStructure() b.adjust() let bDescription = b.simpleDescription*
Observeu l'ús de la paraula clau mutating en la declaració de l'estructura simple per marcar un mètode que modifica l'estructura. La declaració de la classe simple no necessita cap dels seus mètodes marcat com a mutating perquè els mètodes d'una classe sempre poden modificar la classe.
Utilitzeu extension per afegir funcionalitat a un tipus existent, com ara nous mètodes i propietats calculades. Es pot utilitzar una extensió per afegir conformitat a un protocol a un tipus que es declara en un altre lloc, o fins i tot a un tipus que s'ha importat d'una biblioteca o framework.
extension Int: ExampleProtocol { var simpleDescription: String { return "The number \(self)" } mutating func adjust() { self += 42 } } print(7.simpleDescription)
Es pot utilitzar un nom de protocol igual que qualsevol altre tipus conegut, per exemple, per crear una col·lecció d'objectes que tenen tipus diferents però que tots s'ajusten a un únic protocol. Quan es treballa amb valors el tipus dels quals és un tipus de protocol, els mètodes fora de la definició del protocol no estan disponibles.
let protocolValue: ExampleProtocol = a print(protocolValue.simpleDescription) // print(protocolValue.anotherProperty) // Uncomment to see the error
Tot i que la variable protocolValue té un tipus en temps d'execució de SimpleClass, el compilador la tracta com el tipus donat ExampleProtocol. Això significa que no es pot accedir accidentalment als mètodes o propietats que la classe implementa a més de la seva conformitat amb el protocol.
Genèrics
Escriviu un nom dins de parèntesis angulars per fer una funció o tipus genèric.
func repeatItem<Item>(item: Item, numberOfTimes: Int) -> [Item] {
var result = [Item]()
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
repeatItem("knock", numberOfTimes:4)
Podeu crear formes genèriques de funcions i mètodes, així com de classes, enumeracions i estructures.
// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
case None
case Some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .None
possibleInteger = .Some(100)*
Utilitzeu where després del nom de tipus per especificar una llista de requisits, per exemple, per requerir que un tipus implementi un protocol, per requerir que dos tipus siguin el mateix, o per requerir que una classe tingui una superclasse particular.
func anyCommonElements <T: SequenceType, U: SequenceType where T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element> (lhs: T, _ rhs: U) -> Bool { for lhsItem in lhs { for rhsItem in rhs { if lhsItem == rhsItem { return true } } } return false } anyCommonElements([1, 2, 3], [3])anyCommonElements([1,2,3,4,5,6],[3])anyCommonElements([1,2,3,4,5,6],[7])
Escriure <T: Equatable> és el mateix que dir <T where T: Equatable>.
És possible que necessitem fer:
import Foundation
perquè funcioni correctament.
Més informació: