Swift = 객체 지향 언어
- 필요한 기능을 객체로 구현하여 사용한다!
- 구조체와 클래스: 객체를 만들어내는 주요 대상, Swift가 언어적으로 유연성을 가질 수 있게 해주는 근간을 이룸
- 유연성: 코드를 떼어서 다른 곳으로 옮기거나 새로운 코드를 추가하기 쉬움
→ 변수, 상수, 함수가 구조체나 클래스 안에 들어가면서 특별한 성격을 가지게 되기 때문!
구조체와 클래스의 공통점
1. 프로퍼티: 변수나 상수를 사용하여 값을 저장하는 프로퍼티를 정의할 수 있음
2. 메소드: 함수를 사용하여 기능을 제공하는 메소드를 정의할 수 있음
3. 서브스크립트: 속성값에 접근할 수 있는 방법을 제공하는 서브스크립트를 정의할 수 있음
3-1. 서브스크립트: Swift에서 클래스(Class), 구조체(Struct), 열거형(Enum) 등의 컬렉션이나 시퀀스의 요소에 간단하게 접근하기 위한 방법(예. array[0])
4. 초기화 블록: 객체를 원하는 초기 상태로 설정해주는 초기화 블록 정의할 수 있음
struct Person {
var name: String
var age: Int
// 초기화 블록
// 새로운 Person 객체를 생성할 때 이름과 나이를 지정하도록 함
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let person = Person(name: "Alice", age: 30)
print("이름: \(person.name), 나이: \(person.age)")
5. 확장: 객체에 함수적 기능을 추가하는 확장(extension) 구문을 사용할 수 있음
6. 프로토콜: 특정 형식의 함수적 표준을 제공하기 위한 프로토콜을 구현할 수 있음
구조체와 클래스의 차이점: 구조체는 할 수 없지만, 클래스는 할 수 있는 기능
1. 상속: 클래스의 특성을 다른 클래스에게 물려줄 수 있음
2. 타입 캐스팅: 실행 시 컴파일러가 클래스 인스턴스의 타입을 미리 파악하고 검사할 수 있음
class Animal {
var name: String
init(name: String) {
self.name = name
}
}
class Dog: Animal {
func bark() {
print("Woof!")
}
}
// Upcasting: 서브클래스의 인스턴스를 슈퍼클래스 타입으로 변환, 항상 성공
let myPet: Animal = Dog(name: "Buddy") // 업캐스팅 (Animal 타입으로 저장)
// Downcasting: 슈퍼클래스의 인스턴스를 서브클래스 타입으로 변환하는 것, 성공할 수도 실패할 수도 있음
// 컴파일러는 객체가 Dog 타입인지 확인하기 위해 런타임 검사를 수행함
if let dog = myPet as? Dog { // 다운캐스팅
dog.bark() // Dog의 메서드 사용 가능
} else {
print("캐스팅 실패")
}
// 컴파일러가 타입을 미리 파악하여 캐스팅이 적절한지 확인!
3. 소멸화 구문: 인스턴스가 소멸되기 직전에 처리해야할 구문을 미리 등록해 놓을 수 있음
- deinit 키워드를 통해 정의되며 주로 리소스 해제, 파일 닫기, 네트워크 연결 끊기 작업등을 수행
class ExampleClass {
var name: String
init(name: String) {
self.name = name
print("\(name) 생성됨")
}
// 클래스가 메모리에서 해제되기 직전에 자동으로 호출
// ARC와 연계되어, 참조 카운트가 0이 되었을 때 호출
deinit {
print("\(name) 소멸됨")
}
}
var example: ExampleClass? = ExampleClass(name: "테스트")
// ExampleClass 인스턴스 생성
example = nil
// 인스턴스를 nil로 설정하면 deinit이 호출되고 인스턴스가 소멸됨
4. 참조에 의한 전달: 클래스 인스턴스가 전달될 때에는 참조 형식으로 제공되며, 이때 참조가 가능한 개수에는 제약이 없음
구조체와 클래스의 기본 개념
1. 정의구문
// 이름은 카멜 표기법으로 작성!
struct 구조체이름 {
// 구조체 정의 내용
}
class 클래스이름 {
// 클래스 정의 내용
}
2. Method와 Property
// 픽셀 기반 디스플레이의 해상도 정보를 관리
struct Resolution {
// 저장 프로퍼티: 특정 값을 저장하기 위해 클래스나 구조체 내부에 정의된 변수나 상수
var width = 0
var height = 0
// Method
func desc() -> String {
return "Resolution Struct"
}
}
// 비디오 디스플레이에서 표현되는 비디오에 대한 정보를 관리
class VideoMode {
var interlaced = false
var fremeRate = 0.0
// Optional 타입에 초기값이 할당되지 않는 경우 자동으로 nil로 초기화
var name: String?
func desc() -> String {
return "VideoMode Class"
}
}
3. Instance
- 구조체와 클래스를 그대로 사용해서 값을 저장하거나 메소드를 실행할 수는 없음
→ 단순히 객체의 정의일 뿐 실제로 값을 저장하고 메소드를 호출하는 데 필요한 메모리 공간을 할당받지 못했기 때문!
→ 실질적인 값을 저장하고 사용하려면 메모리 공간을 할당받은 객체가 필요!
- 구조체나 클래스는 값을 담을 수 있는 실질적인 그릇을 만들어 내기 위한 일종의 틀 → Origin(원형)
- 원형 틀을 이용하여 찍어낸 그릇 → Instance(인스턴스)
* 객체지향 프로그래밍: 틀 역할을 하는 클래스나 구조체를 정의하고 이를 바탕으로 실질적으로 값을 담을 여러 개의 그릇, 인스턴스를 만들어내는 것!
// Resolution 구조체에 대한 인스턴스를 생성하고 상수 insRes에 할당
let insRes = Resolution()
// VideoMode 클래스에 대한 인스턴스를 생성하고 상수 insVMode에 할당
let insVMode = VideoMode()
// 구조체와 클래스 내부에 선언된 프로퍼티는 인스턴스를 통해서만 접근할 수 있음
let width = insRes.width // Resolution.width로 접근하면 해당 프로퍼티를 찾을 수 없다는 에러 발생
[참조] ( )의 역할
1. 함수 호출 연산자: 함수의 이름 다음에 위치
2. 인스턴스 생성 연산자: 클래스나 구조체 이름 다음에 위치
// 비디오 디스플레이에서 표현되는 비디오에 대한 정보를 관리
class VideoMode {
var interlaced = false
var fremeRate = 0.0
var name: String?
var res = Resolution()
// VideoMode의 프로퍼티이자 Resolution 구조체의 인스턴스
// res 하위에는 Resolution의 width 프로퍼티가 존재
func desc() -> String {
return "VideoMode Class"
}
}
// dot syntax를 이용하여 인스턴스 프로퍼티에 값을 할당
vMode.name = "Sample"
vMode.res.width = 1280
if let name = vMode.name {
print("\(name) and \(vMode.res.width)") // Sample and 1280
}
4. Initialize
- 옵셔널 타입으로 선언되지 않은 모든 프로퍼티는 명시적으로 초기화해주어야 함
- 옵셔널 타입으로 선언된 프로퍼티는 초기값이 지정되지 않을 경우 자동으로 nil로 초기화
* 명시적인 초기화
1) 프로퍼티를 선언하면서 동시에 초기값을 지정하는 경우
2) 초기화 메소드 내에서 프로퍼티의 초기값을 지정하는 경우
(Struct)Memberwise Initializer
- 구조체는 모든 프로퍼티의 값을 인자값으로 입력받아 초기화하는 기본 초기화 구문을 자동으로 제공
→ 이 초기화 구문을 멤버와이즈 초기화 구문이라고 부름!
// 입력한 인자값이 프로퍼티의 초기값으로 설정됨
let defaultRes = Resolution(width: 1024, height: 768)
// Resolution 구조체의 인스턴스를 생성할 때 사용할 수 있는 초기화 구문
Resolution() // 기본 초기화 구문. 구조체의 인스턴스를 생성하는 역할. 아무 인자값도 입력받지 않으므로 내부적으로 프로퍼티를 초기화하지 않음.
Resolution(width: Int, height: Int) // 모든 프로퍼티의 초기값을 입력받는 멤버와이즈 초기화 구문. 내부적으로 모든 프로퍼티를 초기화함
- 클래스에서는 멘버와이즈 형식의 초기화 구문이 제공되지 않음 → 빈 괄호 형태의 기본 초기화 구문
- 모든 프로퍼티가 선언과 동시에 초기화 되어야만 빈 괄호 형태의 기본 초기화 구문을 사용할 수 있음
구조체와 클래스의 값 전달 차이
구조체의 값 전달 방식: 복사에 의한 전달
- Value Type, 복사에 의한 전달
- 정수, 문자열, 배열, 딕셔너리 등의 기본 자료형이 복사를 통해 값이 전달되는 이유 → 해당 자료형이 구조체로 구현되었기 때문!
- 모든 구조체는 값 타입 = 모든 구조체 인스턴스들이 상수나 변수에 할당될 때 복사된다는 뜻!
- 구조체 인스턴스를 변수에 대입하면 기존의 인스턴스가 그대로 대입되는 것이 아니라 이를 복사한 새로운 값이 대입됨
→ 변수에 대입된 인스턴스와 기존의 인스턴스는 서로 독립적
→ 인스턴스를 할당한 후에 기존 인스턴스나 할당된 쪽의 인스턴스에 변경이 발생해도 서로에게 전혀 영향을 끼치지 않음
→ 값이 소멸해도 마찬가지!
- 구조체 인스턴스가 상수에 할당되면 프로퍼티 값을 변경할 수 없음
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
// hd의 복사본이 생성된 후, 이 복사본이 cinema에 대입됨 -> 값만 같을 뿐 실제로는 별개의 인스턴스가 대입!
cinema.width = 2048
print("hd.width: \(hd.width), cinema.width: \(cinema.width)")
// hd.width: 1920, cinema.width: 2048
클래스의 값 전달 방식: 참조에 의한 전달
- Reference Type, 참조에 의한 전달
- 참조: 인스턴스가 저장된 메모리 주소 정보가 전달
- 클래스 타입은 항상 메모리 주소를 사용하여 객체 자체를 전달
let video = VideoMode()
video.name = "Original Video Instance"
// video는 상수로 정의되었기 때문에 값이 변경되지 않는 게 맞음..!
let dvd = video
dvd.name = "Modified Video Instance after reference"
print("video.name: \(video.name!)") // Modified Video Instance after reference
// dvd와 video가 동일한 클래스 인스턴스를 참조하고 있으므로, dvd를 통해 변경된 클래스 인스턴스의 프로퍼티 값이 video에도 동일하게 적용되고 있음!
// 참조 타입에서는 상수로 선언된 변수라도 해당 인스턴스의 속성은 변경될 수 있음!(동일한 인스턴스를 참조하고 있으므로!)
// VideoMode: 사용자가 만든 새로운 자료형! (클래스, 구조체는 새로운 자료형으로 사용 가능한 객체) -> 따라서 ()가 붙지 않음
// 함수의 매개변수에 inout 키워드를 붙여주지 않았지만, 전달한 값이 클래스 타입이므로 원본 인스턴스의 참조가 전달됨!
func changeName(v: VideoMode) {
v.name = "Fuction Video Instance"
}
changeName(v: video)
print("video.name: \(video.name!)")
- 클래스는 참조 타입이므로 한 곳에서 수정하는 경우 다른 곳에도 적용됨
- 하나의 클래스 인스턴스를 여러 변수나 상수, 함수의 인자값에서 동시에 참조할 수 있음
(구조체는 값의 할당 = 값의 복사 → 하나의 인스턴스는 오로지 하나의 변수/상수만이 참조할 수 있음 → 단일 참조!)
클래스의 메모리 이슈
- 구조체 인스턴스는 단일 참조가 보장되므로 인스턴스가 할당된 변수나 상수의 사용이 끝나면 바로 메모리에서 해제해도 됨
- 클래스 인스턴스는 여러 곳에서 동시 참조가 가능하므로 한 곳에서 참조가 완료되었다 해도 메모리 해제를 마음대로 할 수 없음
→ 다른 곳에서 해당 인스턴스를 참조할 수 있으며, 아직 인스턴스를 참조하고 있는 변수, 상수, 함수의 인자값이 잘못된 메모리 참조로 인한 오류가 발생할 수 있음
⇒ ARC(Auto Reference Counter)
- 클래스 인스턴스를 참조하는 곳을 계속 검사하고, 참조하는 곳들이 모두 제거되면 해당 인스턴스를 사용하지 않는다고 판단하여 메모리에서 해제시킴
- 변수, 상수, 함수의 인자값으로 클래스 인스턴스가 할당되면 카운트를 1 증가시키고, 해당 변수나 상수들이 종료되면 카운트를 1 감소시킴
→ 인스턴스의 참조 카운트가 0이 되면 메모리에서 해제
클래스 인스턴스에서의 비교
- 단순한 값 비교가 불가능
- 두 대상이 같은 메모리 공간을 참조하는 인스턴스인지 아닌지에 대한 비교가 필요
- 클래스 인스턴스 비교구문은 메모리 주소의 일치 여부, 즉 객체의 동일성 여부에 근거!
// 동일 인스턴스인지 비교할 때 ===
// 동일 인스턴스가 아닌지 비교할 때 !==
if video === dvd {
print("동일한 인스턴스를 참조!") // 동일한 인스턴스를 참조!
} else {
print("동일한 인스턴스를 참조하지 않음!")
}
let vs = VideoMode()
let ds = VideoMode() // 새롭게 생성된 인스턴스
if vs === ds {
print("동일한 인스턴스를 참조!")
} else {
print("동일한 인스턴스를 참조하지 않음!") // 동일한 인스턴스를 참조하지 않음!
}
// 동일한 타입의 인스턴스이지만 같은 메모리 주소를 참조하는 것은 아님!
구조체를 사용하는 경우(하나라도 해당된다면 적용!)
1. 서로 연관된 몇 개의 기본 데이터 타입들을 캡슐화하여 묶는 것이 목적일 때
2. 캡슐화된 데이터에 상속이 필요하지 않을 때
3. 캡슐화된 데이터를 전달하거나 할당하는 과정에서 값이 복사되는 것이 합리적일 때
4. 캡슐화된 원본 데이터를 보존해야 할 때
함수의 생명주기와 참조 카운트의 연관성(feat. chat GPT) (0) | 2024.09.08 |
---|---|
Closure in Swift and @escaping, @autoclosure (0) | 2024.09.08 |
Nested Function (feat. Closure in Software Architecture) (0) | 2024.09.08 |
First-Class Object - 일급 객체로서의 함수 (0) | 2024.09.08 |
[Ellen] FAQ: 조건문 (0) | 2024.08.27 |
댓글 영역