상세 컨텐츠

본문 제목

Structure and Class in Swift

Swift&SwiftUI

by (방울)도마토 2024. 9. 18. 23:40

본문

 

 

[Dev_Pathway] Customize views with properties : Structures and Classes

Structures- 관련된 코드들을 함께 패키징하도록 코드를 구성하는 방법- 앱의 어느 곳에서나 재사용할 수 있도록 이름을 가짐 // DayForecast 구조체 struct DayForecast: View { let day: String let isRainy: Bool let hi

bangul-domato.tistory.com

 

 

Swift = 객체 지향 언어

- 필요한 기능을 객체로 구현하여 사용한다!

- 구조체와 클래스: 객체를 만들어내는 주요 대상, Swift가 언어적으로 유연성을 가질 수 있게 해주는 근간을 이룸

- 유연성: 코드를 떼어서 다른 곳으로 옮기거나 새로운 코드를 추가하기 쉬움

 

  • Member: Property + Method
  • Property: 구조체와 클래스 내에서 정의된 변수와 상수
  • Method: 구조체와 클래스 내부에서 정의된 함수 

→ 변수, 상수, 함수가 구조체나 클래스 안에 들어가면서 특별한 성격을 가지게 되기 때문!

 

 

구조체와 클래스의 공통점

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
}

 

[출처] 꼼꼼한 재은씨 문법편 p.426

 

 

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. 캡슐화된 원본 데이터를 보존해야 할 때 

관련글 더보기

댓글 영역