As estruturas e as classes são dois dos principais blocos de construção da programação em Swift. Ambas são formas de agrupar dados e funções relacionados em unidades de funcionalidade autónomas. E quando defines uma estrutura ou uma classe, estás a definir um novo tipo para usar em Swift, tal como usaste os tipos com que já trabalhaste, como Int e String.
Há muitas semelhanças entre estruturas e classes em Swift. Entre outras semelhanças, ambas conseguem armazenar valores em propriedades e fornecer funcionalidade através do uso de métodos.
Cada uma delas oferece também algumas funcionalidades adicionais, que estão fora do âmbito deste exercício.
Tanto as estruturas como as classes são definidas de forma semelhante. Começam com a palavra-chave adequada, seguida do nome do tipo que está a ser definido; a seguir vem o corpo da estrutura ou da classe, colocado entre chavetas.
O corpo pode ser composto por propriedades armazenadas, que são definidas e se comportam exatamente como constantes ou variáveis normais.
struct CharacterStats {
var health = 0.0
var speed = 0
var strength = 0
}
class GameCharacter {
var stats = CharacterStats()
var characterClass: String?
var name: String?
var active = false
let id = makeRandomID()
}
Como vimos acima, definir uma estrutura ou uma classe é apenas definir um novo tipo. É só o plano do que serão os valores desse tipo, mas não cria, na verdade, nenhum valor desse tipo com que possas trabalhar.
Para criar uma instância desse tipo, tens de escrever o nome do tipo seguido de um par de parênteses.
let someStats = CharacterStats()
let someCharacter = GameCharacter()
Isto cria valores desses tipos, em que as propriedades são preenchidas com os valores predefinidos fornecidos na definição. Repara que, em casos opcionais como a propriedade name de GameCharacter, se não for fornecido um valor, a propriedade assume nil por omissão, tal como acontece ao definir tipos opcionais normais em que não se fornece logo um valor.
No caso das estruturas, o Swift fornece automaticamente algo chamado inicializador membro a membro, em que os valores das propriedades da estrutura podem ser indicados dentro dos parênteses e substituem os valores predefinidos da definição.
let differentStats = CharacterStats(health: 100.0, speed: 6, strength: 18)
As propriedades das estruturas e das classes podem ser acedidas com a notação de ponto, em que o nome do valor é seguido de um ponto (.) e do nome da propriedade. Se uma propriedade de uma estrutura ou classe tiver propriedades próprias, também podes usar esta notação de ponto para aceder a essas propriedades aninhadas.
Esta notação pode ser usada tanto para obter o valor da propriedade como, quando permitido, para o alterar.
someStats.health
// => 0
someCharacter.name
// => nil
someStats.health = 87.3
someStats.health
// => 87.3
someCharacter.name = "Luther"
someCharacter.name
// => "Luther"
someCharacter.id = "new id"
// Error: Cannot assign to property: 'id' is a 'let' constant
Tal como as propriedades, que guardam dados nas tuas estruturas e classes, também podes definir métodos, que guardam funções na tua estrutura ou classe.
Os métodos são definidos da mesma forma que uma função normal, mas dentro do corpo da estrutura ou da classe. Repara que, se uma função alterar o valor de uma propriedade de uma estrutura, tem de ser precedida da palavra-chave mutating. Além disso, se uma propriedade puder ser alterada por um método, essa propriedade tem de ser definida com var e não com let, tal como as variáveis normais.
struct CharacterStats {
var health = 0.0
var speed = 0
var strength = 0
mutating func takeHit(_ damage: Double) {
health = max(0.0, health - damage)
}
func canLift(_ weight: Int) -> Bool {
weight < strength * 100
}
}
class GameCharacter {
var stats = CharacterStats()
var characterClass: String?
var name: String?
var active = false
let id: String = makeRandomID()
func takesDamage(_ damage: Double) {
stats.takeHit(damage)
if stats.health <= 0 {
active = false
}
}
func sayName() -> String {
return "My name is \(name ?? "no one"), my class is \(characterClass ?? "undetermined")"
}
func lift(_ weight: Int) -> String {
if stats.canLift(weight) {
return "No problem!"
} else {
return "Ooof! No way."
}
}
}
Estes métodos podem ser chamados com a notação de ponto, tal como as propriedades.
var myChar = GameCharacter()
myChar.stats = CharacterStats(health: 72.8, speed: 19, strength: 6)
myChar.active = true
myChar.lift(750)
// => "Ooof! No way."
myChar.takesDamage(80)
myChar.active
// => false
Cada instância de uma estrutura ou classe tem uma propriedade implícita chamada self, que se refere à própria instância.
A utilização mais comum de self é distinguir entre uma propriedade da instância e um parâmetro com o mesmo nome:
struct Position {
var x = 0
mutating func move(x: Int) {
// Here, 'self.x' refers to the struct's property,
// while 'x' refers to the parameter passed to the method.
self.x = x
}
}
Os tipos de Swift também disponibilizam a palavra-chave Self com maiúscula inicial, que se refere ao tipo da instância e não à própria instância.
Neste exercício, vais simular um sistema informático baseado em janelas. Vais criar janelas que podem ser movidas e redimensionadas e que mostram o seu conteúdo. A imagem seguinte é representativa dos valores com que vais trabalhar abaixo.
<--------------------- screenSize.width --------------------->
^ ╔════════════════════════════════════════════════════════════╗
| ║ ║
| ║ position.x,_ ║
| ║ position.y \ ║
| ║ \<----- size.width -----> ║
| ║ ^ *──────────────────────┐ ║
| ║ | │ title │ ║
| ║ | ├──────────────────────┤ ║
screenSize.height ║ | │ │ ║
| ║ size.height │ │ ║
| ║ | │ contents │ ║
| ║ | │ │ ║
| ║ | │ │ ║
| ║ v └──────────────────────┘ ║
| ║ ║
| ║ ║
v ╚════════════════════════════════════════════════════════════╝
Define uma estrutura chamada Size com duas propriedades do tipo Int, width e height, que guardam, respetivamente, a largura e a altura atuais da janela.
A largura e a altura iniciais devem ser 80 e 60, respetivamente.
Inclui um método resize(newWidth:newHeight:) que recebe novos parâmetros de largura e altura e altera as propriedades de acordo com o novo tamanho.
let size1080x764 = Size(width: 1080, height: 764)
// Returns Size
var size1200x800 = size1080x764
// Returns Size
size1200x800.resize(newWidth: 1200, newHeight: 800)
size1200x800.height
// Returns 800
Define uma estrutura chamada Position com duas propriedades do tipo Int, x e y, que guardam, respetivamente, a posição horizontal e vertical atual do canto superior esquerdo da janela.
Os valores iniciais de x e y devem ser ambos 0. A posição (0, 0) é o canto superior esquerdo do ecrã, com os valores de x a aumentar à medida que te deslocas para a direita e os valores de y a aumentar à medida que te deslocas para baixo.
Inclui um método moveTo(newX:newY:) que recebe novos parâmetros x e y e altera as propriedades de acordo com a nova posição.
var point = Position(x: 10, y: 20)
// Returns Position
point.moveTo(newX: 100, newY: -100)
point.y
// Returns -100
Define uma classe de janela com as seguintes propriedades:
title : String, valor inicial é "New Window"screenSize : Size, valor constante com width = 800 e height = 600size : Size, valor inicial é o valor predefinido da estrutura Size
position : Position, valor inicial é o valor predefinido da estrutura Position
contents : String?, valor inicial é nil
Deves também definir um inicializador vazio para a classe.
let window = Window()
window.title
// Returns "New Window"
resize(to:) : (Size) -> () - Este método recebe uma estrutura Size como parâmetro de entrada e tenta redimensionar a janela para o tamanho especificado. No entanto, o novo tamanho não pode exceder certos limites.x = 400, y = 300 e for pedido um redimensionamento para height = 400, width = 300, a janela será redimensionada para height = 300, width = 300, porque o ecrã não é suficientemente grande na direção y para acomodar totalmente o pedido.move(to:) : (Position) -> () - É semelhante a resize(to:), mas este método ajusta a posição da janela ao valor pedido, em vez do tamanho. Tal como em resize, a nova posição não pode exceder certos limites.x como para y.x = 250, y = 100 e for pedido um movimento para x = 600, y = 200, a janela será movida para x = 550, y = 200, porque o ecrã não é suficientemente grande na direção x para acomodar totalmente o pedido.update(title:) : (String) -> () - Este método define a propriedade title com o valor da string que foi passada.update(text:) : (String?) -> () - Este método define a propriedade contents com o valor da string opcional que foi passada.display() : () -> String - Este método devolve uma string que descreve o estado atual da janela. Por exemplo, se a janela tiver o title "My First Window", com a posição: x = 10, y = 100; o tamanho: width = 200, height = 150; e contents: "I 😍 my window", deve devolver a string: "My First Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n" - Se contents for nil, a última linha deve ser "[This window intentionally left blank]"O sistema de janelas deve ter um inicializador para que a janela possa ser criada com valores de entrada personalizados.
Cria outro inicializador para a classe Window.
O inicializador deve receber os seguintes parâmetros: title, contents e dois parâmetros opcionais, size e position.
Os parâmetros size e position devem ter como valores predefinidos os valores predefinidos das estruturas Size e Position.
let window = Window(title: "My First Window", contents: "I 😍 my window")
window.display()
// Returns "My First Window\nPosition: (0, 0), Size: (80 x 60)\nI 😍 my window\n"
let window2 = Window(title: "My Second Window", contents: "I 😍 my window", size: Size(width: 200, height: 150), position: Position(x: 10, y: 100))
window2.display()
// Returns "My Second Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n"
Inscreve-te no Exercism para aprenderes e dominares Swift com 35 conceitos116 exercícios, e mentoria humana real, tudo grátis.