※ 본 포스팅은 유튜브 Swift Programming Tutorial 강의를 기반으로 제가 직접 정리한 Swift 문법에 대한 글입니다.
Swift에 대해 처음 배워나가는 과정을 블로그에 정리하였습니다. 기본적으로 다른 프로그래밍 언어를 다룬 적이 있기에 제가 아는 부분은 생략될 수 있습니다. 최대한 틀린 부분이 있지 않게 적겠지만 혹시 잘못된 부분이 있으면 알려주세요!
< Structures >
구조체(Structure)는 데이터를 저장하고 조작하기 위한 자료형입니다. 이 데이터는 구조체 내에 property라고 불리는 변수나 상수로 선언되어 저장합니다.
1. Structure 의 구조
구조체의 기본 구조는 다음과 같습니다.
struct Person {
let name: String
let age: Int
}
위의 코드는 Person이라는 구조체를 생성했습니다. 그리고 그 구조체 안에 name과 age라는 properties(속성)이 포함되었습니다. 속성은 변수 또는 상수로 선언합니다. 구조체의 속성은 구조체 외부에서도 접근할 수 있습니다. 즉, 구조체의 인스턴스를 생성하고 속성에 접근하여 값을 할당하거나 값을 읽을 수 있습니다.
Swift안에서 구조체는 value type입니다. 이는 구조체의 인스턴스를 또 다른 변수에 할당할 때 그 값이 복사되어 이동한다는 뜻입니다. 이는 Class와 Structure의 차이점 중 하나인데 이에 대해서는 이후에 좀 더 다루겠습니다.
let foo = Person(
name: "Foo",
age: 20
)
foo.name
foo.age
위의 코드를 보시면 Person structure의 instance를 상수 foo에 할당했다는 것을 알 수 있습니다. 예시처럼 구조체 안 속성들을 인스턴스에 dot notation을 이용해 접근할 수 있습니다.
2. Structure의 initializer
구조체의 생성자(Initializer)는 구조체를 초기화하는 역할을 합니다. 생성자는 객체를 생성할 때 호출되며, 속성의 초기값을 설정하거나 필요한 초기화 작업을 수행합니다. 구조체는 기본적으로 property 멤버별로 생성자를 컴파일러가 자동으로 만들기 때문에 class와 달리 굳이 직접 추가할 필요가 없습니다. (implicit constructor)
하지만 필요에 따라 커스텀 생성자를 정의할 수 있습니다.
struct CommodoreComputer {
let name: String
let manufacturer: String
init(name: String) {
self.name = name
self.manufacturer = "Commodore"
}
}
커스텀 생성자를 사용하려면 `init`키워드를 사용하여 생성자를 정의합니다. 생성자 내부에서는 `self`키워드를 사용하여 현재 인스턴스를 참조할 수 있습니다. `init`이 implicitly 함수 역할을 하는데, 그렇지만 생성자는 반환타입을 가지지 않습니다.
let c64 = CommodoreComputer(
name: "My commodore",
manufacturer: "Commodore"
)
let c128 = CommodoreComputer(
name: "My commodore 128",
manufacturer: "Commodore"
)
이런 식으로 구조체 인스턴스들을 만드는데 manufacturer가 항상 "Commodore"라면 이 값을 생성자를 통해 default 시킬 수 있지 않을까? 한 게 위의 init키워드를 이용한 코드 블록입니다.
let c64 = CommodoreComputer(name: "C64")
c64.name // C64
c64.manufacturer // Commodore
이와 같이 manufacturer가 init 생성자를 통해 고정이 됐으므로 굳이 manufacturer에 값을 할당하지 않아도 Commodore가 나오는 것을 알 수 있습니다.
사실 이렇게 복잡하게 하지 않아도 똑같은 결과를 만들어 낼 수 있습니다. 커스텀 생성자를 굳이 만드는 대신 `=` operator를 통해 default 값을 정할 수 있습니다.
struct CommodoreComputer {
let name: String
let manufacturer = "Commodore"
}
3. Computed Properties
Computed properties는 구조체 내부에 선언되며, 특정한 연산을 통해 값을 계산하는 속성입니다. 예시코드를 통해 설명드리겠습니다.
struct Person2 {
let firstName: String
let lastName: String
이러한 Person2라는 구조체가 있다고 생각해 봅시다. 우리는 fullName을 firstName과 lastName을 통해 구하고 싶은 상황입니다.
아마 이렇게 코드를 구현하면 되지 않을까 생각할 수 있습니다.
struct Person2 {
let firstName: String
let lastName: String
let fullName = "\(firstName) \(lastName)"
// 위의 fullName 문장은 오류가 남
이 코드에서는 아직 firstName과 lastName이 뭔지 모르기 때문에 Invalid 에러가 나게 됩니다. 그렇다면 이를 어떻게 처리할 수 있을까요?
struct Person2 {
let firstName: String
let lastName: String
var fullName: String {
"\(firstName) \(lastName)"
}
위 코드와 같이 우선 fullName은 Computed Property로 저장된 값을 가지지 않고, 매번 호출될 때마다 새로운 값을 반환하게 됩니다. 그렇기에 let이 아닌 var로 변수를 지정해 주고 function을 추가해 주는 형식처럼 { } 안에 계산될 코드를 적으면 됩니다.
이 상태를 실제 initializer로 표현하면 이렇습니다.
struct Person2 {
let firstName: String
let lastName: String
init(
firstName: String,
lastName: String
) {
self.firstName = firstName
self.lastName = lastName
self.fullName = "\(firstName) \(lastName)"
}
이렇게 initializer를 직접 만드는 것보다 더 간편하게 표현될 수 있는 것이 Computed Property입니다.
let fooBar = Person2(
firstName: "Foo",
lastName: "Bar"
)
fooBar.firstName // "Foo"
fooBar.lastName // "Bar"
fooBar.fullName // "Foo Bar"
코드 예시와 같이 fooBar라는 Person2 구조체 상수를 만들고 fullName을 확인해 보면, fullName도 출력이 되는 것을 알 수 있습니다.
4. mutable Structure
구조체는 원래 value type이기 때문에 기본적으로 변경을 허용하지 않습니다. 즉, 인스턴스가 한 번 생성되고 초기화된 후에는 해당 인스턴스의 속성을 변경할 수 없습니다. 이는 구조체의 불변성을 나타냅니다.
하지만 `mutating` 키워드를 이용하면 구조체의 속성을 변경할 수 있습니다.
struct Car {
var currentSpeed: Int
mutating func drive(speed: Int) {
"Driving..."
currentSpeed = speed
}
}
위 코드와 같이 mutating 키워드를 통해 Car 구조체의 instance가 바뀔 수 있는 함수를 만들 수 있습니다.
하지만 mutating 키워드를 이용해도 기본적인 구조체의 룰은 변경되지 않습니다.
한번 Car 구조체로 immutableCar 상수를 만들어보겠습니다.
let immutableCar = Car(currentSpeed: 10)
//immutableCar = Car(currentSpeed: 30) // 에러1
//immutableCar.drive(speed: 20) // 에러2
let으로 immutableCar상수를 만들었기에 재할당을 해도 let constant이기에 에러가 납니다. 즉 Internally change가 불가능하다는 뜻입니다.
그렇다면 drive 함수는 어떨까요? 이 역시 mutating 키워드를 달고 있지만 변수 자체가 let으로 지정되어 있기 때문에 아래와 같은 오류가 발생합니다.
Cannot use mutating member on immutable value: 'immutableCar' is a 'let' constant
그렇기에 `mutating`키워드를 썼더라도 구조체 안의 instance를 변경하고자 한다면 let이 아닌 var로 변수를 할당해줘야 합니다.
var mutableCar = Car(currentSpeed: 10)
mutableCar.drive(speed: 30)
mutableCar.currentSpeed // 30
위와 같이 var로 변수를 할당하면 Car의 구조체 instance인 currentSpeed가 30으로 변경되는 것을 확인할 수 있습니다.
앞서 말했듯이 mutating 키워드를 쓴다고 구조체의 value type이라는 성격이 변하는 것은 아닙니다. 한번 mutableCar의 copy를 만들어보겠습니다.
let copy = mutableCar
mutableCar.drive(speed: 30)
mutableCar.currentSpeed //30
copy.currentSpeed // 10
copy라는 변수로 mutable car을 복사했지만, 구조체는 value type이기에 여기서 copy와 mutableCar은 접점이 없는 각각 다른 개체라고 볼 수 있습니다.
코드 상에서도 mutableCar의 currentSpeed를 변경시켜도 copy의 currentSpeed는 변경되지 않는다는 것을 알 수 있습니다.
구조체가 mutable 하다고 해서 reference type처럼 연결된 게 아닌 value type의 성질을 가지고 있다는 사실은 잊지 말아야 합니다.
클래스에서 더 이야기하겠지만 구조체는 inheritance(상속)이 가능하지 않습니다. 이에 대해서는 클래스를 이야기하면서 더 설명하겠습니다.
여기까지 Structures 에 대한 정리였습니다. 다음 Swift 편은 Enumerations 에 대한 이야기로 돌아오겠습니다.
강의는 freeCodeCamp 채널의 영상인데, Vandad라는 분이 진행하는 7시간 분량의 full tutorial for beginners입니다. 해당 강의는 프로그래밍 언어를 Swift로 처음 접하는 사람들에게는 다소 어려울 수 있다고 생각합니다. 저는 기초적인 부분의 디테일한 중요성을 깊게 알려주는 것 같아서 강의를 택했습니다. 영어 강의이며 영어자막 또는 자동번역 기능이 제공되기에, 혹시 프로그래밍 언어를 다룬 적이 있는 분들 중 Swift에 대해 새롭게 배우고자 한다면 강추드립니다!
해당 강의 영상은 아래에 걸어두겠습니다. 혹시 틀린 부분이나 추가하고 싶은 부분이 있으면 알려주세요.
강의 전체를 시리즈로 적을 수 있도록 노력하겠습니다.
<이전글>
2023.05.15 - [TIL/Swift] - [Swift] Swift 기본 문법 정리 2. Operators & If-else
2023.05.23 - [TIL/Swift] - [Swift] Swift 기본 문법 정리 3. Functions
2023.05.28 - [TIL/Swift] - [Swift] Swift 기본 문법 정리 4. Closures