Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

클래스와 구조체 #45

Open
simoniful opened this issue Oct 24, 2022 · 0 comments
Open

클래스와 구조체 #45

simoniful opened this issue Oct 24, 2022 · 0 comments

Comments

@simoniful
Copy link
Owner

simoniful commented Oct 24, 2022

구조체(structure)와 클래스(class)는 보통 프로그램 코드 블록을 유연성 있게 구축하기 위해 사용되어 추상화
상수, 변수, 함수를 정의하는 것과 같은 문법을 사용해
구조체와 클래스에 프로퍼티(property)와 메소드(method)를 정의 가능

다른 언어와 달리 Swift는 구조체와 클래스로부터 인터페이스와 구현을 분리하지 않아도 된다
구조체 또는 클래스를 하나의 파일에 정의하면 Swift가 자동으로 해당 클래스와 구조체를 사용할 수 있는 인터페이스를 만들어 준다

클래스의 인터페이스는 전통적으로 객체로 알려져 있다
하지만 Swift 구조체와 클래스는 다른 언어보다 더 기능성(funtionality)에 가깝고
해당 챕터에선 클래스나 구조체 타입의 인스턴스에 적용되는 기능에 대해 설명

구조체와 클래스의 비교

  • 구조체와 클래스의 공통점

    • 값을 저장하기 위한 프로퍼티를 정의
    • 기능성을 제공하기 위한 메소드를 정의
    • subscript 문법을 사용하는 값에 접근하기 위한 subscript 문법을 정의 - id
    • 초기화 상태를 설정하기 위한 initializer를 정의
    • 기본적인 구현을 넘어서 기능성을 확장
    • 특정한 종류의 표준 기능성을 제공하기 위한 프로토콜을 준수
  • 클래스에만 있는 추가적인 기능

    • 상속 (inheritance) : 한 클래스가 다른 클래스를 상속 가능
    • 타입 캐스팅 (type casting) : 런타임 시에 클래스 인스턴스의 타입을 확인하고 이해하기 위한 타입 캐스팅이 가능
    • 소멸자 (deinitializer) : 할당된 자원을 해제 가능
    • 참조 카운팅 (reference counting) : 클래스 인스턴스에 하나 이상의 참조를 가능 - ARC

클래스가 지원하는 추가적인 기능은 복잡성의 증가로 인한 비용 상승을 초래 - 메모리 사용 관련
가이드라인에 따르면, 사용하기 쉽기 때문에 구조체를 더 선호, 클래스는 적합하고 필요할 때만 사용
대부분의 커스텀 데이터 타입은 구조체 혹은 열거형으로 정의
Class와 Actor는 동일한 특성과 동작을 많이 공유 - 동시성 모델 참고

구조체와 클래스는 앱에서 데이터를 저장하고 동작을 모델링하는 데 좋은 선택이지만,
둘의 유사성으로 인해 서로를 선택하기 어려울 수 있다

  • 기본적으로 구조체를 사용
    • 구조체의 주 목적이 관계된 간단한 값을 캡슐화(encapsulate)를 위한 경우
    • 인스턴스, 내부 프로퍼티가 참조되기 보다 복사되기를 기대하는 경우
    • 구조체가 프로퍼티나 메소드 등을 상속할 필요가 없는 경우
  • Objective-C 상호 운용성이 필요할 때 클래스를 사용
  • 모델링하는 데이터의 ID를 제어해야 하는 경우 클래스를 사용
  • 프로토콜과 함께 구조체를 사용하여 구현을 공유하여 동작을 채택

1) 정의 문법

구조체와 클래스는 비슷한 정의 문법을 가짐
struct 또는 class 키워드로 구조체나 클래스를 선언

struct SomeStructure {
    // structure definition goes here
}
class SomeClass {
    // class definition goes here
}

구조체나 클래스를 정의할 때마다 새로운 Swift 타입이 정의
UpperCamelCase로 작성하는 것을 원칙
내부 프로퍼티나 메소드는 lowerCamelCase로 선언

// 화면의 해상도를 묘사하는 Resolution 구조체를 정의
struct Resolution {
    // 프로퍼티 정의 - 저장되고 묶이는 상수 또는 변수
    var width = 0
    var height = 0
}

// 비디오 화면의 특정 모드를 묘사하는 VideoMode 클래스 정릐
class VideoMode {
    // 프로퍼티 정의 - 저장되고 묶이는 상수 또는 변수
    // resolution은 새로운 Resolution 구조체 인스턴스로 초기화
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

2) 구조체와 클래스 인스턴스

Resolution 구조체 정의와 VideoMode 클래스 정의는 해당 구조체 / 클래스가 어떻게 구성이 되었는지 나타낸다
자체는 특정한 해상도와 비디오 모드를 묘사하지 않는다
구체성을 위해 새로운 인스턴스를 초기화, 생성 문법은 구조체와 클래스가 유사

let someResolution = Resolution()
let someVideoMode = VideoMode()

구조체와 클래스는 모두 새로운 인스턴스를 위한 initializer 문법을 사용
가장 간단한 형태는 구조체, 클래스의 타입 이름을 빈 소괄호와 함께 사용
새로운 인스턴스를 생성하며, 그 인스턴스의 모든 프로퍼티는 기본 값으로 초기화

3) 프로퍼티 엑세스

점 문법을 사용하여 인스턴스의 프로퍼티에 접근 가능
인스턴스 이름 뒤에 바로 점과 프로퍼티 이름을 표기
계층에 따른 접근과 변수 값의 할당 가능

print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"
// someResolution.width 는 someResolution의 width 프로퍼티로 추론되며, 기본 값인 0을 반환

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is 0"
// VideoMode의 resolution 프로퍼티 안 width 프로퍼티와 같이 subproperty에 깊게 접근 가능

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280"
// 변수 프로퍼티에 새로운 값을 할당하기 위해 점 문법을 사용

4) 구조체 타입의 Memberwise Initailizers

모든 구조체는 자동적으로 memberwise initializer를 생성
새로운 구조체 인스턴스의 멤버 프로퍼티를 초기화 하는 데 사용 가능
새로운 인스턴스의 프로퍼티에 대한 초기 값은 이름에 의해 memberwise initializer에 전달 가능

let vga = Resolution(width: 640, height: 480)
// 구조체와 다르게 클래스 인스턴스는 기본 Memberwise Initailizers를 받지 않음

값 타입으로 구조체와 열거형

값 타입(value type)은 상수/변수에 할당되거나 함수에 들어갈 때 그 값이 복사되어 전달된다는 의미를 지닌다
Swift에서 모든 기본 타입(정수, 실수, Boolean, 문자열, 배열, 딕셔너리)은 값 타입이고, 기본적으로 구조체로써 구현

커스텀하게 만든 모든 구조체와 열거형 역시 값 타입
새롭게 생성하는 모든 구조체와 열거형은(그것들의 프로퍼티를 포함하여) 항상 복사되어 코드에 전달된다

배열, 딕셔너리, 문자열같은 기본 라이브러리에 의해 정의된 컬렉션 타입은 복사의 퍼포먼스 비용을 줄이기 위한 최적화를 실시
즉시 복사본을 생성하는 게 아니라 원본과 복사본이 메모리 공간을 공유하다가
만약 컬렉션의 복사본 중 하나가 수정될 경우, 수정 직전에 해당 원소는 복사

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

cinema.width = 2048

print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"

print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"

Resolution은 구조체이기 때문에, 기존 인스턴스의 복사본이 생성
복사본이 cinema에 할당
hd와 cinema가 같은 너비와 높이를 갖고 있을지라도 둘은 완벽히 다른 인스턴스

cinema의 width 프로퍼티를 2048로 늘린 결과
분리된 인스턴스기 때문에, cinema의 width를 수정해도 hd의 width는 영향을 받지 않음

열거형에도 같은 원리가 적용

enum CompassPoint {
    case north, south, east, west
    
    mutating func turnNorth() {
        self = .north
    }
}

var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()

print("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")
// Prints "The current direction is north"
// Prints "The remembered direction is west"

currentDirection의 값을 turnNorth() 메서드를 이용해 수정해도 rememberedDirection은 영향을 받지 않음

참조 타입으로 클래스

값 타입과 달리, 참조 타입은 변수나 상수에 할당되거나 함수에 전달될 때 복사되지 않는다
참조는 기존의 동일한 인스턴스를 활용

let tenEighty = VideoMode()
tenEighty.resolution = Resolution(width: 1920, height: 1080)
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"

클래스는 참조 타입이기 때문에, tenEighty와 alsoTenEighty는 같은 VideoMode 인스턴스를 참조
하나의 같은 인스턴스를 위한 두 가지의 이름이 있는 것
tenEighty의 frameRate 프로퍼티를 확인하면 30.0으로 변한 것을 확인 가능

예시는 참조 타입이 추론하기 어렵다는 것을 보여준다
tenEighty와 alsoTenEighty가 지금 작성하는 코드에서 멀리 떨어져 있다면,
바뀐 VideoMode를 모두 확인하는 것은 매우 어려운 일
tenEighty을 사용 중이더라도 alsoTenEighty을 사용하는 코드를 고려해야하며 반대의 경우도 마찬가지

tenEighty와 alsoTenEighty를 상수로 선언했음에도
tenEighty.frameRate와 alsoTenEighty.frameRate를 통해 프로퍼티를 변경할 수 있었다
tenEighty와 alsoTenEighty 상수 그 자체의 값은 사실상 바뀌지 않기 때문
tenEighty와 alsoTenEighty는 VideoMode 인스턴스를 저장하지 않는다
VideoMode 인스턴스의 메모리를 참조한다
VideoMode의 상수 레퍼런스 값이 아니라 내부 frameRate 프로퍼티가 변하는 것

1) 식별 연산자

클래스는 참조 타입이어서 하나의 동일한 클래스 인스턴스를 여러 상수 / 변수가 참조하는 것이 가능
구조체나 열거형은 항상 복사본을 저장하기 때문에 이와 다르다
두 상수 / 변수가 정확히 같은 클래스의 인스턴스를 참조하고 있는지를 확인하기 위해 두 식별 연산자를 사용한다

  • === : 동일할 경우(identical) 참.
  • !== : 동일하지 않을 경우(not identical) 참.
if tenEighty === alsoTenEighty {
    print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."

identical to(===)와 equal to(==)가 같은 의미가 아니라는 것에 주목해보자
identical to(===)는 클래스 타입의 두 상수/변수가 정확히 같은 클래스 인스턴스 참조하는지 확인 - 메모리 주소
equal to(==)는 두 인스턴스가 값이 상등(equel, 같다/틀리다)하거나 동등(equivalent, 크다/작다/같다)한지를 고려

2) 포인터 (Pointers)

C, C++, Objective-C는 메모리의 주소를 참조하기 위한 포인터(pointer)를 사용
몇 몇 참조 타입의 인스턴스를 참조하는 Swift의 상수와 변수는 C의 포인터와 비슷
하지만, 메모리 주소에 대한 직접적인 포인터는 아니다
새로운 레퍼런스를 생성한다는 것을 표시하기 위해 별표(*)를 작성할 필요도 없다
대신 이러한 레퍼런스는 Swift의 다른 상수/변수와 같이 정의
표준 라이브러리는 직접적으로 포인터와 상호작용하고자 할 경우 포인터와 버퍼 타입을 제공 - 수동적 메모리 관리 가능

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant