본문 바로가기

Swift 문법

[Swift] 클래스와 구조체

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