구조체와 클래스는 Swift 프로그래밍을 이루는 주요 구성 요소 두 가지예요. 둘 다 관련된 데이터와 함수를 하나의 독립적인 기능 단위로 묶는 방법이고요. 그리고 구조체나 클래스를 정의한다는 건, 이미 다뤄 본 Int나 String 같은 타입을 사용하듯이, Swift 안에서 사용할 새로운 타입을 정의하는 거예요.
Swift에서 구조체와 클래스는 비슷한 점이 아주 많아요. 그중에서도 둘 다 값을 _프로퍼티_에 저장하고, _메서드_를 통해 기능을 제공할 수 있어요.
각각 추가적인 기능도 제공하지만, 그 부분은 이 연습 문제의 범위를 벗어나요.
구조체와 클래스는 거의 같은 방식으로 정의해요. 알맞은 키워드로 시작해서 정의하려는 타입의 이름을 쓰고, 그다음 중괄호 사이에 구조체나 클래스의 본문을 작성해요.
본문은 저장 프로퍼티로 구성될 수 있는데, 저장 프로퍼티는 일반 상수나 변수와 똑같이 정의되고 동작해요.
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()
}
위에서 언급했듯이, 구조체나 클래스를 정의하는 건 새로운 _타입_을 정의하는 것일 뿐이에요. 그 타입의 값이 어떤 모습일지에 대한 설계도일 뿐이고, 실제로 작업할 수 있는 그 타입의 값을 만들어 주지는 않아요.
그 타입의 _인스턴스_를 만들려면 타입 이름 뒤에 괄호 두 개를 써야 해요.
let someStats = CharacterStats()
let someCharacter = GameCharacter()
이렇게 하면 정의에서 제공한 기본값으로 프로퍼티가 채워진, 해당 타입의 값이 만들어져요. GameCharacter의 name 프로퍼티처럼 옵셔널인 경우에는 값을 제공하지 않으면, 값을 바로 주지 않고 일반 옵셔널 타입을 정의할 때처럼 그 프로퍼티의 기본값이 nil이 돼요.
구조체에서는 Swift가 자동으로 _멤버와이즈 이니셜라이저_라는 것을 제공하는데, 괄호 안에 구조체 프로퍼티의 값을 제공하면 정의의 기본값을 덮어쓸 수 있어요.
let differentStats = CharacterStats(health: 100.0, speed: 6, strength: 18)
구조체와 클래스의 프로퍼티는 _점 표기법_으로 접근할 수 있는데, 값의 이름 뒤에 점(.)을 찍고 프로퍼티 이름을 쓰는 방식이에요. 구조체나 클래스의 프로퍼티가 자체적인 프로퍼티를 가지고 있다면, 이 점 표기법으로 중첩된 프로퍼티에도 접근할 수 있어요.
이 표기법은 프로퍼티의 값을 가져올 때도, 허용된다면 값을 바꿀 때도 사용할 수 있어요.
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
구조체와 클래스에 데이터를 저장하는 프로퍼티처럼, 구조체나 클래스에 함수를 저장하는 _메서드_도 정의할 수 있어요.
메서드는 일반 함수와 같은 방식으로 정의하는데, 구조체나 클래스의 본문 안에만 정의해요. 구조체에서 함수가 프로퍼티의 값을 바꾼다면, 함수 앞에 mutating 키워드를 붙여야 한다는 점에 유의해요. 또한 프로퍼티가 메서드에 의해 바뀔 수 있다면, 그 프로퍼티는 일반 변수처럼 let 대신 var로 정의해야 해요.
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."
}
}
}
이 메서드들은 프로퍼티처럼 점 표기법으로 호출할 수 있어요.
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
구조체나 클래스의 모든 인스턴스에는 self라는 암시적 프로퍼티가 있는데, 이는 인스턴스 자기 자신을 가리켜요.
self는 주로 인스턴스 프로퍼티와 이름이 같은 매개변수를 구분할 때 가장 많이 사용해요.
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
}
}
Swift 타입에는 대문자로 시작하는 Self 키워드도 있는데, 이는 인스턴스 자체가 아니라 인스턴스의 타입을 가리켜요.
이 연습 문제에서는 윈도우 기반 컴퓨터 시스템을 시뮬레이션해요. 이동하고 크기를 조절할 수 있으며 내용을 표시하는 창을 만들어 봐요. 다음 그림은 아래에서 다룰 값들을 나타내요.
<--------------------- screenSize.width --------------------->
^ ╔════════════════════════════════════════════════════════════╗
| ║ ║
| ║ position.x,_ ║
| ║ position.y \ ║
| ║ \<----- size.width -----> ║
| ║ ^ *──────────────────────┐ ║
| ║ | │ title │ ║
| ║ | ├──────────────────────┤ ║
screenSize.height ║ | │ │ ║
| ║ size.height │ │ ║
| ║ | │ contents │ ║
| ║ | │ │ ║
| ║ | │ │ ║
| ║ v └──────────────────────┘ ║
| ║ ║
| ║ ║
v ╚════════════════════════════════════════════════════════════╝
width와 height라는 두 개의 Int 프로퍼티를 가진 Size라는 구조체를 정의해요. 각각 창의 현재 너비와 높이를 저장해요.
초기 너비와 높이는 각각 80과 60이어야 해요.
새 너비와 높이 매개변수를 받아 프로퍼티를 새 크기에 맞게 변경하는 resize(newWidth:newHeight:) 메서드를 포함해요.
let size1080x764 = Size(width: 1080, height: 764)
// Returns Size
var size1200x800 = size1080x764
// Returns Size
size1200x800.resize(newWidth: 1200, newHeight: 800)
size1200x800.height
// Returns 800
x와 y라는 두 개의 Int 프로퍼티를 가진 Position이라는 구조체를 정의해요. 각각 창의 왼쪽 위 모서리의 현재 가로 위치와 세로 위치를 저장해요.
x와 y의 초기값은 각각 0이어야 해요. 위치 (0, 0)은 화면의 왼쪽 위 모서리이고, 오른쪽으로 갈수록 x 값이 커지고 아래로 갈수록 y 값이 커져요.
새 x와 y 매개변수를 받아 프로퍼티를 새 위치에 맞게 변경하는 moveTo(newX:newY:) 메서드를 포함해요.
var point = Position(x: 10, y: 20)
// Returns Position
point.moveTo(newX: 100, newY: -100)
point.y
// Returns -100
다음 프로퍼티를 가진 창 클래스를 정의해요:
title : String, 초기값은 "New Window"screenSize : Size, width = 800이고 height = 600인 상수값size : Size, 초기값은 Size 구조체의 기본값position : Position, 초기값은 Position 구조체의 기본값contents : String?, 초기값은 nil
클래스에 대한 빈 이니셜라이저도 정의해야 해요.
let window = Window()
window.title
// Returns "New Window"
resize(to:) : (Size) -> () - 이 메서드는 Size 구조체를 입력으로 받아 창을 지정된 크기로 조절하려고 해요. 하지만 새 크기는 특정 범위를 넘을 수 없어요.x = 400, y = 300이고 height = 400, width = 300으로 크기 조절을 요청하면, 화면이 y 방향으로 요청을 완전히 수용할 만큼 크지 않기 때문에 창은 height = 300, width = 300으로 조절돼요.move(to:) : (Position) -> () - resize(to:)와 비슷하지만, 이 메서드는 크기가 아니라 창의 _위치_를 요청된 값으로 조절해요. resize와 마찬가지로 새 위치는 특정 한계를 넘을 수 없어요.x와 y 모두 0이에요.x = 250, y = 100이고 x = 600, y = 200으로 이동을 요청하면, 화면이 x 방향으로 요청을 완전히 수용할 만큼 크지 않기 때문에 창은 x = 550, y = 200으로 이동해요.update(title:) : (String) -> () - 이 메서드는 title 프로퍼티를 전달받은 문자열 값으로 설정해요.update(text:) : (String?) -> () - 이 메서드는 contents 프로퍼티를 전달받은 옵셔널 문자열 값으로 설정해요.display() : () -> String - 이 메서드는 창의 현재 상태를 설명하는 문자열을 반환해요. 예를 들어, 창의 title이 "My First Window"이고 위치가 x = 10, y = 100, 크기가 width = 200, height = 150, contents가 "I 😍 my window"라면 "My First Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n" 문자열을 반환해야 해요. contents가 nil이면 마지막 줄은 "[This window intentionally left blank]"여야 해요.윈도우 시스템에는 사용자 지정 입력으로 창을 만들 수 있도록 하는 이니셜라이저가 있어야 해요.
Window 클래스에 또 다른 이니셜라이저를 만들어요.
이니셜라이저는 title, contents, 그리고 두 개의 옵셔널 매개변수 size와 position을 받아야 해요.
size와 position 매개변수는 Size 구조체와 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"
Exercism에 가입하고 Swift 트랙을 개념 35개연습 문제 116개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.