데이터를 용도에 맞게 묶어 표현하고자 할 때 유용하다. 구조체와 클래스는 프로퍼티와 메서드를 사용하여 구조화된 데이터와 기능을 가질 수 있다.
스위프트의 구조체의 인스턴스는 값 타입이고, 클래스의 인스턴스는 참조 타입이다. 지금까지 알아본 스위프트의 데이터 타입과 열거형은 모두 값 타입이다.
스위프트는 소스파일 하나에 여러 개의 구조체와 여러 개의 클래스를 정의하고 구현할 수 있다. 또, 중첩 함수와 마찬가지로 구조체 안에 구조체, 클래스 안에 클래스 등과 같이 중첩 타입의 정의 및 선언이 가능하다.
9.1 구조체
9.1.1 구조체 정의
구조체는 struct 키워드로 정의한다.
NOTE_구조체 명명법 대문자 카멜케이스를 사용하여 이름을 지어준다. 프로퍼티와 메서드는 소문자 카멜케이스를 사용하여 이름을 지어준다.
struct 구조체 이름 {
프로퍼티와 메서드들
}
[코드 9-1]에는 String 타입인 name과 Int 타입인 age라는 저장 프로퍼티가 있는 BasicInformation이라는 이름의 구조체를 정의했다.
/*
코드 9-1. BasicInformation 구조체 정의
*/
struct BasicInformation {
var name: String
var age: Int
}
9.1.2 구조체 인스턴스의 생성 및 초기화
구조체의 인스턴스를 생성하고 초기화하고자 할 때는 기본적으로 생성되는 멤버와이즈 이니셜라이저(11.1.5절 참고)를 사용한다. 구조체에 기본 생성된 이니셜라이저의 매개변수는 구조체의 프로퍼티 이름으로 자동 지정된다.
프로퍼티 값에 접근하고 싶다면 마침표(.)를 사용하면 된다. 구조체를 상수 let으로 선언하면 인스턴스 내부의 프로퍼티 값을 변경할 수 없고, 변수 var로 선언하면 내부의 프로퍼티가 var로 선언된 경우에 값을 변경해줄 수 있다.
/*
코드 9-2. BasicInformation 구조체의 인스턴스 생성 및 사용
*/
// 프로퍼티 이름(name, age)으로 자동 생성된 이니셜라이저를 사용하여 구조체를 생성한다.
var yagomInfo: BasicInformation = BasicInformation(name: "yagom", age: 99)
yagomInfo.age = 100 // 변경 가능
yagomInfo.name = "Seba" // 변경 가능
// 프로퍼티 이름(name, age)으로 자동 생성된 이니셜라이저를 사용하여 구조체를 생성한다.
let sebaInfo: BasicInformation = BasicInformation(name: "Seba", age: 99)
sebaInfo.age = 100 // 변경 불가! 오류!
기본 제공되는 멤버와이즈 이니셜라이저 외에 사용자 정의 이니셜라이저도 구현이 가능하다.
9.2 클래스
스위프트의 클래스는 부모클래스가 없더라도 상속 없이 단독으로 정의가 가능하다.
9.2.1 클래스 정의
클래스를 정의할 때는 class라는 키워드를 사용한다.
NOTE_클래스 명명법 대문자 카멜케이스를 사용하여 이름을 지어준다. 프로퍼티와 메서드는 소문자 카멜케이스를 사용하여 이름을 지어준다.
class 클래스 이름 {
프로퍼티와 메서드들
}
클래스 정의 방법은 구조체와 흡사하지만 클래스는 상속받을 수 있기 때문에 상속 받을 때는 클래스 이름 뒤에 콜론(:)을 써주고 부모클래스 이름을 명시한다.
class 클래스 이름: 부모클래스 이름 {
프로퍼티와 메서드들
}
[코드 9-3]에 정의된 클래스는 Float 타입인 height와 weight 저장 프로퍼티가 있는 Person 클래스이다.
/*
코드 9-3. Person 클래스 정의
*/
class Person {
var height: Float = 0.0
var weight: Float = 0.0
}
9.2.2 클래스 인스턴스의 생성과 초기화
클래스를 정의한 후, 인스턴스를 생성하고 초기화하고자 할 때는 기본적인 이니셜라이저를 사용한다. [코드 9-3]의 Person 클래스에서는 프로퍼티의 기본값이 지정되어 있으므로 전달인자를 통하여 따로 초깃값을 전달해주지 않아도 된다.
인스턴스의 프로퍼티 값에 접근하고 싶다면 마침표(.)를 사용하면 된다. 구조체와는 다르게 클래스의 인스턴스는 참조 타입이므로 클래스의 인스턴스를 상수 let으로 선언해도 내부 프로퍼티 값을 변경할 수 있다.
/*
코드 9-4. Person 클래스의 인스턴스 생성 및 사용
*/
var yagom: Person = Person()
yagom.height = 123.4
yagom.weight = 123.4
let jenny: Person = Person()
jenny.height = 123.4
jenny.weight = 123.4
기본 이니셜라이저 외에 사용자가 직접 이니셜라이저를 정의할 수도 있다.
9.2.3 클래스 인스턴스의 소멸
클래스의 인스턴스는 참조 타입이므로 더는 참조할 필요가 없을 때 메모리에서 해제된다. 이 과정을 소멸이라고 하는데 소멸되기 직전 deinit라는 메서드가 호출된다. 클래스 내부에 deinit 메서드를 구현해주면 소멸되기 직전 deinit 메서드가 호출된다. deinit 메서드는 클래스당 하나만 구현할 수 있으며, 매개변수와 반환 값을 가질 수 없다. deinit 메서드는 매개변수를 위한 소괄호도 적어주지 않는다.
/*
코드 9-5. Person 클래스의 인스턴스 생성 및 소멸
*/
class Person {
var height: Float = 0.0
var weight: Float = 0.0
deinit {
print("Person 클래스의 인스턴스가 소멸됩니다.")
}
}
var yagom: Person? = Person()
yagom = nil // Person 클래스의 인스턴스가 소멸됩니다.
보통 deinit 메서드에는 인스턴스가 메모리에서 해제되기 직전에 처리할 코드를 넣어준다.
9.3 구조체와 클래스 차이
먼저 구조체와 클래스의 같은 점을 알아보자.
- 값을 저장하기 위해 프로퍼티를 정의할 수 있다.
- 기능 실행을 위해 메서드를 정의할 수 있다.
- 서브스크립트 문법을 통해 구조체 또는 클래스가 갖는 값(프로퍼티)에 접근하도록 서브스크립트를 정의할 수 있다.
- 초기화될 때의 상태를 저장하기 위해 이시녈라이저를 정의할 수 있다.
- 초기구현과 더불어 새로운 기능 추가를 위해 익스텐션을 통해 확장할 수 있다.
- 특정 기능을 실행하기 위해 특정 프로토콜을 준수할 수 있다.
다음은 둘의 다른 점이다.
- 구조체는 상속할 수 없다.
- 타입캐스팅은 클래스의 인스턴스에만 허용된다.
- 디이니셜라이저는 클래스의 인스턴스에만 활용할 수 있다.
- 참조 횟수 계산(Reference Counting)은 클래스의 인스턴스에만 적용된다.
두 타입을 구분 짓는 가장 큰 차이점은 값 타입과 참조 타입이라는 것이다.
9.3.1 값 타입과 참조 타입
값 타입과 참조 타입의 가장 큰 차이는 '무엇이 전달되느냐'이다. 예를 들어 어떤 함수의 전달인자로 값 타입의 값을 넘기면 전달될 값이 복사되어 전달된다. 그러나 참조 타입이 전달인자로 전달될 때는 값을 복사하지 않고 참조(주소)가 전달된다.
/*
코드 9-6. 값 타임과 참조 타입의 차이
*/
struct BasicInformation {
let name: String
var age: Int
}
var yagomInfo: BasicInformation = BasicInformation(name: "yagom", age: 99)
yagomInfo.age = 100
// yagomInfo의 값을 복사하여 할당한다
var friendInfo: BasicInformation = yagomInfo
print("yagom's age : \(yagomInfo.age)") // 100
print("friend's age : \(friendInfo.age)") // 100
friendInfo.age = 999
print("yagom's age : \(yagomInfo.age)") // 100 - yagom의 값은 변동 없음
print("friend's age : \(friendInfo.age)") // 999 - friendInfo와 yagomInfo는 별개의 값을 가짐
class Person {
var height: Float = 0.0
var weight: Float = 0.0
}
var yagom: Person = Person()
var friend: Person = yagom // yagom의 참조를 할당
print("yagom's height: \(yagom.height)") // 0.0
print("friend's height: \(friend.height)") // 0.0
friend.height = 185.5
print("yagom's height: \(yagom.height)") // 185.5 - friend는 yagom을 참조하기 때문에 값이 변동된다.
print("friend's height: \(friend.height)")
// 185.5 - 이를 통해 yagom과 friend가 참조하는 곳이 같음을 알 수 있다.
값 타입의 데이터를 함수의 전달인자로 전달하면 메모리에 전달인자를 위한 인스턴스가 새로 생성된다. 생성된 새 인스턴스에는 전달하려는 값이 복사되어 들어간다. 반면 참조 타입의 데이터는 전달인자로 전달할 때 기존 인스턴스의 참조를 전달하므로 새로운 인스턴스가 아닌 기존의 인스턴스 참조를 전달한다.
클래스의 인스턴스끼리 참조가 같은지 확인할 때는 식별 연산자(Identity Operators)를 사용한다.
/*
코드 9-7. 식별 연산자의 사용
*/
class Person {
var height: Float = 0.0
var weight: Float = 0.0
}
var yagom: Person = Person()
let friend: Person = yagom // yagom의 참조를 할당
let anotherFriend: Person = Person() // 새로운 인스턴스를 생성
print(yagom === friend) // true
print(yagom === anotherFriend) // false
print(friend !== anotherFriend) // true
9.3.2 스위프트의 기본 데이터 타입은 모두 구조체
스위프트의 기본 타입(Bool, Int, Array, Dictionary, Set, String 등등)은 모두 구조체로 구현되어 있다. 이는 기본 데이터 타입은 모두 값 타입이라는 뜻이다.
9.4 구조체와 클래스 선택해서 사용하기
구조체와 클래스는 새로운 데이터 타입을 정의하고 기능을 추가한다는 점이 같다. 하지만 구조체 인스턴스는 항상 값 타입이고 클래스 인스턴스는 참조 타입이다. 둘의 용도는 다르다는 의미이다.
애플은 다음 조건 중 하나 이상에 해당한다면 구조체를 사용하는 것을 권장한다.
- 연관된 간단한 값의 집합을 캡슐화하는 것만이 목적일 때
- 캡슐화한 값을 참조하는 것보다 복사하는 것이 합당할 때ㅜ
- 구조체에 저장된 프로퍼티가 값 타입이며 참조하는 것보다 복사하는 것이 합당할 때
- 다른 타입으로부터 상속받거나 자신을 상속할 필요가 없을 때
NOTE_똑똑한 스위프트의 복사 처리 스위프트는 꼭 필요한 경우에만 '진짜 복사'를 한다. 컴파일러가 판단해서 꼭 복사를 할 필요가 없을 경우, 요소를 많이 갖는 큰 배열을 함수의 전달인자로 넘겨준다고 해서 꼭 모든 값을 메모리의 다른 공간에 복사해 넣지 않을 수도 있다는 뜻이다. 스위프트가 적절히 알아서 효율적으로 처리해준다.
'iOS > SWIFT' 카테고리의 다른 글
[Swift] Chapter 11. 인스턴스 생성 및 소멸 (0) | 2021.10.08 |
---|---|
[Swift] Chapter 10. 프로퍼티와 메서드 (0) | 2021.10.08 |
[Swift] Chapter 08. 옵셔널 (0) | 2021.10.08 |
[Swift] Chapter 07. 함수 (0) | 2021.10.08 |
[Swift] Chapter 06. 흐름 제어 (0) | 2021.10.08 |
댓글