평소에 노션에 필기한 내용들은 #TIL (TodayILearned) 해시태그를 붙여 여기 티스토리에 공유하려고 합니다...! 구글이라는 우주에 우주쓰레기마냥 흩어져 있는 개념들을 처리하는 용도로 쓰세요 ゝ。∂)
- 중요하다고 느낀 부분은 ⭐별⭐, 📌핀📌 이모티콘, 밑줄로 표시했고
- 혼동될 만한 개념이나 용어들은 vs. 표시,
- 명령어나 폴더/파일명은 하이라이트로 표시했으니
참고해 주세요!
또 노트정리 포스팅은 우주쓰레기가 다 정리되는 그 날까지 꾸준히 업로드할 예정이니 많은 관심 부탁드려용
swift 언어를 예제로 들어 설명했지만 클래스, 구조체 개념이라던가 문법 자체는 다른 객체지향언어와 매우 유사하기 때문에 자바, 코틀린... 사용자라면 도움이 많이 될 듯하다!
Class, Struct 공통점
- 구조체 클래스 모두 데이터를 용도에 맞게 묶어 표현하고자 할 때 유용한 기능이다. 구조체와 클래스는 모두 ‘프로퍼티'와 ‘메서드'를 사용하여 구조화된 데이터와 기능을 가질 수 있고, 하나의 데이터 타입을 만드는 역할을 한다.
- 값을 저장할 프로퍼티, 그리고 메소드를 정의
- 생성자를 통해 초기 상태 설정 가능
- 확장 (extension), 프로토콜 사용 가능
- 타입이름은 대문자 카멜케이스를 사용하여 정의한다.
Class, Struct 차이점
클래스, 구조체는 언뜻 보기에 그 모양새가 너무 비슷해서 차이점을 하나하나 숙지하기 어려울 수 있다. 만약 초보자라면 오늘 포스팅에서 '클래스는 참조 타입, 구조체는 값 타입' 이라는 점만 알아두면 좋을 듯하다.
Class
- ⭐ 참조 타입이며 ARC로 메모리를 관리한다. 즉 call by reference. 따라서 같은 클래스 인스턴스를 여러 개의 변수에 할당한 뒤 값을 변경시키면 할당한 모든 변수에 영향을 준다. (메모리만 복사)
- 단일상속이 가능하다. (=상속이 가능함, 참고로 swift의 경우 다중상속은 불가하다)
- Apple 프레임워크의 대부분의 큰 뼈대는 모두 클래스로 구성
- 타입 캐스팅을 통해 런타임에서 클래스 인스턴스의 타입을 확인할 수 있음.
메인 파일에서 클래스 인스턴스를 생성하고 사용하는 예제를 둘러보자.
// 1. 인스턴스 생성 - 참조정보 수정 가능
var mutableReference: Sample = Sample()
mutableReference.mutableProperty = 200
// 불변 프로퍼티는 인스턴스 생성 후 수정할 수 없다
//mutableReference.immutableProperty = 200
// 2. 인스턴스 생성 - 참조정보 수정 불가
let immutableReference: Sample = Sample()
// 클래스의 인스턴스는 참조 타입이므로 let으로 선언되었더라도 인스턴스 프로퍼티의 값 변경이 가능하다
immutableReference.mutableProperty = 200
// 다만 참조정보를 변경할 수는 없다
//immutableReference = mutableReference
// 참조 타입이라도 불변 인스턴스는 인스턴스 생성 후에 수정할 수 없다
//immutableReference.immutableProperty = 200
// 3. 타입 프로퍼티 및 메서드
Sample.typeProperty = 300
Sample.typeMethod() // type method
// 인스턴스에서는 타입 프로퍼티나 타입 메서드를 사용할 수 없다
Struct
- 값 타입이다. 즉 call by value.
- ⭐ 따라서 같은 구조체를 여러 개의 변수에 할당한 뒤 값을 변경시키더라도 다른 변수에 영향을 주지 않는다. (값 자체를 복사)
- 스위프트의 대부분의 큰 뼈대는 모두 구조체로 구성되며, C 언어 등의 구조체보다 다양한 기능이 있다.
- 구조체 역시 클래스처럼 인스턴스를 만들 수 있음.
- 클래스는 자동으로 초기화 함수를 생성해 주지 않지만, 구조체는 자동으로 초기화 함수를 생성 해준다.
구조체는 다음과 같이 선언된다.
struct Sample {
// var: 가변 프로퍼티(인스턴스 변수)
var mutableProperty: Int = 100
// let: 불변 프로퍼티
let immutableProperty: Int = 100
// static: 타입 프로퍼티
static var typeProperty: Int = 100
// 인스턴스 메서드
func instanceMethod() {
print("instance method")
}
// 타입 메서드
static func typeMethod() {
print("type method")
}
}
- 보통 타입 프로퍼티(구조체 타입 자체가 사용하는 프로퍼티)를 제외한 가변, 불변 프로퍼티를 인스턴스 변수라고 말한다.
- 타입 프로퍼티(혹은 메소드)가 구조체 내에서만 사용하기 때문에, 구조체의 인스턴스를 생성 후 인스턴스를 통해 수정하는 것이 불가하다. 아래 코드를 참고하자.
// 1. 가변 인스턴스 생성
var mutable: Sample = Sample()
mutable.mutableProperty = 200
// 불변 프로퍼티는 인스턴스 생성 후 수정할 수 없다
// 컴파일 오류 발생
//mutable.immutableProperty = 200
// 2. 불변 인스턴스
let immutable: Sample = Sample()
// 불변 인스턴스는 아무리 가변 프로퍼티라도 인스턴스 생성 후에 수정할 수 없다
// 컴파일 오류 발생
//immutable.mutableProperty = 200
//immutable.immutableProperty = 200
// 3. 타입 프로퍼티 및 메서드
Sample.typeProperty = 300
Sample.typeMethod() // type method
// 인스턴스에서는 타입 프로퍼티나 타입 메서드를 사용할 수 없다
// 컴파일 오류 발생
//mutable.typeProperty = 400
//mutable.typeMethod()
- 구조체는 언제 사용할까?
- 다른 객체 또는 함수 등으로 전달될 때 참조가 아닌 복사를 원할 때
- 자신을 상속할 필요가 없거나, 자신이 다른 타입을 상속 받을 필요가 없을 때
- Apple 프레임워크에서 프로그래밍할 때에는 주로 클래스를 많이 사용
- ⭐ Value vs. Reference
- Value 타입은 데이터를 전달할 때 값을 복사하여 전달한다.
- 참고로 다른 언어에서 기본 타입을 값 타입으로 쓰는 반면 스위프트는 구조체로 값 타입을 만들며, 실제로 스위프트의 많은 기본 데이터 타입들이 구조체로 이루어져 있다. ex. public struct Int ...
- Reference 타입은 데이터를 전달할 때 값의 메모리 위치를 전달한다.
- Value 타입은 데이터를 전달할 때 값을 복사하여 전달한다.
예제를 통해 call by value, call by reference 차이를 간단하게 알아보자.
이 두 개념은 스위프트를 떠나 매우 중요하기 때문에 개별 포스팅으로 또 다룰 예정이다.
class SimpleClass {
var count: Int = 0
// deinit을 사용하여 클래스 인스턴스의 메모리 할당을 해제할 수 있다.
deinit {
print("할당 해제")
}
}
struct SimpleStruct {
var count: Int = 0
}
var class1 = SimpleClass()
var class2 = class1
var class3 = class1
class3.count = 3
print(class1.count)
// class3의 값을 변경했지만 참조타입이므로 class1도 변경 되는 것을 볼 수 있다.
var struct1 = SimpleStruct()
var struct2 = struct1
var struct3 = struct1
// 구조체 변수를 새로운 변수에 할당할 때마다 새로운 구초체가 할당된다.
struct2.count = 2
struct3.count = 3
print(struct1.count) // 0
print(struct2.count) // 2 <- 구조체는 값 타입이므로 항상 새로운 메모리가 할당된다.
print(struct3.count) // 3
- 위 코드에서 class2, class3에 클래스 변수 class1을 대입하는 것을 볼 수 있다. 그리고 class3 클래스의 프로퍼티를 변경했을 때 class1의 프로퍼티도 변경되었음을 볼 수 있는데 이는 클래스 변수 class1, class2, class3가 대입연산자(=)를 통해 전달되는 값이 '메모리 주소'이기 때문이다. 따라서 세 클래스 변수 class1, 2, 3는 모두 '동일한' 메모리 공간을 가리키고 있어 한 변수로 프로퍼티를 바꾸면 모두 바뀌게 되는 것이다.
- 또 아까 예제에서 클래스 인스턴스가 let으로 생성되어도 인스턴스의 프로퍼티의 값은 바꿀 수 있었다. 이는 let 키워드의 대상이 class1, 2, 3와 같은 클래스 변수이기 때문인데, 이 변수들의 값은 메모리 주소이기 때문에 바뀔 필요가 없다. 반면 클래스 속 프로퍼티는 불변 프로퍼티가 아니라면 클래스명.필드명=xx 등을 통해 수정 가능하다.
- 이에 반해 구조체는 값 타입인데, 값 타입을 대입연산자로 전달할 경우 struct1과 동일한 구조체가 '새로' 생성된다. 그래서 call by value를 설명할 때 '메모리 주소의 복사'가 아닌 '값의 복사'라고 표현하는 것이다. 여기서 말한 '값'이 구조체를 이루는 변수 count를 가리키는 셈이다. 아예 새로 생성된 인스턴스를 struct2, struct3가 가리키기 때문에, struct1, 2, 3 각각의 메모리 주소도 다 다르다.
Epilogue
질문과 피드백은 언제나 환영입니다. 부족하거나 틀린 부분은 편하게 말씀해주시면 감사하겠습니다!