Swift

[Swift] Class(클래스)란? (feat. 구조체와 차이점)

Hatchling.dev 2023. 6. 10. 02:23

(편의상 편한 말투로 작성하는 점 이해 부탁드립니다.😅)

(부정확한 정보가 있을 수 있습니다. 지적 환영🤩)

 

Swift에서는 Class를 통해 구조체와 마찬가지로 프로퍼티나 메서드를 캡슐화하여 인스턴스(Swift에서는 객체를 인스턴스라고 표현합니다.)를 생성할 수 있습니다.

 

클래스의 특징

  1. 클래스는 단일상속이 가능합니다.
  2. 클래스는 참조타입(reference type)입니다.
  3. iOS 프레임워크 대부분이 클래스로 구현되어 있습니다. (SwiftUI에서는 대부분이 구조체로 구현되어 있습니다.)
  4. ARC를 사용하여 메모리 관리를 한다.

클래스를 사용하는 경우

Swift에서는 클래스의 많은 부분을 구조체에서도 가능하며 구조체가 쓰레드 측면에서도 안전하고 빠르기 때문에 Apple에서는 기본적으로 꼭 필요한 경우가 아니라면 클래스보다 구조체 사용을 권장하고 있습니다. 그렇다면 클래스가 꼭 필요한 상황은 어떤 상황일까요?!

  1. Objective-C에서 지원하는 API를 사용할 때. Objective-C의 클래스를 상속받아 사용해야하는 경우
  2. 값의 고유성을 보장해야 하는 상황. 클래스는 참조타입이기 때문에 인스턴스를 복사해도 값의 고유성을 보장받을 수 있습니다.
  3. 상속이 필요한 경우. (하지만 상속은 클래스만 가능하고 프로토콜을 활용하면 클래스, 구조체, 열거형에서 계층 구조를 모델링 할 수 있기 때문에 상속과 프로토콜의 차이를 잘 파악하여 사용하는 것이 좋다. Swift는 구조체와 프로토콜 사용을 권장합니다.)

사용예시

클래스를 정의하는 방법은 구조체와 많이 비슷합니다. 다만 초기 값이 존재하지 않을 때는 초기화(initializer)를 사용해야 하며 deinit을 사용하여 인스턴스가 메모리에서 사라지기 전에 필요한 작업을 추가할 수 있습니다.

클래스를 정의하기 위해서는 class 키워드를 사용하며 구조체와 마찬가지로 대문자 카멜케이스를 사용합니다.

class Student {
    var name: String
    var age: Int
	
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    deinit {
        print("woo 학생 제거")
    }
	
    func introduce() {
        print("안녕하세요. 저는 \(name)이며 \(age)세 입니다.")
    }
}

var student: Student? = Student(name: "woo", age: 20)
print(student!.name)    // woo
print(student!.age)    // 20
student!.introduce()    // 안녕하세요. 저는 woo이며 20세 입니다.
student = nil			// woo 학생 제거

상속할 때는 아래와 같이 클래스 이름 옆에 콜론(:)과 함께 상속받을 부모 클래스의 이름을 붙여 상속 받을 수 있습니다.

class Person {
    var name: String
    var age: Int
	
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

class Student: Person {
    var school: String
	
    init(name: String, age: Int, school: String) {
        self.school = school
        super.init(name: name, age: age)
    }
	
    func introduce() {
        print("안녕하세요. 저는 \(school) school에 다니는 \(name)이며 \(age)세 입니다.")
    }
}

var student: Student = Student(name: "woo", age: 17, school: "high")
print(student.name)    // woo
print(student.age)    // 17
print(student.school)	// high
student.introduce()    // 안녕하세요. 저는 high school에 다니는 woo이며 17세 입니다.

구조체와 공통점

  1. 값을 저장하는 프로퍼티를 생성할 수 있습니다.
  2. 메서드를 정의하여 사용할 수 있습니다.
  3. 인스턴에서 .을 붙여 프로퍼티에 접근할 수 있습니다.
  4. 생성자를 사용할 수 있습니다.
  5. extension을 활용하여 확장할 수 있습니다.
  6. protocol을 활용하여 프로토콜 및 기능을 정의할 수 있습니다.

구조체와 차이점

  1. 구조체는 값 타입, 클래스는 참조타입
    • 구조체는 값 타입이기 때문에 복사할 때 새로운 인스턴스를 생성해 복사하기 때문에 복사를 하더라도 별도의 인스턴스가 생성된다.
    • 클래스는 참조 타입이기 때문에 복사할 때 주소 값을 공유하기 때문에 원본이나 복사한 인스턴스가 변경되면 모두 변경된다.
  2. 구조체는 상속이 불가능하며, 클래스는 상속이 가능하다.
  3. 클래스는 타입 캐스팅을 통해 인스턴스 타입을 확인할 수 있다.

 

 

ARC(Automatic Reference Counting)란?

위에서 클래스는 ARC를 통해 메모리 관리를 한다고 설명했습니다. 그렇다면 ARC는 무엇일까요? 이름에서 유추할 수 있듯이 자동으로 참조 횟수를 카운팅해주는 방식을 말합니다.

클래스는 참조타입이기 때문에 메모리 중 Heap에 저장되고 여러 곳에서 참조를 하기 때문에 제때 메모리에서 해제되는 것이 중요합니다. 메모리는 한정되어 있기 때문에 인스턴스가 메모리에서 해제되지 않고 계속 쌓이다보면 앱이 죽는 경우가 생길 수 있습니다.

ARC는 참조 횟수를 통해 적절한 타이밍에 인스턴스를 해제시키기 때문에 개발자가 메모리에 대해 덜 신경 쓰도록 도와줍니다. 하지만 ARC를 맹신해서는 안됩니다. 왜냐하면 retain cycle을 신경쓰지 않고 사용하다보면 메모리 릭이 발생할 수 있습니다!!

더보기

retain cycle이란?

Swift에서 참조할 때 Strong, Weak, Unowned 세가지 방식이 존재합니다.

Strong은 강한 참조, Weak과 Unowned은 약한 참조이며 해당 인스턴스의 소유권을 가지지 않으며, retain count를 증가시키지 않는다는 공통점이 있지만 참조가 끝날 때 weak은 nil을 할당(옵셔널)하지만, unowned는 옵셔널이 아니기 때문에 참조가 끝난 후에 접근을 시도하면 crash가 발생하기 때문에 unowned이 사라지지 않는다는 보장이 될 때만 사용해야 한다.

개발자는 Class를 통해 참조가 필요할 때 순환참조를 유의해야 한다.

더보기

순환참조란?

서로 다른 두 인스턴스가 서로를 참조하고 있어 인스턴스가 nil이어도 메모리에서 해제되지 않는 상황을 말합니다.

 

오늘은 클래스에 대해 알아보고 클래스와 구조체의 공통점과 차이점에 대해 알아보았습니다!!

굉장히 비슷한 점도 많고 알고보면 다른 점도 명확하기 때문에 어떤 특징이 있는지, 어떤 상황에 활용하면 좋을지에 대한 고민이 필요해 보이네요!

다음에는 구조체와 클래스를 설명할 때 같이 등장했던 열거형에 대해 알아보도록 하겠습니다~~~

'Swift' 카테고리의 다른 글

[Swift] Protocol(프로토콜)이란?  (0) 2023.06.13
[Swift] 열거형(Enumeration)이란?  (0) 2023.06.11
[Swift] 구조체(Struct)란?  (0) 2023.06.08
[Swift] Optional이란?  (0) 2023.06.05
[Swift] 반복문 정리  (0) 2023.03.16