Swift/Swift 가이드

[Swift] 공식 문서 - 구조체와 클래스 (Structures and Classes)

유정주 2022. 2. 15. 20:24
반응형

안녕하세요. 개발하는 정주입니다.

 

오늘은 구조체와 클래스 (Structures and Classes)를 정리해보겠습니다.

 

* 완벽한 번역이 아닌 내용을 한 줄 한 줄 읽는 것에 의의를 두었습니다.

* 파파고의 힘을 빌려 번역했으며 잘못된 번역이 있다면 댓글로 알려주세요.

 


새로 배운 점

  • 구조체 및 클래스를 인터페이스 파일과 구현 파일로 분리하지 않는 것이 Swift만의 특징이라는 사실
  • object라는 단어가 클래스 인스턴스만을 지칭한다는 사실
  • struct를 더 많이 사용하는 이유가 클래스의 추가적인 기능때문이라는 점
  • 구조체나 클래스에서 UpperCamelCase, lowerCamelCase를 맞추는 행위가 표준 스위프트 타입과 일치하려고 하는 것이라는 점
  • 콜렉션은 복사 성능 비용을 감소시키려고 최적화 한다는 사실
    • 바로 복사하지 않고 저장되는 메모리를 공유하고 있다가 복사본 중 하나가 수정되면 수정 직전에 요소가 복사된다.
    • 클래스는 참조 타입이므로 복사해서 사용할 경우 복사본 데이터에 주의하며 코딩해야 한다는 점
  • 포인터 참조를 나타내기 위해 별표(*)를 쓸 필요가 없다는 사실
  • 포인터와 직접 상호 작용해야 하는 경우 표준 라이브러리가 포인터와 버퍼 타입을 제공한다는 점

목차


    Structures and Classes (구조체와 클래스)

    구조체와 클래스는 프로그램 코드에서 건축 자재 역할을 하는 범용적(general-purpose)이고 유연한 구조물(flexible constructs)입니다.

    구조체와 클래스에 기능을 추가하는 속성(properties)과 메소드는 상수, 변수 및 함수 정의에 쓰는 구문과 똑같이  정의합니다. 

     

    다른 프로그래밍 언어와 달리, Swift는 사용자 정의 구조체 및 클래스를 인터페이스 파일과 구현 파일로 분리해서 생성하라고 요구하지 않습니다.

    Swift에서는 구조체나 클래스를 단일 파일로 정의하며, 클래스는 구조체의 외부 인터페이스를 다른 코드에서 자동으로 사용 가능합니다.

     

    클래스 인스턴스를 전통적(traditionally)으로 객체(object)라고 합니다. 하지만 Swift 구조체와 클래스는 다른 언어보다 기능적으로 훨씬 비슷하기 때문에 이 장 많은 곳에서 클래스나 구조체 모두 인스턴스(instance)라고 부릅니다.

     


    Comparing Structures and Classes (구조체 및 클래스 비교하기)

    Swift 구조체와 클래스는 많은 공통점이 있습니다. 

    공통점

    - 속성을 정의하여 값을 저장함

    - 메소드를 정의하여 기능을 제공함

    - 첨자(subscripts) 연산을 정의하여 자신의 값에 접근하는 첨자 연산 구문을 제공함

    - 생성자(initializers)를 정의하여 자신의 초기 값을 설정함

    - 기본 구현 이상으로 기능을 확장함 

    - 프로토콜을 준수하여 정해진 종류의 표준 기능을 제공함

     

    클래스에는 구조체는 없는 추가적인 능력이 있습니다.

    - 상속(Inheritance)을 사용하면 한 클래스가 다른 클래스의 특성을 상속할 수 있습니다. 

    - 타입 변환(type casting)은 런타임에 클래스 인스턴스의 타입을 검사하고 해석할 수 있게 합니다.

    - 소멸자(deinitializer)는 클래스 인스턴스에 할당한 어떤 자원이든 해제 가능합니다.

    - 참조 카운팅(reference counting)은 클래스 인스턴스에 대한 하나 이상의 참조를 허용합니다.

     

    클래스가 지원하는 추가적인 능력은 복잡도 증가를 불러옵니다.

    일반적인 가이드라인으로는 구조체가 이유를 파악하기 더 쉽기 때문에, 클래스는 적절히 필요할 때만 사용합니다.

    따라서 실제로 정의하는 사용자 정의 데이터 유형은 대부분 구조체 및 열거형이 됩니다.

     

    Definition Syntax (정의 구문)

    구조체와 클래스의 정의 구문은 비슷합니다. 구조체는 struct 키워드를 쓰고 클래스는 class 키워드를 씁니다.

    둘 다 전체 정의를 중괄호 한 쌍 안에 넣습니다.

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

     

    새로운 구조체나 클래스를 정의할 때마다 새로운 Swift 타입을 정의하는 것입니다.
    SomeStructure, SomeClass처럼 UpperCamelCase로 명명하여 표준 스위프트 타입과 일치하도록 합니다.
    속성과 메소드엔 frameRate, incrementCount처럼 lowerCamelCase를 사용하여 타입 이름과 구별합니다.

     

    다음 예제는 구조체 정의 및 클래스 정의입니다.

    struct Resolution {
        var width = 0
        var height = 0
    }
    
    class VideoMode {
        var resolution = Resolution()
        var interlaced = false
        var frameRate = 0.0
        var name: String?
    }

    위 예제는 디스플레이 해상도를 픽셀 베이스로 설명하는 Resolution이라는 새 구조체를 정의합니다.

    이 구조체에는 width와 height라는 두 개의 stored 속성이 있습니다. Stored 속성은 '구조체나 클래스의 일부로 묶여서 저장되는 상수 또는 변수'입니다.

     

    위 예제는 VideoMode라는 새 클래스도 정의합니다.

    이 클래스에는 네 개의 Stored 속성 변수가 있습니다.

     

    Structure and Class Instances (구조체와 클래스 인스턴스)

    Resolution 구조체 정의와 VideoMode 클래스 정의는 Resolution이나 VideoMode가 어떻게 보일지만 설명합니다.

    그들은 스스로 특정 해상도나 영상 모드를 설명하지 않습니다.

    그렇게 하려면 구조체나 클래스 인스턴스를 생성해야 합니다.

     

    인스턴스 생성 구문은 구조체와 클래스 둘 다 아주 비슷합니다.

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

    구조체와 클래스 둘 다 새로운 인스턴스에 생성자 구문(initializer syntax)을 사용합니다.

    가장 단순한 형식의 생성자 구문은 Resolution()이나 VideoMode()처럼 클래스 또는 구조체 타입 이름 뒤에 빈 괄호를 사용합니다.

    이는 클래스나 구조체의 어떤 속성이든 자신의 기본 값으로 초기화한 새 인스턴스를 생성합니다.

     

    Accessing Properties (속성에 접근하기)

    점 구문(dot syntax)을 사용하여 인스턴스의 속성에 접근할 수 있습니다.

    점 구문은 인스턴스 이름 바로 뒤에 어떤 공백도 없이 마침표(.)로 구분하여 속성 이름을 작성합니다.

    print("The width of someResolution is \(someResolution.width)")
    // Prints "The width of someResolution is 0"

    이 예제에서 someResolution.width는 someResolution의 width 속성을 참조하여 0이라는 초기 값을 반환합니다.

     

    VideoMode의 resolution 속성에 있는 width 속성 같이 하위 속성으로 들어 갈 수 있습니다.

    print("The width of someVideoMode is \(someVideoMode.resolution.width)")
    // Prints "The width of someVideoMode is 0"

     

    변수 속성에 새로운 값을 할당하기 위해 점 구문을 사용할 수도 있습니다.

    someVideoMode.resolution.width = 1280
    print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
    // Prints "The width of someVideoMode is now 1280"

     

    Memberwise Initializers for Structure Types (구조체 타입을 위한 멤버 초기자)

    모든 구조체엔 멤버 생성자(memberwise initializer)가 자동으로 생기며, 이것으로 새로운 구조체 인스턴스의 멤버 속성을 초기화할 수 있습니다.

    새 인스턴스 속성을 위한 기본 값은 멤버 생성자에 이름을 써서 전달할 수 있습니다.

    let vga = Resolution(width: 640, height: 480)

     

    구조체와 달리 클래스 인스턴스는 기본 멤버 생성자를 받지 않습니다.

     


    Structures and Enumerations Are Value Types (구조체와 열거체는 값 타입)

    값 타입(value type)은 변수나 상수에 할당할 때나 함수에 전달할 때, 값을 복사하는(copied) 타입입니다.

     

    실제로 이전 장들 전반에 걸쳐 광범위하게 값 타입을 사용해왔습니다.

    사실 Swift의 모든 기본 타입(integers, floating-point numbers, Booleans, strings, arrays, dictionaries)들은 값 타입이며 구조체로 구현되어 있습니다.

     

    Swift에서 모든 구조체와 열거체는 값 타입입니다.

    이는 생성한 어떤 구조체와 열거체 인스턴스든, 이들의 속성으로 가진 어떤 값 타입이든 코드에서 전달할 때 항상 복사된다는 의미입니다.

    배열, 딕셔너리, 문자열같이 표준 라이브러리가 정의하는 콜렉션(collections)은 복사 성능 비용을 감소시키려고 최적화 합니다.
    곧바로 복사하는 대신 이러한 컬렉션은 원본 인스턴스와 복사본 간에 요소가 저장되는 메모리를 공유합니다.
    만약 복사본 중 하나가 수정되면 수정 직전에 요소가 복사됩니다.
    코드 동작은 항상 즉시 복사가 발생한 것처럼 보입니다.

     

    이전 예제의 Resolution 구조체를 사용한 다음 예제를 봅시다.

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

    이 예제는 hd라는 상수를 선언하고 여기에 1920 * 1080으로 초기화한 Resolution 인스턴스를 설정합니다.

     

    그런 다음 cinema라는 변수를 선언하고 여기에 현재 hd 값을 설정합니다.

    Resolution은 구조체이기 때문에 기존 인스턴스의 복사본을 만들며 이 새 복사본을 cinema에 할당합니다.

    hd와 cinema는 width와 height는 같지만 서로 완전히 다른 두 개의 인스턴스입니다.

     

    그 다음 cinema의 width를 2048로 수정합니다.

    cinema.width = 2048

    cinema의 width 속성을 검사하면 바뀐 것을 보여줍니다.

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

     

    그러나 원본 hd 인스턴스의 width 속성은 여전히 1920이라는 예전 값을 가집니다.

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

     

    cinema는 현재 hd 값을 줄 땐, hd에 저장한 값(values)을 새로운 cinema 인스턴스로 복사합니다.

    끝단의 결과는 동일한 수치 값을 담은 완전히 분리된 인스턴스입니다.

    분리된 인스턴스이기 때문에 아래 그림처럼 cinema의 width를 바꿔도 hd에 영향을 주지 않습니다.

     

    열거체 동작도 똑같이 적용합니다.

    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"

    rememberedDirection에 currentDirection 값을 할당할 땐 실제로 그 값의 복사본을 설정합니다.

    그 후에 currentDirection 값을 바꾸는 건 rememberedDirection에 저장된 원본 값의 복사본에 영향을 주지 않습니다.

     


    Classes Are Reference Types (클래스는 참조 타입입니다)

    값 타입과 달리 참조 타입(reference types)은 변수나 상수에 할당할 때나 함수에 전달할 때 복사하지 않습니다.

    복사본 대신 동일한 기존 인스턴스에 대한 참조가 사용됩니다.

     

    다음 예제는 위에서 정의한 VideoMode 클래스를 사용합니다.

    let tenEighty = VideoMode()
    tenEighty.resolution = hd
    tenEighty.interlaced = true
    tenEighty.name = "1080i"
    tenEighty.frameRate = 25.0

    이 예제는 tenEighty라는 새로운 상수를 선언하고 여기에 새로운 VideoMode 클래스 인스턴스의 참조를 설정합니다.

    영상 모드에는 전에 있던 1920 * 1080 높이의 복사본을 할당하고 여러 속성을 설정합니다.

     

    그 다음 tenEighty에 alsoTenEighty라는 새로운 상수를 할당하고 alsoTenEighty의 프레임 재생 속도를 수정합니다.

    let alsoTenEighty = tenEighty
    alsoTenEighty.frameRate = 30.0

     

    클래스가 참조 타입이기 때문에 tenEighty와 alsoTenEighty 둘 다 실제로 동일한 VideoMode 인스턴스를 참조합니다.

    아래 그림처럼 이들은 사실상 동일한 단일 인스턴스이고 서로 다른 이름일 뿐입니다.

    tenEighty의 frameRate 속성을 검사하면 실제 VideoMode 인스턴스에 있는 30.0이라는 새로운 값으로 출력됩니다.

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

    이 예제는 참조 타입을 파악하는 게 얼마나 힘들어질 수 있는지 보여줍니다.

    tenEighty와 alsoTenEighty가 코드상 멀리 떨어져 있으면, 바뀐 모든 것을 찾는 게 어려울 수도 있습니다.

    tenEighty를 사용하는 곳마다 alsoTenEighty를 고려해야 하며 그 반대로 마찬가지입니다.

    이와 반대로 값 타입은 소스 파일에서 동일 값과 상호 작용하는 모든 코드가 서로 가까이 있기 때문에 더 파악하기 쉽습니다.

     

    두 클래스 인스턴스는 상수이지만 그들의 속성인 frameRate는 여전히 바꿀 수 있습니다.

    이는 두 클래스 인스턴스 자체의 값이 바뀌지 않기 때문입니다.

    바뀌는 건 VideoMode를 참조하는 상수 값이 아니라, 실제 VideoMode에 있는 frameRate 속성입니다.

     

    Identity Operators (식별 연산자)

    클래스는 참조 타입이기 때문에, 내부에서 여러 개의 상수와 변수가 동일한 단일 클래스 인스턴스를 참조하는 게 가능합니다. (구조체와 열거체는 상수나 변수에 할당하거나 함수에 전달할 때 항상 복사하기 때문에 이와 똑같지 않습니다.)

     

    두 상수 또는 변수가 정확하게 동일한 클래스 인스턴스를 참조하는 지 찾아내는 게 유용할 때가 있습니다.

    이것이 가능하도록, Swift는 두 개의 식별 연사자를 제공합니다.

    • 식별됨(Identical to) (===)
    • 식별되지 않음(Not identical to) (!==)

    이 연산자들을 사용하여 두 상수 또는 변수가 동일한 단일 인스턴스를 참조하는지 검사합니다.

    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는 클래스 타입인 두 상수 또는 변수가 정확하게 동일한 클래스 인스턴스를 참조한다는 의미입니다.

    equals to는 타입 설계자가 정의한 적절한 의미에 따라서 두 인스턴스의 값이 같다(eqaul)는 것을 의미합니다.

     

    Pointers (포인터)

    C, C++ 또는 Objective-C에 대한 경험이 있으면 메모리 주소를 참조할 때 포인터(pointers)를 사용한다는 걸 알 것입니다.

    어떠한 참조 타입 인스턴스를 참조하는 건 Swift 상수 또는 변수와 C의 포인터와 비슷하지만,

    메모리에 있는 주소의 직접적인 포인터는 아니며 참조를 나타내기 위해 별표(*)를 쓸 필요가 없습니다.

    그 대신, 이 참조들은 스위프트의 다른 어떤 상수나 변수인 것처럼 정의합니다.

    표준 라이브러리는 포인터와 직접 상호 작용할 필요할 있을 경우 사용할 수 있는 포인터와 버퍼 타입을 제공합니다.

     


    참조

    https://docs.swift.org/swift-book/

    https://bbiguduk.gitbook.io/swift/

     

     

    반응형