구조체와 클래스는 비슷한 점도 있지만 다른 점도 분명하게 있다고 생각한다.
그래서 구조체와 클래스를 비교해서 정리하기로 했다.
구조체와 클래스의 공통점
- 값을 저장하기 위한 프로퍼티를 정의한다.
- 기능성을 제공하기 위한 메소드를 정의한다.
// 구조체 정의
struct People {
// 값을 저장하기 위한 프로퍼티 정의
var name: String = "jaynam"
var age: Int = 28
var gender: String = "male"
// 기능성 제공을 위한 메서드 정의
func getPeopleInfo() {
print("Name : \(name) , Age : \(age) , Gender : \(gender)")
}
}
// 클래스 정의
class Computer {
// 값을 저장하기 위한 프로퍼티 정의
var cpu: String = "AMD"
var mem: Int = 16
var disk: String = "1TB"
// 기능성 제공을 위한 메서드 정의
func getComputerInfo() {
print("CPU : \(cpu) , MEMORY : \(mem) , DISK : \(disk)")
}
}
- subscript 문법을 사용하는 값에 접근하기 위한 subscript 문법을 정의한다.
struct Weight {
let book: Int
// 서브스크립트 선언
subscript(cnt: Int) -> Int {
// set 인자가 따로 없기 때문에 get으로 동작
return book * cnt
}
}
let myBagWeight = Weight(book: 3)
print("my bag weight is \(myBagWeight[3])")
========== 출력 결과 ==========
my bag weight is 9
간단하게 서브 스크립트를 생성해서 출력해보았다.
프로퍼티와 메서드와 같이 선언을 할 수 있고 다만 서브스크립트는 read-write 와 read only 만 가능하다고 한다.
지금은 간단하게 사용만 해봤지만 서브 스크립트에 대한 옵션 등과 같이 자세한 내용은 아래의 참고사이트에 자세히 설명되어 있다.
- 초기화 상태를 설정하기 위한 initializer 를 정의한다.
초기화는 init() 이라는 함수를 통해서 초기화를 설정해줄 수 있다.
class Temperature {
var temp: Double
init() {
temp = 36.5
}
}
var t = Temperature()
print("Now temperature is \(t.temp)")
만약 프로퍼티가 초기값을 가져야 한다면 초기화에서 설정하는 것보다 기본 값을 정해주는 것이 좋다.
이 외에도 인자 레이블을 통해서 초기화 하는 방법도 있다.
class Temperatures {
let temp: Double
init(temp: Double) {
self.temp = temp
}
func getTemp() {
print("Now temperature is \(temp)")
}
}
let tt = Temperatures(temp: 30.5)
tt.getTemp()
========== 출력 결과 ==========
Now temperature is 30.5
인자 레이블을 통해서 초기화를 하는 등 다양한 방법으로 초기화를 시킬 수 있다는 점을 알아두자.
이 부분에 대한 내용도 아래의 참고 사이트에서 자세하게 공부할 수 있다.
- 기본적인 구현을 넘어서 기능성을 확장한다.
- 특정한 종류의 표준 기능성을 제공하기 위한 프로토콜을 정한다.
- 초기 구현과 더불어 새로운 기능 추가를 위해 익스텐션을 통해 확장할 수 있다.
구조체와 클래스의 차이점
가장 큰 차이로는 구조체는 값 타입이고 클래스는 참조 타입이라는 점이다.
C 언어의 포인터와 유사한 개념이라고 한다.
구조체
구조체를 정의하고 인스턴스를 생성하고 초기화 하고자 할 때에는 기본적으로 생성되는 멤버와이즈 이니셜라이저를 사용한다.
구조체에 기본 생성된 이니셜 라이저의 매개변수는 구조체의 프로퍼티 이름으로 자동 지정된다.
인스턴스가 생성되고 초기화한 후에 프로퍼티 값에 접근해서 수정할 수 있다.
프로퍼티가 상수 let 이면 변경할 수 없고, 변수 var 이면 변경할 수 있다.
그리고 클래스와 달리 상속할 수 없다.
// 구조체 정의
struct People {
// 값을 저장하기 위한 프로퍼티 정의
var name: String = "jaynam"
var age: Int = 28
var gender: String = "male"
let country: String = "Korea"
// 기능성 제공을 위한 메서드 정의
func getPeopleInfo() {
print("Name : \(name) , Age : \(age) , Gender : \(gender) , Country : \(country)")
}
}
// 자동으로 이니셜라이저를 사용해서 구조체를 생성할 수 있다.
var me: People = People(name: "nam", age: 27, gender: "male")
me.getPeopleInfo()
me.age = 30
// 에러 발생
//me.country = "ENG"
me.getPeopleInfo()
========== 출력 결과 ==========
Name : nam , Age : 27 , Gender : male , Country : Korea
Name : nam , Age : 30 , Gender : male , Country : Korea
클래스
클래스를 정의하는 방법은 구조체와 비슷하다.
다만, 클래스는 상속받을 수 있기 때문에 상속받을 때에는 클래스 이름 뒤에 콜론을 써주고 부모 클래스 이름을 명시한다.
class [클래스 이름] : [부모 클래스 이름] {
[프로퍼티, 메서드 생성]
}
클래스의 인스턴스를 생성하고 초기화 할 때는 기본적인 이니셜라이저를 사용한다.
구조체와 다르게 자동으로 이니셜라이저를 사용할 수 없다.
// 클래스 정의
class Computer {
// 값을 저장하기 위한 프로퍼티 정의
var cpu: String = "AMD"
var mem: Int = 16
var disk: String = "1TB"
// 기능성 제공을 위한 메서드 정의
func getComputerInfo() {
print("CPU : \(cpu) , MEMORY : \(mem) , DISK : \(disk)")
}
}
// 에러 발생
//var cominfo: Computer = Computer(cpu: "intel", mem: 32, disk: "500GB")
간단한 예제를 한번 작성해봤다.
class Person {
// 값을 저장하기 위한 프로퍼티 정의
var name: String = "jaynam"
var height:Float = 0.0
var weight:Float = 0.0
// 기능성 제공을 위한 메서드 정의
func getPersonInfo() {
print("Name : \(name) , Height : \(height) , Weight : \(weight)")
}
}
var jay: Person = Person()
jay.height = 174.4
jay.weight = 65.0
let su: Person = Person()
su.height = 164.3
su.weight = 49.8
프로퍼티의 값을 온점(.) 을 통해서 프로퍼티의 값을 변경할 수 있고
상수 let 으로 인스턴스를 생성해도 내부 프로퍼티의 값을 변경할 수 있었다.
구조체는 상수로 선언할 경우 변경할 수 없다.
클래스는 참조 타입이므로 참조할 필요가 없을 때 메모리에서 해제된다.
이 과정으로 소멸이라고 칭하고 소멸되지 직전 deinit 이라는 메서드가 호출된다.
클래스 내부에서 deinit 메서드를 구현해주면 소멸되기 직전에 deinit 메서드가 호출된다.
deinit 메서드를 소멸자(deinitializer) 라고 부른다.
deinit 메서드는 클래스당 하나만 구현할 수 있다. 매개변수와 반환 값을 가질 수 없고 매개변수를 위한 소괄호도 적지 않는다.
프로그래머가 직접 deinit 을 호출할 수도 없다.
class Person {
// 값을 저장하기 위한 프로퍼티 정의
var name: String = "jaynam"
var height:Float = 0.0
var weight:Float = 0.0
// 기능성 제공을 위한 메서드 정의
func getPersonInfo() {
print("Name : \(name) , Height : \(height) , Weight : \(weight)")
}
deinit {
print("소멸자 확인~~~")
}
}
var jaynam: Person? = Person()
jaynam = nil
========== 출력 결과 ==========
소멸자 확인~~~
옵셔널 타입으로 정해주고 nil 로 값을 바꿔주었을 때 deinit 메서드가 호출된 것을 확인할 수 있었다.
추가로 구조체와 다르게 클래스에서만 사용 가능한 추가적인 기능을 확인할 수 있다.
- 상속(inheritance) : 한 클래스가 다른 클래스를 상속할 수 있다.
- 타입 캐스팅(type casting) : 런타임 시에 클래스 인스턴스의 타입을 확인하고 이해하기 위한 타입 캐스팅이 가능하다.
타입 캐스팅이란 인스턴스의 타입을 확인하는 용도나 클래스의 타입으로 사용할 수 있는지 확인하는 용도로 사용한다.
is 를 통해 타입을 확인할 수 있다. 자세한 내용은 야곰님의 블로그를 통해 자세하게 공부할 수 있었다.
var jay: Person = Person()
var typeCheck: Bool
typeCheck = jay is Person // true
typeCheck = jay is People // false
- 소멸자(deinitializer) : 할당된 자원을 해제시킬 수 있다. deinit 메서드 호출
- 참조 카운팅(reference counting) : 클래스 인스턴스에 하나 이상의 참조를 가능케 한다.
아무래도 가장 큰 차이점은 타입이 다르다는 점 같다.
구조체는 값(value) 타입 , 클래스는 참조(reference) 타입
클래스와 구조체 중에 어떤 것을 선택해야할까?
구조체와 클래스는 서로 타입이 다르기 때문에 용도 또한 다르다고 할 수 있다.
애플의 가이드 라인에서 나와있듯이,
다음과 같은 경우에는 구조체를 사용하기를 권하고 있다고 한다.
- 연관된 간단한 값의 집합을 캡슐화 하는 것만이 목적일 때
- 캡슐화한 값이 참조되는 것보다 복사되는 것이 합당할 때
- 구조체에 저장된 프로퍼티가 값 타입이면 참조되는 것보다 복사되는 것이 합당할 때
- 다른 타입으로부터 상속받거나 자신이 상속될 필요가 없을 때
솔직히 이렇게 글만 봐서는 어떤 때에 효율적으로 사용할 수 있는지 알 수 없다.
직접 사용해보고 겪어봐야 더 와닿을 것 같다고 생각한다.
마지막으로,
스위프트에서의 사용
- 스위프트의 기본 데이터 타입은 모두 구조체로 되어있다.
- 스위프트는 구조체와 열거형 사용을 선호한다
- Apple 프레임워크는 대부분 클래스를 사용한다.
- 구조체/클래스 선택과 사용은 개발자의 몫이다.
정리하기
구조체와 클래스에 대해서 비교해가면 공부했다.
솔직하게 참고한 사이트들에 적혀 있는 것들을 그대로 따라 해본 것들을 정리해서 적은 것이라고 해도 무방하다.
아래에 참고해놓은 사이트에서 더 자세하게 설명되어 있고 따라 했기 때문에 정확히 이해하고 정리했다고 보기는 어렵지만
더 공부한 후 나중에 다시 봤을 때 더 자세하고 쉽게 이해할 수 있게 수정해야겠다.
- 참고 사이트 -
velog.io/@co-in/공식-문서로-공부하는-Swift-8-구조체와-클래스
medium.com/@jgj455/오늘의-swift-상식-subscript-2288551588f9
edu.goorm.io/learn/lecture/1141/야곰의-스위프트-프로그래밍/lesson/43403/타입캐스팅
'iOS > Swift' 카테고리의 다른 글
[Swift] 16. 클로저(closure) 기본 (0) | 2020.11.02 |
---|---|
[Swift] 15. 열거형(enum) 기본 (0) | 2020.10.26 |
[Swift] 13. 클래스(class) 기본 (0) | 2020.10.20 |
[Swift] 12. 구조체(struct) 기본 (0) | 2020.10.18 |
[Swift] 11. 옵셔널 추출 (Optional Unwrapping) (0) | 2020.10.08 |