A struktúrák és az osztályok a Swift-programozás két legfontosabb építőköve. Mindkettő arra való, hogy a kapcsolódó adatokat és függvényeket önálló funkcionális egységekbe csoportosítsuk. Amikor pedig struktúrát vagy osztályt definiálsz, valójában egy új típust hozol létre a Swiftben, éppúgy, ahogy a korábban megismert típusokat is használtad, például az Int-et és a String-et.
A Swiftben a struktúrák és az osztályok között számos hasonlóság van. Többek között mindkettő képes értékeket tárolni tulajdonságokban, és funkcionalitást nyújtani metódusok segítségével.
Mindkettő nyújt emellett néhány további funkciót, amelyekre ebben a feladatban nem térünk ki.
A struktúrákat és az osztályokat nagyjából ugyanúgy definiáljuk. Egy megfelelő kulcsszóval kezdődnek, amelyet a definiált típus neve követ, majd a struktúra vagy osztály törzse következik, kapcsos zárójelek között.
A törzs tárolt tulajdonságokból állhat, amelyeket ugyanúgy definiálunk, és ugyanúgy viselkednek, mint a hagyományos konstansok vagy változók.
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()
}
Ahogy fentebb említettük, egy struktúra vagy osztály definiálása csupán egy új típus definiálása. Ez csak egy tervrajz arra, hogy milyenek lesznek az adott típus értékei, de valójában nem hoz létre egyetlen ilyen típusú értéket sem, amellyel dolgozhatnál.
Ahhoz, hogy létrehozz egy példányt ebből a típusból, a típus nevét kell leírnod, majd utána egy pár zárójelet.
let someStats = CharacterStats()
let someCharacter = GameCharacter()
Ez létrehozza ezen típusok értékeit, ahol a tulajdonságok a definícióban megadott alapértelmezett értékekkel töltődnek fel. Vedd figyelembe, hogy opcionális esetekben, mint például a GameCharacter name tulajdonsága, ha nem adunk meg értéket, a tulajdonság értéke nil lesz, éppúgy, mint amikor hagyományos opcionális típusokat definiálunk úgy, hogy nem adunk meg azonnal értéket.
Struktúrák esetében a Swift automatikusan biztosít valamit, amit tagonkénti kezdőértéknek nevezünk. Ennek segítségével a struktúra tulajdonságaihoz a zárójelek között adhatsz meg értékeket, amelyek felülírják a definícióban szereplő alapértelmezett értékeket.
let differentStats = CharacterStats(health: 100.0, speed: 6, strength: 18)
A struktúrák és osztályok tulajdonságai pont jelöléssel érhetők el, ahol az érték nevét egy pont (.) követi, majd a tulajdonság neve. Ha egy struktúra vagy osztály valamely tulajdonságának saját tulajdonságai vannak, ezzel a pont jelöléssel ezek az egymásba ágyazott tulajdonságok is elérhetők.
Ez a jelölés arra is használható, hogy lekérdezzük a tulajdonság értékét, és ahol megengedett, meg is változtassuk azt.
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
A tulajdonságokhoz hasonlóan, amelyek az adatokat tárolják a struktúráidban és osztályaidban, metódusokat is definiálhatsz, amelyek függvényeket tárolnak a struktúrádban vagy osztályodban.
A metódusokat ugyanúgy definiáljuk, mint egy hagyományos függvényt, csak éppen a struktúra vagy osztály törzsén belül. Vedd figyelembe, hogy ha egy függvény megváltoztatja egy struktúra valamely tulajdonságának értékét, azt a mutating kulcsszóval kell megelőzni. Továbbá, ha egy tulajdonságot egy metódus megváltoztathat, akkor azt a tulajdonságot let helyett var-ral kell definiálni, éppúgy, mint a hagyományos változókat.
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."
}
}
}
Ezek a metódusok pont jelöléssel hívhatók meg, akárcsak a tulajdonságok.
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
A struktúrák és osztályok minden példányának van egy implicit self nevű tulajdonsága, amely magára a példányra utal.
A self leggyakoribb használata, hogy megkülönböztesse egymástól a példány egy tulajdonságát és egy vele azonos nevű paramétert:
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
}
}
A Swift-típusok emellett biztosítanak egy nagybetűs Self kulcsszót is, amely a példány típusára utal, nem magára a példányra.
Ebben a feladatban egy ablakokon alapuló számítógépes rendszert fogsz szimulálni. Létrehozol néhány ablakot, amelyeket mozgatni és átméretezni lehet, és amelyek megjelenítik a tartalmukat. A következő ábra szemlélteti azokat az értékeket, amelyekkel az alábbiakban dolgozni fogsz.
<--------------------- screenSize.width --------------------->
^ ╔════════════════════════════════════════════════════════════╗
| ║ ║
| ║ position.x,_ ║
| ║ position.y \ ║
| ║ \<----- size.width -----> ║
| ║ ^ *──────────────────────┐ ║
| ║ | │ title │ ║
| ║ | ├──────────────────────┤ ║
screenSize.height ║ | │ │ ║
| ║ size.height │ │ ║
| ║ | │ contents │ ║
| ║ | │ │ ║
| ║ | │ │ ║
| ║ v └──────────────────────┘ ║
| ║ ║
| ║ ║
v ╚════════════════════════════════════════════════════════════╝
Definiálj egy Size nevű struktúrát két Int tulajdonsággal, width és height, amelyek rendre az ablak aktuális szélességét és magasságát tárolják.
A kezdeti szélesség és magasság rendre 80 és 60 legyen.
Helyezz el benne egy resize(newWidth:newHeight:) metódust, amely átveszi az új szélesség és magasság paramétereit, és úgy módosítja a tulajdonságokat, hogy azok az új méretet tükrözzék.
let size1080x764 = Size(width: 1080, height: 764)
// Returns Size
var size1200x800 = size1080x764
// Returns Size
size1200x800.resize(newWidth: 1200, newHeight: 800)
size1200x800.height
// Returns 800
Definiálj egy Position nevű struktúrát két Int tulajdonsággal, x és y, amelyek rendre az ablak bal felső sarkának aktuális vízszintes és függőleges pozícióját tárolják.
Az x és az y kezdeti értéke egyaránt 0 legyen. A (0, 0) pozíció a képernyő bal felső sarka, ahol az x értéke jobbra haladva, az y értéke pedig lefelé haladva növekszik.
Helyezz el benne egy moveTo(newX:newY:) metódust, amely átveszi az új x és y paramétereket, és úgy módosítja a tulajdonságokat, hogy azok az új pozíciót tükrözzék.
var point = Position(x: 10, y: 20)
// Returns Position
point.moveTo(newX: 100, newY: -100)
point.y
// Returns -100
Definiálj egy ablakosztályt a következő tulajdonságokkal:
title : String, a kezdeti értéke "New Window"screenSize : Size, konstans érték, ahol width = 800 és height = 600size : Size, a kezdeti értéke a Size struktúra alapértékeposition : Position, a kezdeti értéke a Position struktúra alapértékecontents : String?, a kezdeti értéke nil
Az osztályhoz egy üres kezdőértéket is definiálnod kell.
let window = Window()
window.title
// Returns "New Window"
resize(to:) : (Size) -> () - Ez a metódus egy Size struktúrát vesz bemenetként, és megpróbálja a megadott méretre átméretezni az ablakot. Az új méret azonban nem léphet túl bizonyos határokat.x = 400, y = 300, és height = 400, width = 300 méretre kérsz átméretezést, akkor az ablak height = 300, width = 300 méretre lesz átméretezve, mivel a képernyő az y irányban nem elég nagy ahhoz, hogy teljesen befogadja a kérést.move(to:) : (Position) -> () - Ez hasonló a resize(to:) metódushoz, csakhogy ez a metódus az ablak pozícióját állítja a kért értékre a mérete helyett. Ahogy a resize esetében, az új pozíció sem léphet túl bizonyos határokat.x és y esetében is 0.x = 250, y = 100, és x = 600, y = 200 pozícióra kérsz mozgatást, akkor az ablak x = 550, y = 200 pozícióra lesz mozgatva, mivel a képernyő az x irányban nem elég nagy ahhoz, hogy teljesen befogadja a kérést.update(title:) : (String) -> () - Ez a metódus a title tulajdonságot a neki átadott string értékére állítja.update(text:) : (String?) -> () - Ez a metódus a contents tulajdonságot a neki átadott opcionális string értékére állítja.display() : () -> String - Ez a metódus egy stringet ad vissza, amely leírja az ablak aktuális állapotát. Ha például az ablak title értéke "My First Window", a pozíciója x = 10, y = 100, a mérete width = 200, height = 150, a tartalma pedig "I 😍 my window", akkor a visszaadott string ez legyen: "My First Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n" - Ha a contents értéke nil, akkor az utolsó sor ez legyen: "[This window intentionally left blank]"Az ablakrendszernek rendelkeznie kell egy kezdőértékkel, hogy az ablak egyéni bemenetekkel is létrehozható legyen.
Hozz létre még egy kezdőértéket a Window osztályhoz.
A kezdőérték a következő paramétereket vegye át: title, contents, valamint két opcionális paraméter, a size és a position.
A size és a position paraméter alapértéke a Size és a Position struktúra alapértéke legyen.
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"
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) Swift nyelvet 35 fogalom116 feladat segítségével, valódi emberi mentorálással, mindez ingyen.