[Swift] Class & Struct
Swift Class & Struct
Class & Struct 클래스와 구조체
클래스와 구조체의 개념이 나오고부터 프로그래밍의 패러다임이 바뀌었음
- 클래스 : 어떤 하나의 틀 (ex. 붕어빵 틀)
- 객체 : 메모리에 각각 저장 된 실제 데이터 (ex. 붕어빵)
Class 클래스
// 클래스(붕어빵 틀) 만들기
// class를 struct로 바꾸어도 아래 코드는 문제없이 동작
// 둘의 차이는 아래에서..
class Dog {
var name = "강아지" // 클래스 내의 변수들을 속성(property)라고 지칭
var weight = 0
func sit() { // 클래스 내의 함수들을 메서드(method)라고 지칭
print("앉았습니다.")
}
}
// 붕어빵 찍어내기 (객체의 생성)
var bori = Dog()
// 객체의 속성에 접근
bori.name = "보리"
bori.weight = 15
bori.sit()
- 일반적으로 클래스, 구조체 선언할때 모두 속성을 먼저 쓰고 메서드를 나중에 작성
- 클래스 내부에는 직접 메서드 실행문이 올 수 없음 (메서드 실행문은 메서드의 정의문 내에 존재해야함)
클래스와 구조체의 차이
- 클래스와 구조체 둘다, 메모리에 찍어낸 것을 인스턴스(instance)라고 함
- 인스턴스는 실제로 메모리에 할당되어 구체적 실체를 갖춘 것이라는 의미
클래스의 instance를 객체(object)라고 부름
- 클래스의 인스턴스 (객체)
- 구조체의 인스턴스
- 열거형의 인스턴스
메모리 저장 방식의 차이
구조체 (struct) | 클래스 (class) | |
---|---|---|
타입 | Value Type (값 형식) | Reference Type (참조 형식) |
메모리 관련 값의 저장 | Stack / 복사 전달 (메모리에서 자동 제거) | Heap / 주소 전달 (ARC로 관리) |
let / var 선언 | 인스턴스 상수(let)로 선언시 저장 속성이 전부 상수로 선언됨 | 인스턴스 상수(let)로 선언하면 가리키는 인스턴스 고정 (저장 속성은 각 let/var 선언에 따름) |
생성자 관련 | 멤버와이즈 이니셜라이저 자동 제공 | 편의 생성자 존재 |
메서드 + 속성 | 메서드 내에서 속성 변경 원칙적으로는 불가능 | 메서드 내에서 속성 변경 가능 |
소멸자 | 소멸자 없음 | 소멸자 있음 |
상속 가능 여부 | 상속 불가능 | 상속 가능 (클래스가 유일) |
값 형식과 참조 형식
값 형식 (구조체) | 참조 형식 (클래스) | |
---|---|---|
타입 | Value Type | Reference Type |
메모리 상의 저장 위치 | 필요시에 항상 메모리의 값이 복사되어 전달 값의 저장 : Stack | 필요시에 항상 메모리의 주소를 전달 값의 저장 : Heap (주소를 Stack에 저장) |
메모리 관리 방식 | 값이 들어있는 스택의 스코프가 종료되면 메모리에서 자동 제거 | RC(Reference Counting)을 통해 메모리 관리 Swift에서 사용하는 ARC 모델 |
각 형식의 타입 예시 | 기본 타입, 튜플, 열거형, 컬렉션, 구조체 | 클래스, 클로저 |
클래스와 구조체의 let과 var 키워드
class PersonClass {
var name = "사람"
var age = 0
}
struct AnimalStruct {
var name = "동물"
var age = 0
}
let astruct = AnimalStruct()
// 구조체의 경우에 let으로 선언하면 메모리 구조가 스택에 생기기 때문에 속성을 변경할 수 없음, 전체 내부의 속성 모두가 let으로 선언된 것
let pclass = PersonClass()
// 클래스의 경우에 let으로 선언하면 새로운 메모리 주소로 변경할 수 없다는 뜻으로 다른 객체를 가르킬 수 없지만 반면에 가리키고 있는 속성은 var로 선언되어 있기 때문에 변경이 가능
astruct.name = "동물1" // let으로 선언했으므로 값을 바꿀 수 없음 (Error)
pclass.name = "사람1" // let으로 선언했어도 값이 변함
Initialize 초기화
생성자(Initialize)는 인스턴스를 만들 때 사용하는 특별한 메서드
class Dog {
var name: String
var weight: Int
init(name: String, weight: Int) {
self.name = name
self.weight = weight
}
}
var dog1 = Dog(name: "흰둥이", weight: 25)
초기화 메서드 / 이니셜라이저 / 생성자
- 모든 저장 속성(변수)을 초기화 해야함 (구조체, 클래스 동일)
- 생성자 실행 종료시점에는 모든 속성의 초기값이 저장되어 있어야 함 (초기화가 완료되지 않으면 컴파일 에러)
- 클래스, 구조체, (열거형)은 모두 설계도 일뿐이고, 실제 데이터(속성), 동작(메서드)을 사용하기 위해서는 초기화 과정이 반드시 필요함
- 인스턴스 초기화를 완료하면 메모리에 정상적으로 인스턴스가 생성
- 옵셔널 타입을 가진 변수의 경우는 nil로 초기화되기 때문에 반드시 초기화값이 있을 필요는 없음
식별 연산자
두개의 참조가 같은 인스턴스를 가르키고 있는지를 비교하는 방법
print(dog1 === dog2)
print(dog1 !== dog2)
클래스와 구조체 사용 이유와 상황에 따른 사용
결국 클래스/구조체는 의미있는 데이터를 묶음으로 만들려는 것이다.
클래스/구조체를 사용하는 이유
- 사용하려는 모델의 설계
- Data Transfer Object (DTO) / VO : 관련 데이터를 한 곳에 담는 역할
- Data Access Object (DAO) : 데이터 처리 (비즈니스 로직)
- Helper Object : 유틸리티 (도움주는 기능 : 날짜, 시간, 통화, 인코딩)
- 미리 설계 해 놓은 클래스/구조체들을 잘 활용하기 위함 (프레임 워크)
사용 상황의 차이
- struct 사용
- 상속이 필요하지 않고 모델의 사이즈가 그리 크지 않을 떄
- JSON의 필드와 1:1 mapping되는 간단한 모델이 필요할 때
(JSON대신 다른 데이터 encoder/decoder를 구현가능하지만 Swift에서는 JSON만 제공됨)
- class 사용
- 해당모델을 serialize 해서 전송하거나 파일로 저장할 일이 있을 때
- 해당 모델이 Obj-C와 상호 운용성이 필요할 떄
필연적으로 클래스는 구조체보다 여러가지 면에서 속도가 느릴 수 밖에 없으므로 차이점을 명확하게 인지하고 사용
애플 가이드 라인
- 연관된 간단한 값의 집합을 캡슐화 하는 것만이 목적일 때
- 캡슐화한 데이터를 참조하는 것보다 복사되는 것이 효율적일 때
- 구조체에 저장된 프로퍼티가 값 타입이며 참조되는 것보다 복사되는 것이 합당할 때
- 다른 타입으로부터 상속받거나 자신이 상속될 필요가 없을 때
이러한 상황에서 애플은 구조체를 사용하기를 권장하고 있다.
애플은 상속 등의 클래스의 기능들을 필요로 하지 않는다면 구조체를 사용하기를 권장한다.
객체지향 4대 특징
- 추상화 (Abstraction) [모델링]
- 실생활에서의 구체적인 것들을 관찰자가 관심있는 부분만 가지고 재조합 하는 것
- 실체들에서 관심있고, 공통적인 특성을 뽑아내서 하나의 분류(class)로 만든 것 (모델링)
- 객체지향의 관점에서, 실체들의 공통적 특성을 뽑아내서 클래스로 정의하는 것 자체가 추상화의 개념
ex) 도서관리 프로그램 -> 제목 / 저자 / 출판사 / 가격 등
- 캡슐화 (Encapsulation) [모델링 / 정보 은닉 / 데이터 캡슐화]
- 연관이 있는 속성(상태)과 메서드(기능)를 ‘하나의 클래스’로 묶어서 활용한다는 개념
- 묶을 때, 객체는 자신이 맡은 역할을 수행하기 위한 하나의 목적을 가진 실체라는 관점에서 접근해야함
- 추상화와 밀접하게 연결되는 개념
(추상화는 디자인 레벨 관점에서 바라보는 개념인데 반해, 캡슐화는 실제 코드로 구현하는 레벨에서의 개념) - 은닉화 (Information Hiding)
- 캡슐화를 하면, 접근 제어자(private, public 등)를 사용해 객체 외부에서 내부 데이터의 접근 통제가 가능해짐
- 상속성 (Inheritance) [재사용 / 확장]
- 부모 클래스의 속성과 메서드를 자식 클래스에서 그대로 물려 받는 개념
- 상속을 통해, 코드가 재활용되기 때문에 생산성이 높아짐 (클래스의 재사용과 확장을 위해 상속이 사용됨)
- 객체지향의 개념에서, 클래스가 다른 타입과 구별되는 결정적인 차별점
- 다형성 (Polymorphism) [사용 편의 / 동적 바인딩]
- 하나의 객체가 여러가지 타입의 형태로 저장될 수 있고, 다양한 메서드의 형태로 동작 가능함을 의미
(하나의 객체는 부모의 타입으로도 저장이 가능하고, 프로토콜 타입으로도 저장이 가능) - 하나의 객체는 다양한 방식으로 동작 가능 (동적 바인딩 / Method Dispatch)
(또한 오버라이딩과 오버로딩을 통해 하나의 메서드나 클래스를 다양한 방법으로 동작시키는 것을 포함하는 개념)
- 하나의 객체가 여러가지 타입의 형태로 저장될 수 있고, 다양한 메서드의 형태로 동작 가능함을 의미