일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 깃헙
- FeedbackGenerator
- 이니셜라이저
- 스위프트
- 스토리보드
- SWIFT
- 알뜰폰
- without Storyboard
- 아이폰
- viewDidDisappear
- viewWillDisappear
- refreshControl
- IOS
- viewDidAppear
- do try catch
- 런치 스크린
- 클로저
- indicator style
- viewcontroller
- graceful termination
- loadView
- 뷰컨
- git
- 스크롤 인디케이터
- scroll indicator
- exit()
- 레이아웃 사이클
- 세븐모바일
- 우아한 앱종료
- swift haptic
- Today
- Total
예거's Bicycle for the mind
[Swift] 구조체(struct)와 클래스(class)의 공통점과 차이점, 클래스 인스턴스의 identity 의 개념 본문
[Swift] 구조체(struct)와 클래스(class)의 공통점과 차이점, 클래스 인스턴스의 identity 의 개념
유예거 2021. 10. 28. 05:30스위프트의 사용자 정의 타입으로는, 구조체(struct)와 클래스(class), 열거형(enum) 등이 있다.
이번 글에서는 구조체와 클래스의 공통점과 차이점에 대해 정리하고, 어떤 기준으로 둘 중에 하나를 선택해야 하는지 정리해보자!
구조체와 클래스의 공통점과 차이점
- 타입/인스턴스 프로퍼티를 가질 수 있다.
- 타입/인스턴스 메서드를 가질 수 있다.
- 서브스크립트 문법(subscript syntax)을 사용하여 값에 접근할 수 있다.
- 초기화 상태(initial state)를 만들기 위한 이니셜라이저를 정의할 수 있다.
- 기능적 확장이 가능하다.
- 프로토콜을 준수할 수 있다.
여기서, 클래스는 구조체가 가지고 있지 않은 별도의 능력이 있다.
- 클래스의 단일 상속이 가능하다. (구조체는 상속 불가)
- 타입 캐스팅(Type casting)을 사용하면 런타임 동안 클래스 인스턴스의 타입을 확인할 수 있고, 인스턴스의 타입을 슈퍼클래스 또는 서브클래스 타입처럼 다룰 수 있다.
- 디이니셜라이저(Deinitializer)를 사용하면, 클래스의 인스턴스에 할당된 리소스를 해제할 수 있다.
- 참조 카운팅(Reference counting)은 클래스 인스턴스에 대한 하나 이상의 참조를 허용한다.
어떤 기준으로 구조체와 클래스를 선택해야 할까?
애플 공식 문서에서는 아래와 같은 가이드라인을 제시했다.
- 기본적으로 구조체를 사용해라. (Use structures by default.)
- Objective-C 하고 상호운영성(interoperability)이 필요할 때 클래스를 사용해라.
- 데이터의 identity 를 다뤄야 할 필요가 있다면 클래스를 사용해라.
- 기능 구현을 공유하고 싶다면, (클래스 간의 상속이 아니라) 구조체를 프로토콜과 함께 사용해라.
여기서 identity 라는 개념이 굉장히 중요하다.
스위프트를 조금이라도 공부해봤다면, 구조체는 값 타입, 그리고 클래스는 참조 타입이란 걸 알고 있을 것이다.
프로퍼티의 값이 모두 동일한 2개의 클래스 인스턴스가 있다고 생각해보자.
이 둘은 identity operator (===)로 판단해보면 false 라는 결과가 나올 것이다.
이 부분은 예시 코드와 함께 살펴보자!
클래스의 인스턴스와 identity 의 개념
// 2개의 인스턴스 프로퍼티를 갖는 클래스 Person 을 만들어보자.
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
// 2개의 인스턴스 생성. 프로퍼티는 동일하지만, 인스턴스의 메모리 주소가 다르다.
let jager = Person(name: "예거", age: 30)
let mimic = Person(name: "예거", age: 30)
// jager 와 mimic 의 identity 비교
print(jager === mimic) // false (다른 identity)
구조체의 인스턴스를 만들고 그걸 var 또는 let 에 할당해준다면, 그건 인스턴스의 실체를 메모리에 저장하는 것이라고 볼 수 있다. (값 타입)
반면, 클래스의 인스턴스를 만들어 var 또는 let 에 할당해주는 것은, 인스턴스의 실체는 메모리 어딘가에 저장되고, 대신에 메모리 주소가 저장된다. 그 인스턴스의 실체를 가리키는 주소값이 할당되는 것이다.
위 예시에서 let 으로 선언한 인스턴스 jager, mimic 의 경우, 메모리 주소값이 할당되어 있다고 할 수 있다.
그 주소가 identity 인 것이고, identity operator (===) 를 통해 그 주소값이 동일한지 확인할 수 있다.
// 인스턴스 jager 의 주소값을 그대로 mimic 에 할당해준다면?
let jager = Person(name: "예거", age: 30)
let mimic = jager
// jager 와 mimic 의 identity 비교
print(jager === mimic) // true (같은 identity)
위 예시 처럼, 인스턴스 jager 의 주소값을 그대로 mimic 에 할당해준다면, === 로 비교했을 때 true 가 나온다.
두 객체는 같은 주소값을 저장하고 있으니, 같은 identity 라고 할 수 있다.
// 하나의 인스턴스를 계속 참조할 수 있다. (참조 카운팅 == 3)
let jager = Person(name: "예거", age: 30)
let mimic = jager
let copycat = mimic
// jager, mimic, copycat 의 identity 비교
print(jager === mimic) // true (같은 identity)
print(jager === copycat) // true (같은 identity)
print(mimic === copycat) // true (같은 identity)
하나의 인스턴스에 대한 참조는 계속 늘릴 수 있다.
참조하는 객체가 하나 늘어날 때마다, 참조 카운팅(Reference counting) 또한 하나씩 늘어난다.
위 예시에서는 jager, mimic, copycat 이 모두 동일한 identity 를 갖고 있으므로, 저 인스턴스는 총 3개의 참조 카운팅을 가지고 있다.
만약 앱 전체에서 하나의 클래스 인스턴스를 공유해야 한다면, 그 인스턴스를 전역으로(globally) 만들어서, 필요한 부분에서 그 인스턴스를 참조하게 만들 수도 있다.
이 개념이 싱글톤(Singleton)인데, 조만간 별도의 글로 정리해보겠다!
자 어쨌든, 클래스의 인스턴스는 동일한 메모리 주소(identity)를 참조하는 객체 숫자를 늘릴 수도, 줄일 수도 있다.
그러다 참조 카운팅이 0 이 된다면, 스위프트의 ARC(Automatic Reference Counting) 기능에 의해, 그 인스턴스는 메모리에서 해제될 것이다.
여기서 꼭 체크하고 넘어가면 좋을 개념이 있다.
상수(let)로 선언된 클래스 인스턴스의 프로퍼티 값을 변경할 수 있는 이유는?
// 상수(let)로 선언된 jager 와 mimic
let jager = Person(name: "예거", age: 30)
let mimic = jager
mimic.name = "미믹"
print(mimic.name) // 미믹
print(jager.name) // 미믹
우리는 let 은 변경할 수 없는 값인 상수. var 는 변경할 수 있는 변수라고 배웠다.
근데 위 예시를 보면, jager 와 mimic 모두 let 으로 선언했는데, mimic 인스턴스의 name 프로퍼티를 "미믹" 으로 변경할 수 있다. 둘은 같은 인스턴스를 참조하고 있기에, name 프로퍼티는 둘 다 "미믹" 으로 출력된다.
이런 일이 가능한 이유는, 위에서 언급했듯이, 클래스 인스턴스를 let, var 에 할당하면 인스턴스의 실체가 아니라 메모리 주소만 저장되기 때문이다.
우리는 메모리 주소값을 바꾸려고 시도한 게 아니라, 그 주소를 찾아가면 나오는 인스턴스의 name 프로퍼티를 변경한 것이다.
그리고 그 name 프로퍼티는 변수(var) 였기에 변경이 가능했던 것이다. 프로퍼티가 상수(let)였다면, 변경할 수 없었을 것이다.
Equatable 프로토콜을 통해 클래스 인스턴스들의 프로퍼티 값이 동일한지 확인할 수 있다.
혹시 클래스 인스턴스를 비교할 때, == 연산자는 사용할 수 없을까? 바로 시도해보자!
== 연산자를 바로 사용할 수 없다는 컴파일 에러가 뜬다.
하지만 Equatable 프로토콜을 사용하면, == 연산자를 커스터마이징해서 인스턴스 간의 비교가 가능하다는 사실!
// Person 클래스에 Equatable 프로토콜 적용
class Person: Equatable {
// Equatable 프로토콜을 준수하기 위한 requirement
static func == (lhs: Person, rhs: Person) -> Bool {
(lhs.name == rhs.name) && (lhs.age == rhs.age)
}
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let jager = Person(name: "예거", age: 30)
let mimic = jager
let copycat = Person(name: "예거", age: 30)
// == 와 === 를 함께 쓰며 비교해보자.
print(jager == mimic) // true. jager 와 mimic 의 프로퍼티는 동일하다.
print(jager === mimic) // true. 둘의 identity 또한 동일하다.
print(mimic == copycat) // true. mimic 과 copycat 의 프로퍼티는 동일하다.
print(mimic === copycat) // false. 둘의 identity 는 다르다. (다른 주소값)
print(mimic != copycat) // false. == 연산자를 정의했으므로, 그 반대를 뜻하는 != 연산자도 사용 가능하다.
Equatable 프로토콜을 준수하기 위해서, 타입 메서드의 형태로, 연산자 == 를 반드시 커스터마이징해줘야 한다.
이때, lhs 는 left hand side, rhs 는 right hand side 를 말한다. 쉽게 말하면 왼쪽, 오른쪽이다.
굳이 저 단어를 유지할 필요도 없다. left, right 로 바꿔도 구현에는 문제가 없다. ㅎㅎ
어쨌든 나는, 클래스가 가진 2개의 인스턴스 프로퍼티(name, age)가 동일하면, == 연산자로 비교했을 때 true 값을 반환하도록 만들었다.
name 프로퍼티가 똑같더라도, name 이 조금이라도 다르면 false 가 나오도록 && 연산자로 두 조건을 묶어줬다.
근데 클래스에 Equatable 프로토콜을 붙일 일이 있긴 할까?
라는 생각이 들기도 했지만, === 와 비슷하게 생겼으니까 그 차이점을 다시 한번 공부할 겸 만들어봤다. 😊
참고 링크
[Swift 공식 문서] Structures and Classes
[Apple Developer - Article] Choosing Between Structures and Classes
'iOS & Swift' 카테고리의 다른 글
[iOS] ViewController 클래스의 인스턴스는 어디서 어떻게 만들어질까? (0) | 2021.11.07 |
---|---|
[iOS] ViewController 의 역할과 애플 공식 문서에 나오는 UIView, interface, layout 의 차이점 (0) | 2021.11.06 |
[Swift] NameSpace(네임스페이스)란 무엇이고, 어떻게 만들면 좋을까? (3) | 2021.10.17 |
[Swift] 큰 숫자를 다룰 때 underscore(_)를 이용해 자릿수 표기하는 법 (0) | 2021.10.10 |
[Swift] 고차함수 map, compactMap 의 차이와 활용법 (0) | 2021.10.10 |