C/C++를 하게 되면 class나 struct 모두 상관없이 정적할당이나 동적할당으로 Stack영역 혹은 Heap영역으로 저장이 된다.
하지만, Swift는 기본적으로 다른 곳에 할당이 된다고 한다.
추가적으로 차이점들도 많기에 익숙하지 않은 개념을 제대로 잡아보려고 한다.
Swift에서 Class는 참조형 / struct는 복사형
이렇게 되버리면 어떤 경우가 생길 수 있냐면
class Person {
var name: String
init(name: String) {
self.name = name
}
}
var Person_A = Person(name: "Change-Chanllenge")
var Person_B = Person_A // Person_B에 Person_A 주소 값을 복사한다.
Person_B.name = "Hey"
// Person_B의 name만 바꾼 것 같지만 Person_A의 name도 바뀌게 된다. (ref이기에)
print("Person_A의 이름 : \(Person_A.name) | Person_B의 이름 : \(Person_B.name)")
// Person_A의 이름 : Hey | Person_B의 이름 : Hey
혹은
다른 함수나 다른 파일에서 class 인스턴트 안에 값을 잘못바꿔 변경된 값을 사용할 수도 있다. 아래 예시는 다른 함수에서 값을 바꿔버리는 경우이다.
class MyClass {
var myProperty: String
init(myProperty: String) {
self.myProperty = myProperty
}
}
func hello(myClass: MyClass) {
myClass.myProperty = "Hello, world!"
}
let myInstance = MyClass(myProperty: "Original value")
hello(myClass: myInstance) // 값이 바껴버린다.
print(myInstance.myProperty) // "Hello, world!"
이 또한 ref와 value에 대한 차이로 야기되는 문제이다.
만약 Person이라는 Struct가 있고, 이를 let으로 인스턴트를 만들었다고 하자.
그때, Struct는 인스턴트 내부 변수를 바꿀 수가 없다.
struct Person {
var name: String
}
let P = Person(name: "change-challenge")
P.name = "Hey" // 컴파일 오류!
왜냐면 let으로 선언하였기에 Struct 인스턴트 안에 모든 변수는 immutable하기 때문이다.
하지만 Class는 가능하다!
class Person {
var name: String?
}
let P = Person()
P.name = "Hey" // 잘된다!
class에서 let으로 선언하는 것은 인스턴스 참조 자체가 변경될 수 없도록 하기 위한 것이지, 인스턴스 내부의 값이 변경될 수 없도록 하는 것이 아니기 때문이다.
예를 들어, 아래와 같이 인스턴트 내부의 값은 변경이 가능하지만, 참조 자체가 변경될 수 없는 것을 볼 수 있다.
let person1 = Person(name: "Alice", age: 20)
person1 = Person(name: "Bob", age: 30) // 컴파일 오류
person1.age = 25 // 가능
var person2 = Person(name: "Hey", age: 25)
person2 = person1 // 가능, person2는 인스턴트 참조하는 변수가 없어져서 메모리 해제!
즉, Struct의 let은 인스턴트 내부의 값 자체에 const가 되는 것이고,
Class의 let은 인스턴트 참조 자체가 const되는 것이다.
하지만, struct는 var로 선언해도 값 타입이기에 변수의 값을 변경할 수 없다!!!!!
이렇기에 Struct에서도 인스턴트 내부의 값을 변경하려면 Mutating 키워드를 써야한다.
struct Person {
var name: String
mutating func changeName(_ name: String) {
self.name = name
}
}
let P1 = Person(name: "change-challenge")
var P2 = Person(name: "change-challenge")
// P1.changeName("Hey") <- 이게 이미 안된다!
print("what is my name : \(P2.name))
P2.changeName("Hey")
print("what is my name : \(P2.name))
결과값)
what is my name : change-challenge
what is my name : Hey
class는 C++와 같이 소멸자와 상속이 가능하다.
struct는 이 부분이 불가능하다.
아래의 예시와 같다.
1. 소멸자 불가
struct Person {
var name: String
deinit {
}
}
// 소멸자에서 컴파일 오류!
2. 상속 불가
struct Person {
var name: String
}
struct Me : Person {
}
// 상속에서 컴파일 오류!
위에 'let과 Mutating 키워드'예시를 잘 보면,
뭔가 비슷한데 class와 struct가 다르게 정의되어 있고, 선언하는 것을 볼 수 있다.
Class 먼저 보자.
class Person {
var name: String?
}
let P = Person()
P.name = "Hey" // 잘된다!
Class는 기본적으로 가지고 있는 변수 중 하나라도 기본값을 가지지 않는 경우에는 직접 init을 구현 해줘야한다.
Class의 name은 일단 옵셔널로 선언이 되어있다.
옵셔널로 선언하지 않으면 어떻게 될까? 컴파일 오류가 뜬다!
error: class 'Person' has no initializers
라고 뜰 것이다. 이유가 뭘까?
옵셔널은 nil값을 가질 수 있기에 자동적으로 초기화 동안 "아직 값 없음"이라는 의미에서 nil값으로 자동 할당된다.
즉, 옵셔널 타입이 아닌 경우에는 init를 직접 구현해줘야하는 것이다.
class Person {
var name: String
init(name: String) {
self.name = name
}
}
let P = Person(name: "what")
P.name = "Hey"
name을 옵셔널이 아닌 변수로 만들려면 위와 같이 init을 구현해줘야한다.
Struct를 보자,
struct Person {
var name: String
}
let P = Person(name: "change-challenge")
P.name = "Hey" // 컴파일 오류!
Struct는 memberwise init이라고 기본적으로 init를 구현해준다.
struct안에
init(name: String) {
self.name = name
}
가 숨어있는 것이라고 생각하면 된다.
그렇기에 Person(name: "change-challenge") 이라고 인수라벨도 붙을 수 있는 것이다.
이와 같이 class와 struct가 다른 점을 조금 살펴보았다.
익숙하지 않은 언어를 깊게 파보는 것은 코딩 선조들이 과거에 있던 문제점을 해결하려고 했던 노력을 볼 수 있기에 재밌다.
class vs struct 차이점을 위해 참고한 사이트
https://bbiguduk.gitbook.io/swift/language-guide-1/initialization
https://jayb-log.tistory.com/216
https://saad-eloulladi.medium.com/swift-enums-vs-structures-vs-classes-938a4cd76c0d