Swift의 Custom 타입에는 Enum, Class, Struct가 있다.
iOS 개발 진입 장벽이 높다고 하는 수많은 이유 중 하나가
Swift의 Class와 Struct 때문이라고 생각한다.
반대로 Class와 Struct를 잘 이해한다면
앞으로의 iOS 개발에 큰 도움이 될 것이다.
클래스와 구조체
우선, 클래스(Class)와 구조체(Struct)를 서로 거의 비슷한 쌍둥이라고 생각하자.
클래스/구조체는 흔히 붕어빵 틀에 비유가 되곤 하는데, 여기서는 신문공장을 예로 들겠다.
우리는 신문공장을 Newspaper Class/Struct라고 부른다.
공장은 아래와 같이 생겼다.
// 구조체로 선언하려면 class를 struct로 변경만 해주면 됨
class Newspaper {
var title = "물가 급상승"
var subTitle = "원자재/물류값 상승 탓"
func write() {
print("제목은 \(title), 부제목은 \(subTitle)")
}
}
공장(Class/Struct)에서는 제목(title)과 부제목(subTitle)이 있는 신문을 만들 수도 있고, 어떤 기능(write)를 추가할 수도 있다.
다만, 클래스/구조체 내부에서는 직접 메서드 실행문이 올 수 없다. (실행문을 함수 정의 내에서 쓰는 것은 가능)
이 때, Class/Struct 내부에서 title과 subTitle 변수를 속성(프로퍼티)이라고 부르고, write 함수를 메서드라고 부른다.
그리고 모두 멤버라고 칭한다.
그리고 이 공장을 활용하여 본격적으로 신문을 만드는 코드는 다음과 같다.
let morningPaper = Newspaper()
let nightPaper = Newspaper()
let todayPaper = Newspaper()
morningPaper이라는 구체적 실체를 갖춘 신문을 만들어냈다.
이 신문을 객체(인스턴스)라고 부른다.
이렇듯 공장을 활용해서 여러 종류의 신문을 쉽게 만들 수 있다.
객체 내 멤버 접근은 다음과 같다.
morningPaper.title
morningPaper.title = "사라진 모기"
morningPaper.subTitle = "이번 여름 온도가 낮아서..."
morningPaper.write() // "제목은 사라진 모기, 부제목은 이번 여름 온도가 낮아서..." 출력
프로퍼티 속성을 바꾸는 코드에서 Class와 Struct의 차이점이 있다.
객체를 let으로 선언했지만, Class에서는 내부 var로 선언된 속성 변경이 가능하고 Struct에서는 변경이 불가하다.
그 이유는 마지막 부분에서 다루기로 하자.
다만 객체를 var으로 선언했을 때는 둘다 변경이 가능하다.
Class 상속
상속이란 부모클래스(SuperClass)로부터 자식클래스(SubClass)에게 재산(멤버)을 물려주는 기능이다.
Struct에서는 상속을 구현할 수 없다.
위 Newspaper 부모클래스를 상속받은 자식클래스를 만들어보자.
class SportsNewspaper: Newspaper {
var sport = "축구"
var country = "영국"
override func write() {
super.write()
print("\(country) 내 \(sport) 종목에서 일어난 일")
}
}
위의 형식으로 상속을 선언한다.
그리고 override 키워드는 부모클래스의 함수를 새롭게 정의하고 싶을 때 사용한다.
다만 해당 함수 내에서 부모클래스 함수를 유지하면서 수정할 때, super 키워드와 함수명을 명시한다.
상속을 받았기 때문에 부모클래스의 속성 활용이 가능하다.
let spotv = SportNewspaper()
spotv.title
spotv.title = "한국에 온 토트넘"
spotv.subTitlte = "손흥민과 쿠팡의 합작"
spotv.write()
/*
출력
"제목은 한국에 온 토트넘, 부제목은 손흥민과 쿠팡의 합작"
"영국 내 축구 종목에서 일어난 일"
*/
초기화 메서드 Initializer
클래스와 구조체를 구현하면서 약간은 불편한 부분이 있다.
객체를 생성할 때, 신문공장에서 title과 subTitle 속성에 넣어준 값이 계속 할당된다는 것이다.
그래서 객체 생성 후, 다시 그 속성에 접근하여 값을 바꾸어 주었다.
Intializer는 이러한 점을 해결할 수 특수 메서드이다.
초기 클래스/구조체 선언 시점에서 프로퍼티 값 지정이 가능하다.
// 구조체로 선언하려면 class를 struct로 변경만 해주면 됨
class Newspaper {
var title: String
var subTitle: String
var content: String?
init(title: String, subTitle: String) {
self.title = title
self.subTitle = subTitle
}
func write() {
print("제목은 \(title), 부제목은 \(subTitle)")
}
}
init 메서드는 func 키워드를 사용하지 않는다.
초기 객체 선언 시 넣어줄 파라미터를 설정해주고, 위와 같이 속성에 초기화되도록 구현할 수 있다.
프로퍼티명과 파라미터명이 겹칠 경우, self 키워드를 사용하여 구분한다.
클래스/구조체의 저장 프로퍼티는 반드시 초기화가 되어있어야 한다.
따라서 title과 subTitle은 init을 통해 반드시 값이 할당되고, content는 빈 값을 대신하여 nil 값이 들어있게 된다.
Initializer를 통해 객체를 생성해보자.
let morningPaper = Newspaper(title: "편리한 속성 초기화 시점", subTitle: "그것은 이니셜라이즈 덕분")
// 아래와 동일
let morningPaper = Newspaper.init(title: "편리한 속성 초기화 시점", subTitle: "그것은 이니셜라이즈 덕분")
Initializer에서 설정한 파라미터를 반드시 명시하여야 한다.
사실상 클래스명 뒤에 .init으로 선언된 것과 똑같지만, 보통 .init은 생략한다.
Initializer에 기본값을 설정할 수도 있다.
// 구조체로 선언하려면 class를 struct로 변경만 해주면 됨
class Newspaper {
var title: String
var subTitle: String
var content: String?
init(title: String = "제목 미정", subTitle: String = "부제목 없음") {
self.title = title
self.subTitle = subTitle
}
func write() {
print("제목은 \(title), 부제목은 \(subTitle)")
}
}
이러한 경우 선언 시점에서 필요한 파라미터에만 값을 넣어줄 수 있다.
// 초기화 예시1
let paper = Newspaper(title: "떠나는 토트넘", subTitle: "성공적인 내한 이벤트 마무리")
// 초기화 예시2
let paper = Newspaper(title: "떠나는 토트넘")
// 초기화 예시3
let paper = Newspaper(subTitle: "성공적인 내한 이벤트 마무리")
클래스와 구조체의 차이
클래스
- (메모리주소)참조형식
- 인스턴스 데이터는 힙에 저장되고, 해당 힙을 가리키는 변수는 스택에 저장된다.
- 스택의 변수 메모리 주소 값이 힙을 가르킨다.
- 따라서 클래스를 복사 시, 저장된 주소를 전달한다.
- 상속이 가능하고, ARC를 통한 메모리 관리가 이루어진다.
- 주소값 비교연산자인 ===, !== 사용이 가능하다.
구조체
- 값형식
- 인스턴스 데이터를 모두 스택에 저장한다.
- 구조체 복사 시, 복사본을 또다른 스택 공간에 저장한다.
- 상속이 불가능하고, 스택 프레임 종료 시 메모리에서 자동으로 종료된다.
위 둘의 차이점을 코드로 느껴보자.
class MonsterC {
var name = "클래스 괴물"
}
struct MonsterS {
var name = "구조체 괴물"
}
// 클래스 객체 복사 예시
let monsterC = MonsterC()
// 복사
let monsterC2 = monsterC
monsterC2.name = "변경된 클래스 괴물"
print(monsterC.name, monsterC2.name) // 두 변수 모두 "변경된 클래스 괴물" 출력
print(monsterC === monsterC2) // true 출력, 메모리 주소가 같기 때문
// 구조체 객체 복사 예시
let monsterS = MonsterS()
//복사
let monsterS2 = monsterS
monsterS2.name = "변경된 구조체 괴물"
print(monsterS.name, monsterS2.name) // "구조체 괴물", "변경된 구조체 괴물" 출력
'Swift 문법' 카테고리의 다른 글
[Swift] 옵셔널 (0) | 2022.07.18 |
---|---|
[Swift] Enum(열거형) (0) | 2022.07.16 |
[Swift] 함수 (0) | 2022.07.15 |
[Swift] 튜플 (0) | 2022.07.14 |
[Swift] Switch문 (0) | 2022.07.13 |