클래스(class)와 구조체(struct)는 흔하게 접할 수 있는 친구들이죠
사용하는 것을 보면 비슷해 보이지만, 분명 차이가 있기 때문에 나눠놨을 것이고,,
또 중요한 부분이니 딱! 잡고 넘어가면 좋을 것 같습니다. (이걸 이제?)
공통점?
실제로 이 둘은 많은 공통점을 가지고 있죠!
정리를 해보면 아래와 같습니다.
- 프로퍼티(값을 저장)를 정의!
- 메소드(기능 제공)를 정의!
- 생성자(initializer)를 정의!
- 특정 값에 접근할 수 있는 subscript 정의!
- 프로토콜 사용과 extension 가능!
이 정도가 되겠네요,,
그렇다면 클래스에서만 사용할 수 있는 기능에는 무엇이 있을까요~?
크게 상속, 타입 캐스팅, 소멸자, 그리고 참조 카운트가 있겠네요.
이 중에서도 참조 카운트가 중요하다고 생각합니다!
왜냐하면!
구조체는 클래스와 다르게 항상 복사되어 전달되고, 참조 카운트(Reference counting)를 사용하지 않습니다.
아주 약간 자세히 알아봅시다.
값 타입?
우선 구조체(struct)와 열거형(enum)은 값 타입입니다.
음... 이 친구들이 함수에서 상수나 변수에 전달될 때 값이 복사되어 전달된다는 뜻이죠!
주소는 복사되지 않고, 값만! 복사가 됩니다 값만!
예제를 보면 이해하기 쉬울겁니다.
struct Developer {
var name: String
var part: String
var age: Int
func printInfo() {
print("name : \(self.name)")
print("part : \(self.part)")
print("age : \(self.age)")
}
}
여기 Developer라는 구조체가 있습니다!
그 다음 mangDic이라는 이름으로 이 구조체를 선언합니다.
이어서 copyData라는 이름으로 mangDic을 넣어주겠습니다!
let mangDic = Developer(name: "MangDic", part: "iOS", age: 17)
var copyData = mangDic
print("==MangDic==")
mangDic.printInfo()
print("==CopyData==")
copyData.printInfo()
이렇게 코드를 실행하면 결과는... 예상이 되죠?
값을 그대로 가져왔습니다!
이제 여기서 copyData의 값들을 바꿔봅시다!
let mangDic = Developer(name: "MangDic", part: "iOS", age: 17)
var copyData = mangDic
copyData.name = "Copy"
copyData.part = "Developer"
copyData.age = 8
print("==MangDic==")
mangDic.printInfo()
print("==CopyData==")
copyData.printInfo()
이렇게 이름, 파트, 나이를 바꿔주고 다시 실행을 해보면...
copyData의 값들은 바뀌었지만 원본인 mangDic의 값은 그대로인 것을 볼 수 있습니다!
이렇게 값만 복사를 하는 친구들을 값 타입이라고 합니다!
참조 타입?
구조체와 열거형은 값 타입이고...
이번엔 클래스로 테스트를 해보겠습니다!
class Developer {
var name: String
var part: String
var age: Int
init(name: String, part: String, age: Int) {
self.name = name
self.part = part
self.age = age
}
func printInfo() {
print("name : \(self.name)")
print("part : \(self.part)")
print("age : \(self.age)")
}
}
다른 부분은 그대로 두고,
프로퍼티들을 초기화 해줘야 하기 때문에 생성자만 따로 구현하였습니다.
그 이후의 코드들은 위에서 사용했던 코드를 그대로 사용할게요!
let mangDic = Developer(name: "MangDic", part: "iOS", age: 17)
var copyData = mangDic
print("==MangDic==")
mangDic.printInfo()
print("==CopyData==")
copyData.printInfo()
역시나 실행 결과는 같겠죠?
하지만 여기서 copyData의 값을 바꿔주면...!
let mangDic = Developer(name: "MangDic", part: "iOS", age: 17)
let copyData = mangDic
copyData.name = "Copy"
copyData.part = "Developer"
copyData.age = 8
print("==MangDic==")
mangDic.printInfo()
print("==CopyData==")
copyData.printInfo()
???
분명 copyData의 값만 바꿨을 뿐인데 mangDic의 값도 함께 변했습니다!
이렇게 된 이유는 class를 복사할 때 값을 복사하는 것이 아니라 주소를 그대로 복사하여 사용하기 때문입니다!
그렇기 때문에 사본을 변경하더라도 원본이 함께 변하는 것이죠.
이렇게 값이 복사되지 않고 참조를 하는 타입을 참조 타입이라고 합니다!
참조된다는 뜻은 해당 값을 가지고 있는 메모리를 바라보고 있다는 뜻입니다.
메모리에 할당한 값을 변경했기 때문에,
해당 주소를 가지고 있는 모든 인스턴스들의 값도 함께 바뀌는 것이죠!
정리
흔히 볼 수 있는 String과 Int, Array, Dictionary 등, 기본 타입들은 값 타입입니다.
이렇게 들어가 보면 구조체로 정의되어 있기 때문이죠!
하지만 Foundation 안에 있는 NS가 붙은 친구들(NSString, NSArray 등...)은 참조 타입입니다.
이유는 바로 클래스로 정의되어 있기 때문이죠 ㅎㅎ
그렇다면 언제 어느 것을 사용하면 좋을까요?
간단한 캡슐화를 하거나,
인스턴스 혹은 인스턴스의 프로퍼티가 복사가 아니고 참조되기를 기대하거나,
프로퍼티 혹은 메소드 등을 상속할 필요가 없다면
구조체를 사용하는 것을 고려해 볼 수 있습니다!
참고 자료