Properties

Groot·2022년 6월 27일
0

Swift Language Guide

목록 보기
8/24
post-thumbnail

Swift Language Guide - Properties

  • Properties는 특정 클래스, 구조 또는 열거형의 연관값입니다.
  • Stored Properties는 인스턴스의 일부로 상수 및 변수 값을 Stored하는 반면, 계산 Properties는 값을 계산(Stored하지 않고)합니다.
  • 계산 Properties는 클래스, 구조 및 열거형에서 사용이 가능합니다.
  • Stored Properties는 클래스와 구조체에서만 사용이 가능합니다.
  • Stored 및 계산 Properties는 일반적으로 특정 Type의 인스턴스와 연결됩니다. 그러나 프로퍼티은 타입 자체와 연결될 수도 있습니다. 이러한 프로퍼티을 타입 프로퍼티이라고 합니다.

📌 Stored Properties

  • 가장 단순한 형태의 Stored Properties은 특정 클래스 또는 구조의 인스턴스의 일부로 Stored되는 상수 또는 변수입니다.
  • Stored Properties은 가변 Stored Properties(var 키워드로 도입) 또는 상수 Stored Properties(let 키워드로 도입)이 될 수 있습니다.
  • 기본 Properties 값에 설명된 대로 정의의 일부로 Stored된 Properties에 대한 기본값을 제공할 수 있습니다.
  • 초기화하는 동안 Stored Properties의 초기 값을 설정하고 수정할 수도 있습니다.
  • 이는 초기화 중 상수 Properties 할당에 설명된 대로 상수 Stored Properties의 경우에도 마찬가지입니다.
    struct FixedLengthRange {
        var firstValue: Int
        let length: Int
    }
    var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
    // the range represents integer values 0, 1, and 2
    rangeOfThreeItems.firstValue = 6
    // the range now represents integer values 6, 7, and 8
  • FixedLengthRange의 인스턴스에는 firstValue라는 변수 Stored Properties과 length라는 상수 Stored Properties이 있습니다.
  • 위의 예에서 length는 새로운 범위가 생성될 때 초기화되며 그 이후에는 상수 Properties이기 때문에 변경할 수 없습니다.

📍 Stored Properties of Constant Structure Instances

  • 구조체의 인스턴스를 만들고 해당 인스턴스를 상수에 할당하면 변수 Properties으로 선언된 경우에도 인스턴스의 Properties을 수정할 수 없습니다.
    let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
    // this range represents integer values 0, 1, 2, and 3
    rangeOfFourItems.firstValue = 6
    // this will report an error, even though firstValue is a variable property
  • rangeOfFourItems는 상수로 선언되기 때문에(let 키워드 사용), firstValue가 변수 Properties이더라도 firstValue Properties을 변경할 수 없습니다.
  • 이 동작은 구조가 값 Type이기 때문입니다. 값 형식의 인스턴스가 상수로 표시되면 모든 Properties도 마찬가지입니다.
  • 참조 Type인 클래스의 경우에도 마찬가지입니다. 참조 Type의 인스턴스를 상수에 할당하면 해당 인스턴스의 변수 Properties을 계속 변경할 수 있습니다.

📍 Lazy Stored Properties

  • Lazy Stored Properties은 처음 사용할 때까지 초기 값이 계산되지 않는 Properties입니다.

  • 선언 전에 lazy 수정자를 작성하여 Lazy Stored Properties을 나타냅니다.

    인스턴스 초기화가 완료될 때까지 초기 값이 검색되지 않을 수 있으므로 Lazy Properties을 항상 변수로 선언해야 합니다(var 키워드 사용).
    상수 Properties은 초기화가 완료되기 전에 항상 값이 있어야 하므로 Lazy으로 선언할 수 없습니다.

  • Lazy Properties은 Properties의 초기 값이 인스턴스 초기화가 완료될 때까지 값을 알 수 없는 외부 요인에 종속될 때 유용합니다.

  • Lazy Properties은 Properties의 초기 값이 필요하지 않는 한 또는 필요할 때까지 수행되어서는 안 되는 복잡하거나 계산 비용이 많이 드는 설정을 요구할 때도 유용합니다.

    class DataImporter {
        /*
        DataImporter is a class to import data from an external file.
        The class is assumed to take a nontrivial amount of time to initialize.
        */
        var filename = "data.txt"
        // the DataImporter class would provide data importing functionality here
    }
    
    class DataManager {
        lazy var importer = DataImporter()
        var data: [String] = []
        // the DataManager class would provide data management functionality here
    }
    
    let manager = DataManager()
    manager.data.append("Some data")
    manager.data.append("Some more data")
    // the DataImporter instance for the importer property hasn't yet been created
  • DataManager 클래스에는 새로운 빈 문자열 배열로 초기화되는 데이터라는 Stored된 Properties이 있습니다.

  • 나머지 기능은 표시되지 않지만 이 DataManager 클래스의 목적은 이 String 데이터 배열을 관리하고 액세스를 제공하는 것입니다.

  • DataManager 클래스의 기능 중 일부는 파일에서 데이터를 가져오는 기능입니다.

  • 이 기능은 DataImporter 클래스에서 제공하며 초기화하는 데 상당한 시간이 걸린다고 가정합니다.

  • DataManager 인스턴스가 파일에서 데이터를 가져오지 않고도 데이터를 관리할 수 있기 때문에 DataManager 자체가 생성될 때 DataManager는 새로운 DataImporter 인스턴스를 생성하지 않습니다.

  • 대신 DataImporter 인스턴스를 처음 사용하는 경우 생성하는 것이 더 합리적입니다.

  • Lazy 수정자로 표시되기 때문에 가져오기 Properties에 대한 DataImporter 인스턴스는 파일 이름 Properties이 쿼리될 때와 같이 가져오기 Properties에 처음 액세스할 때만 생성됩니다.

    print(manager.importer.filename)
    // the DataImporter instance for the importer property has now been created
    // Prints "data.txt"

    Lazy 수정자로 표시된 Properties이 여러 스레드에서 동시에 액세스되고 Properties이 아직 초기화되지 않은 경우 Properties이 한 번만 초기화된다는 보장은 없습니다.

📍 Stored Properties and Instance Variables

  • Objective-C에 대한 경험이 있는 경우 클래스 인스턴스의 일부로 값과 참조를 Stored하는 두 가지 방법을 제공한다는 것을 알 수 있습니다.
  • Properties 외에도 인스턴스 변수를 Properties에 Stored된 값의 백업 Stored소로 사용할 수 있습니다.
  • Swift는 이러한 개념을 단일 Properties 선언으로 통합합니다.
  • Swift Properties에는 해당 인스턴스 변수가 없으며 Properties의 백업 Stored소에 직접 액세스할 수 없습니다.
  • 이 접근 방식은 다른 컨텍스트에서 값에 액세스하는 방법에 대한 혼동을 피하고 Properties 선언을 단일의 최종 문으로 단순화합니다.
  • 이름, Type 및 메모리 관리 특성을 포함하여 Properties에 대한 모든 정보는 Type 정의의 일부로 단일 위치에 정의됩니다.

📌 Computed Properties

  • Stored된 Properties 외에도 클래스, 구조 및 열거형은 실제로 값을 Stored하지 않는 계산된 Properties을 정의할 수 있습니다.
  • 대신 다른 Properties과 값을 간접적으로 검색하고 설정할 수 있는 getter 및 선택적 setter를 제공합니다.
    struct Point {
        var x = 0.0, y = 0.0
    }
    struct Size {
        var width = 0.0, height = 0.0
    }
    struct Rect {
        var origin = Point()
        var size = Size()
        var center: Point {
            get {
                let centerX = origin.x + (size.width / 2)
                let centerY = origin.y + (size.height / 2)
                return Point(x: centerX, y: centerY)
            }
            set(newCenter) {
                origin.x = newCenter.x - (size.width / 2)
                origin.y = newCenter.y - (size.height / 2)
            }
        }
    }
    var square = Rect(origin: Point(x: 0.0, y: 0.0),
                      size: Size(width: 10.0, height: 10.0))
    let initialSquareCenter = square.center
    // initialSquareCenter is at (5.0, 5.0)
    square.center = Point(x: 15.0, y: 15.0)
    print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
    // Prints "square.origin is now at (10.0, 10.0)"
  • 이 예에서는 기하학적 모양 작업을 위한 세 가지 구조를 정의합니다.
    • Point는 점의 x 및 y 좌표를 캡슐화합니다.
    • Size는 너비와 높이를 캡슐화합니다.
    • Rect는 원점과 크기로 사각형을 정의합니다.
  • Rect 구조는 center라는 계산된 Properties도 제공합니다.
  • Rect의 현재 중심 위치는 항상 원점과 크기에서 결정할 수 있으므로 중심점을 명시적인 Point 값으로 Stored할 필요가 없습니다.
  • 대신 Rect는 center라는 계산된 변수에 대한 사용자 지정 getter 및 setter를 정의하여 사각형의 중심이 실제 Stored된 Properties인 것처럼 작업할 수 있도록 합니다.
  • 위의 예는 square라는 새로운 Rect 변수를 생성합니다.
  • square 변수는 원점(0, 0), 너비와 높이 10으로 초기화됩니다.
  • 이 사각형은 아래 다이어그램에서 연한 녹색 사각형으로 표시됩니다.
  • 그런 다음 square 변수의 중심 Properties은 점 구문(square.center)을 통해 액세스되며, 이로 인해 중심에 대한 getter가 호출되어 현재 Properties 값을 검색합니다.
  • 기존 값을 반환하는 대신 getter는 실제로 정사각형의 중심을 나타내는 새 Point를 계산하고 반환합니다. 위에서 볼 수 있듯이 getter는 (5, 5)의 중심점을 올바르게 반환합니다.
  • 그런 다음 중앙 Properties은 (15, 15)의 새 값으로 설정되어 사각형을 오른쪽 위로 이동하고 아래 다이어그램에서 짙은 녹색 사각형으로 표시된 새 위치로 이동합니다.
  • center Properties을 설정하면 center에 대한 setter가 호출되어 Stored된 origin Properties의 x 및 y 값을 수정하고 사각형을 새 위치로 이동합니다.

📍 Shorthand Setter Declaration

  • 계산된 Properties의 setter가 설정할 새 값의 이름을 정의하지 않으면 기본 이름인 newValue가 사용됩니다.
    struct AlternativeRect {
        var origin = Point()
        var size = Size()
        var center: Point {
            get {
                let centerX = origin.x + (size.width / 2)
                let centerY = origin.y + (size.height / 2)
                return Point(x: centerX, y: centerY)
            }
            set {
                origin.x = newValue.x - (size.width / 2)
                origin.y = newValue.y - (size.height / 2)
            }
        }
    }

📍 Shorthand Getter Declaration

  • getter의 전체 본문이 단일 표현식인 경우 getter는 암시적으로 해당 표현식을 반환합니다.
  • 다음은 이 약식 표기법과 setter에 대한 약식 표기법을 활용하는 Rect 구조의 다른 버전입니다.
    struct CompactRect {
        var origin = Point()
        var size = Size()
        var center: Point {
            get {
                Point(x: origin.x + (size.width / 2),
                      y: origin.y + (size.height / 2))
            }
            set {
                origin.x = newValue.x - (size.width / 2)
                origin.y = newValue.y - (size.height / 2)
            }
        }
    }
  • getter에서 반환을 생략하는 것은 암시적 반환이 있는 함수에 설명된 대로 함수에서 반환을 생략하는 것과 동일한 규칙을 따릅니다.

📍 Read-Only Computed Properties

  • getter는 있지만 setter가 없는 계산된 Properties을 읽기 전용 계산된 Properties이라고 합니다.
  • 읽기 전용 계산 Properties은 항상 값을 반환하고 점 구문을 통해 액세스할 수 있지만 다른 값으로 설정할 수는 없습니다.

    읽기 전용 계산 Properties을 포함한 계산 Properties은 값이 고정되어 있지 않기 때문에 var 키워드를 사용하여 변수 Properties으로 선언해야 합니다.
    let 키워드는 상수 Properties에만 사용되어 인스턴스 초기화의 일부로 설정되면 값을 변경할 수 없음을 나타냅니다.

  • get 키워드와 중괄호를 제거하여 읽기 전용 계산 Properties 선언을 단순화할 수 있습니다.
    struct Cuboid {
        var width = 0.0, height = 0.0, depth = 0.0
        var volume: Double {
            return width * height * depth
        }
    }
    let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
    print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
    // Prints "the volume of fourByFiveByTwo is 40.0"
  • 이 예제에서는 너비, 높이 및 깊이 Properties이 있는 3D 직사각형 상자를 나타내는 Cuboid라는 새 구조를 정의합니다.
  • 이 구조는 또한 볼륨이라는 읽기 전용 계산 Properties을 가지고 있으며, 이 Properties은 직육면체의 현재 볼륨을 계산하고 반환합니다.
  • 특정 볼륨 값에 대해 너비, 높이 및 깊이 값을 사용해야 하는 것이 모호하기 때문에 볼륨을 설정 가능하게 하는 것은 의미가 없습니다.
  • 그럼에도 불구하고 외부 사용자가 현재 계산된 볼륨을 찾을 수 있도록 Cuboid가 읽기 전용 계산 Properties을 제공하는 것이 유용합니다.

📌 Property Observers

  • Properties Observers는 Properties 값의 변화를 관찰하고 반응합니다.
  • 새 값이 Properties의 현재 값과 같더라도 Properties 값이 설정될 때마다 Properties Observers가 호출됩니다.
  • 다음 위치에 Properties Observers를 추가할 수 있습니다.
    • 사용자가 정의한 Stored properties
    • 상속받은 Stored properties
    • 상속받은 Computed properties
  • 상속된 Properties의 경우 하위 클래스에서 해당 Properties을 재정의하여 Properties Observers를 추가합니다.
  • 정의한 계산된 Properties의 경우 Observers를 만들려고 하는 대신 Properties의 setter를 사용하여 값 변경을 관찰하고 응답합니다.
  • Properties에 대해 다음 Observers 중 하나 또는 둘 다를 정의할 수 있는 옵션이 있습니다.
    • willSet은 값이 Stored되기 직전에 호출됩니다.
    • didSet은 새 값이 Stored된 직후에 호출됩니다.
  • willSet Observers를 구현하면 새 Properties 값이 상수 매개변수로 전달됩니다.
  • willSet 구현의 일부로 이 매개변수의 이름을 지정할 수 있습니다.
  • 구현 내에서 매개변수 이름과 괄호를 작성하지 않으면 기본 매개변수 이름인 newValue와 함께 매개변수를 사용할 수 있습니다.
  • 마찬가지로 didSet 옵저버를 구현하면 이전 Properties 값을 포함하는 상수 매개변수가 전달됩니다.
  • 매개변수의 이름을 지정하거나 기본 매개변수 이름인oldValue를 사용할 수 있습니다.
  • didSet Observers 내의 Properties에 값을 할당하면 할당한 새 값이 방금 설정된 값을 대체합니다.

    슈퍼클래스 Properties의 willSet 및 didSet Observers는 슈퍼클래스 이니셜라이저가 호출된 후 서브클래스 이니셜라이저에서 Properties이 설정될 때 호출됩니다.
    슈퍼클래스 이니셜라이저가 호출되기 전에 클래스가 자체 Properties을 설정하는 동안에는 호출되지 않습니다.

    class StepCounter {
        var totalSteps: Int = 0 {
            willSet(newTotalSteps) {
                print("About to set totalSteps to \(newTotalSteps)")
            }
            didSet {
                if totalSteps > oldValue  {
                    print("Added \(totalSteps - oldValue) steps")
                }
            }
        }
    }
    let stepCounter = StepCounter()
    stepCounter.totalSteps = 200
    // About to set totalSteps to 200
    // Added 200 steps
    stepCounter.totalSteps = 360
    // About to set totalSteps to 360
    // Added 160 steps
    stepCounter.totalSteps = 896
    // About to set totalSteps to 896
    // Added 536 steps
  • StepCounter 클래스는 Int Type의 totalSteps Properties을 선언합니다.
  • 이것은 willSet 및 didSet Observers가 있는 Stored Properties입니다.
  • totalSteps에 대한 willSet 및 didSet Observers는 Properties에 새 값이 할당될 때마다 호출됩니다.
  • 새 값이 현재 값과 같더라도 마찬가지입니다.
  • 이 예제의 willSet Observers는 다가오는 새 값에 대해 newTotalSteps라는 사용자 정의 매개변수 이름을 사용합니다.
  • 이 예에서는 설정하려는 값을 단순히 출력합니다.
  • didSet Observers는 totalSteps 값이 업데이트된 후 호출됩니다.
  • totalSteps의 새 값을 이전 값과 비교합니다.
  • 총 단계 수가 증가한 경우 새 단계 수를 나타내는 메시지가 인쇄됩니다.
  • didSet Observers는 이전 값에 대한 사용자 정의 매개변수 이름을 제공하지 않으며 대신 oldValue의 기본 이름이 사용됩니다.

📌 Property Wrappers

  • Properties Wrappers는 Properties이 Stored되는 방식을 관리하는 코드와 Properties을 정의하는 코드 사이에 분리 계층을 추가합니다.

  • 예를 들어 스레드 안전성 검사를 제공하거나 기본 데이터를 데이터베이스에 Stored하는 Properties이 있는 경우 모든 Properties에 해당 코드를 작성해야 합니다.

  • Properties Wrappers를 사용하는 경우 Wrappers를 정의할 때 관리 코드를 한 번 작성한 다음 여러 Properties에 적용하여 해당 관리 코드를 재사용합니다.

  • Properties Wrappers를 정의하려면 wrapedValue Properties을 정의하는 구조, 열거형 또는 클래스를 만듭니다.

  • 아래 코드에서 TwelveOrLess 구조는 래핑된 값이 항상 12보다 작거나 같은 숫자를 포함하도록 합니다.

  • 더 큰 숫자를 Stored하도록 요청하면 대신 12를 Stored합니다.

    @propertyWrapper
    struct TwelveOrLess {
        private var number = 0
        var wrappedValue: Int {
            get { return number }
            set { number = min(newValue, 12) }
        }
    }

    위의 예제에서 number에 대한 선언은 변수를 private로 표시하여 number가 TwelveOrLess 구현에서만 사용되도록 합니다.
    다른 곳에 작성된 코드는 wrapedValue에 대한 getter 및 setter를 사용하여 값에 액세스하며 number를 직접 사용할 수 없습니다.

  • Properties 앞에 Wrappers의 이름을 Properties으로 작성하여 Properties에 Wrappers를 적용합니다.

  • 다음은 치수가 항상 12 이하인지 확인하기 위해 TwelveOrLess Properties Wrappers를 사용하는 사각형을 Stored하는 구조입니다.

    struct SmallRectangle {
        @TwelveOrLess var height: Int
        @TwelveOrLess var width: Int
    }
    
    var rectangle = SmallRectangle()
    print(rectangle.height)
    // Prints "0"
    
    rectangle.height = 10
    print(rectangle.height)
    // Prints "10"
    
    rectangle.height = 24
    print(rectangle.height)
    // Prints "12"
  • height 및 width Properties은 TwelveOrLess.number를 0으로 설정하는 TwelveOrLess의 정의에서 초기 값을 가져옵니다.

  • TwelveOrLess의 setter는 10을 유효한 값으로 처리하므로 10을 직사각형.height에 Stored하면 작성된 대로 진행됩니다.

  • 그러나 24는 TwelveOrLess가 허용하는 것보다 크므로 24를 Stored하려고 하면 결국 허용되는 가장 큰 값인 12로 rectangle.height를 설정하게 됩니다.

  • Properties에 Wrappers를 적용하면 컴파일러는 Wrappers에 대한 Stored소를 제공하는 코드와 Wrappers를 통해 Properties에 대한 액세스를 제공하는 코드를 합성합니다.

  • (Properties Wrappers는 래핑된 값을 Stored하는 역할을 하므로 이에 대한 합성 코드가 없습니다.) 특수 Properties 구문을 활용하지 않고도 Properties Wrappers의 동작을 사용하는 코드를 작성할 수 있습니다.

  • 예를 들어 다음은 @TwelveOrLess를 Properties으로 쓰는 대신 TwelveOrLess 구조에서 Properties을 명시적으로 래핑하는 이전 코드 목록의 SmallRectangle 버전입니다.

    struct SmallRectangle {
        private var _height = TwelveOrLess()
        private var _width = TwelveOrLess()
        var height: Int {
            get { return _height.wrappedValue }
            set { _height.wrappedValue = newValue }
        }
        var width: Int {
            get { return _width.wrappedValue }
            set { _width.wrappedValue = newValue }
        }
    }
  • _height 및 _width Properties은 Properties Wrappers TwelveOrLess의 인스턴스를 Stored합니다.

  • 높이 및 너비에 대한 getter 및 setter는 wrapValue Properties에 대한 액세스를 래핑합니다.

📍 Setting Initial Values for Wrapped Properties

  • 위 예제의 코드는 TwelveOrLess 정의에서 number에 초기 값을 제공하여 래핑된 Properties의 초기 값을 설정합니다.

  • 이 Properties Wrappers를 사용하는 코드는 TwelveOrLess로 래핑된 Properties에 대해 다른 초기 값을 지정할 수 없습니다. 예를 들어 SmallRectangle의 정의는 높이 또는 너비 초기 값을 제공할 수 없습니다.

  • 초기 값 설정 또는 기타 사용자 정의를 지원하려면 Properties Wrappers가 이니셜라이저를 추가해야 합니다.

  • 다음은 래핑 및 최대값을 설정하는 이니셜라이저를 정의하는 SmallNumber라는 TwelveOrLess의 확장된 버전입니다.

    @propertyWrapper
    struct SmallNumber {
        private var maximum: Int
        private var number: Int
    
        var wrappedValue: Int {
            get { return number }
            set { number = min(newValue, maximum) }
        }
    
        init() {
            maximum = 12
            number = 0
        }
        init(wrappedValue: Int) {
            maximum = 12
            number = min(wrappedValue, maximum)
        }
        init(wrappedValue: Int, maximum: Int) {
            self.maximum = maximum
            number = min(wrappedValue, maximum)
        }
    }
  • SmallNumber의 정의에는 init(), init(wrappedValue:) 및 init(wrappedValue:maximum:)의 세 가지 초기화 프로그램이 포함되어 있습니다.

  • Properties에 Wrappers를 적용하고 초기 값을 지정하지 않으면 Swift는 init() 초기화를 사용하여 Wrappers를 설정합니다.

    struct ZeroRectangle {
        @SmallNumber var height: Int
        @SmallNumber var width: Int
    }
    
    var zeroRectangle = ZeroRectangle()
    print(zeroRectangle.height, zeroRectangle.width)
    // Prints "0 0"
  • 높이와 너비를 감싸는 SmallNumber의 인스턴스는 SmallNumber()를 호출하여 생성됩니다.

  • 해당 이니셜라이저 내부의 코드는 기본값 0과 12를 사용하여 초기 래핑된 값과 초기 최대값을 설정합니다.

  • Properties Wrappers는 SmallRectangle에서 TwelveOrLess를 사용한 이전 예제와 같이 여전히 모든 초기 값을 제공합니다.

  • 해당 예제와 달리 SmallNumber는 Properties 선언의 일부로 이러한 초기 값 쓰기도 지원합니다.

  • Properties의 초기 값을 지정하면 Swift는 init(wrappedValue:) 초기화를 사용하여 Wrappers를 설정합니다.

    struct UnitRectangle {
        @SmallNumber var height: Int = 1
        @SmallNumber var width: Int = 1
    }
    
    var unitRectangle = UnitRectangle()
    print(unitRectangle.height, unitRectangle.width)
    // Prints "1 1"
  • Wrappers가 있는 Properties에 = 1을 쓰면 init(wrappedValue:) 이니셜라이저에 대한 호출로 변환됩니다.

  • 높이와 너비를 래핑하는 SmallNumber의 인스턴스는 SmallNumber(wrappedValue: 1)를 호출하여 생성됩니다.

  • 이니셜라이저는 여기에 지정된 래핑된 값을 사용하며 기본 최대값인 12를 사용합니다.

  • 사용자 정의 Properties 뒤에 괄호 안에 인수를 작성할 때, Swift는 Wrappers를 설정하기 위해 해당 인수를 허용하는 이니셜라이저를 사용합니다.

  • 예를 들어, 초기 값과 최대값을 제공하면 Swift는 init(wrappedValue:maximum:) 초기화를 사용합니다.

    struct NarrowRectangle {
        @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
        @SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
    }
    
    var narrowRectangle = NarrowRectangle()
    print(narrowRectangle.height, narrowRectangle.width)
    // Prints "2 3"
    
    narrowRectangle.height = 100
    narrowRectangle.width = 100
    print(narrowRectangle.height, narrowRectangle.width)
    // Prints "5 4"
  • 높이를 감싸는 SmallNumber의 인스턴스는 SmallNumber(wrappedValue: 2, maximum: 5)를 호출하여 생성되고, 너비를 감싸는 인스턴스는 SmallNumber(wrappedValue: 3, maximum: 4)를 호출하여 생성됩니다.

  • Properties Wrappers에 인수를 포함하면 Wrappers의 초기 상태를 설정하거나 Wrappers가 생성될 때 Wrappers에 다른 옵션을 전달할 수 있습니다.

  • 이 구문은 Properties Wrappers를 사용하는 가장 일반적인 방법입니다.

  • Properties에 필요한 모든 인수를 제공할 수 있으며 이니셜라이저에 전달됩니다.

  • Properties Wrappers 인수를 포함할 때 할당을 사용하여 초기 값을 지정할 수도 있습니다. Swift는 WrappedValue 인수처럼 할당을 처리하고 포함된 인수를 허용하는 이니셜라이저를 사용합니다.

    struct MixedRectangle {
        @SmallNumber var height: Int = 1
        @SmallNumber(maximum: 9) var width: Int = 2
    }
    
    var mixedRectangle = MixedRectangle()
    print(mixedRectangle.height)
    // Prints "1"
    
    mixedRectangle.height = 20
    print(mixedRectangle.height)
    // Prints "12"
  • 높이를 래핑하는 SmallNumber의 인스턴스는 기본 최대값 12를 사용하는 SmallNumber(wrappedValue: 1)를 호출하여 생성됩니다.

  • 너비를 래핑하는 인스턴스는 SmallNumber(wrappedValue: 2, maximum: 9)를 호출하여 생성됩니다.

📍 Projecting a Value From a Property Wrapper

  • 래핑된 값 외에도 Properties Wrappers는 프로젝션된 값을 정의하여 추가 기능을 노출할 수 있습니다.

  • 데이터베이스에 대한 액세스를 관리하는 Properties Wrappers는 투영된 값에 flushDatabaseConnection() 메서드를 노출할 수 있습니다.

  • 예상 값의 이름은 달러 기호($)로 시작한다는 점을 제외하고 래핑된 값과 동일합니다.

  • 코드가 $로 시작하는 Properties을 정의할 수 없기 때문에 투영된 값은 정의한 Properties을 절대 방해하지 않습니다.

  • 위의 SmallNumber 예제에서 Properties을 너무 큰 숫자로 설정하려고 하면 Properties Wrappers가 Stored하기 전에 숫자를 조정합니다.

  • 아래 코드는 Properties Wrappers가 새 값을 Stored하기 전에 Properties에 대한 새 값을 조정했는지 여부를 추적하기 위해 SmallNumber 구조에projectedValue Properties을 추가합니다.

    @propertyWrapper
    struct SmallNumber {
        private var number: Int
        private(set) var projectedValue: Bool
    
        var wrappedValue: Int {
            get { return number }
            set {
                if newValue > 12 {
                    number = 12
                    projectedValue = true
                } else {
                    number = newValue
                    projectedValue = false
                }
            }
        }
        init() {
            self.number = 0
            self.projectedValue = false
        }
    }
    
    struct SomeStructure {
        @SmallNumber var someNumber: Int
    }
    
    var someStructure = SomeStructure()
    
    someStructure.someNumber = 4
    print(someStructure.$someNumber)
    // Prints "false"
    
    someStructure.someNumber = 55
    print(someStructure.$someNumber)
    // Prints "true"
  • someStructure.$someNumber를 작성하면 Wrappers의 예상 값에 액세스합니다.

  • 4와 같이 작은 수를 Stored한 후 someStructure.$someNumber의 값은 false입니다.

  • 러나 55와 같이 너무 큰 숫자를 Stored하려고 시도하면 예상 값이 true가 됩니다.

  • Properties Wrappers는 모든 Type의 값을 프로젝션된 값으로 반환할 수 있습니다.

  • 이 예에서 Properties Wrappers는 숫자가 조정되었는지 여부에 관계없이 한 가지 정보만 노출하므로 해당 부울 값을 투영된 값으로 노출합니다.

  • 더 많은 정보를 노출해야 하는 Wrappers는 다른 데이터 Type의 인스턴스를 반환하거나 Wrappers의 인스턴스를 투영된 값으로 노출하기 위해 self를 반환할 수 있습니다.

  • Properties getter 또는 인스턴스 메서드와 같이 Type의 일부인 코드에서 투영된 값에 액세스할 때 self를 생략할 수 있습니다. 다른 Properties에 액세스하는 것처럼 Properties 이름 앞에.

  • 다음 예제의 코드는 $height 및 $width로 높이와 너비에 대한 Wrappers의 투영된 값을 참조합니다.

    enum Size {
        case small, large
    }
    
    struct SizedRectangle {
        @SmallNumber var height: Int
        @SmallNumber var width: Int
    
        mutating func resize(to size: Size) -> Bool {
            switch size {
            case .small:
                height = 10
                width = 20
            case .large:
                height = 100
                width = 100
            }
            return $height || $width
        }
    }
  • Properties Wrappers 구문은 getter 및 setter가 있는 Properties에 대한 구문 설탕이므로 높이 및 너비에 액세스하는 것은 다른 Properties에 액세스하는 것과 동일하게 동작합니다.

  • 예를 들어, resize(to:)의 코드는 Properties Wrappers를 사용하여 높이와 너비에 액세스합니다.

  • resize(to: .large)를 호출하면 .large에 대한 스위치 케이스는 사각형의 높이와 너비를 100으로 설정합니다.

  • Wrappers는 해당 Properties의 값이 12보다 큰 것을 방지하고 예상 값을 true로 설정하여 값을 조정했다는 사실을 기록합니다.

  • resize(to:)가 끝나면 return 문은 $height 및 $width를 확인하여 Properties Wrappers가 높이 또는 너비를 조정했는지 여부를 결정합니다.


📌 Global and Local Variables

  • Properties을 계산하고 관찰하기 위해 위에서 설명한 기능은 전역 변수와 지역 변수에도 사용할 수 있습니다.

  • 전역 변수는 함수, 메서드, 클로저 또는 Type 컨텍스트 외부에 정의된 변수입니다.

  • 지역 변수는 함수, 메서드 또는 클로저 컨텍스트 내에서 정의되는 변수입니다.

  • 이전 장에서 접했던 전역 및 지역 변수는 모두 Stored된 변수입니다. Stored Properties과 같은 Stored 변수는 특정 Type의 값에 대한 Stored소를 제공하고 해당 값을 설정 및 검색할 수 있도록 합니다.

  • 그러나 전역 또는 로컬 범위에서 계산된 변수를 정의하고 Stored된 변수에 대한 Observers를 정의할 수도 있습니다.

  • 계산된 변수는 값을 Stored하지 않고 계산하며 계산된 Properties과 같은 방식으로 작성됩니다.

    전역 상수와 변수는 Lazy Stored Properties와 유사한 방식으로 항상 느리게 계산됩니다. Lazy Stored Properties과 달리 전역 상수 및 변수는 Lazy 수정자로 표시할 필요가 없습니다.
    지역 상수와 변수는 결코 느리게 계산되지 않습니다.

  • Properties Wrappers를 로컬 Stored 변수에 적용할 수 있지만 전역 변수나 계산 변수에는 적용할 수 없습니다. 예를 들어, 아래 코드에서 myNumber는 SmallNumber를 Properties Wrappers로 사용합니다.

    func someFunction() {
        @SmallNumber var myNumber: Int = 0
    
        myNumber = 10
        // now myNumber is 10
    
        myNumber = 24
        // now myNumber is 12
    }
  • Properties에 SmallNumber를 적용할 때와 마찬가지로 myNumber 값을 10으로 설정하는 것이 유효합니다. Properties Wrappers는 12보다 큰 값을 허용하지 않기 때문에 myNumber를 24 대신 12로 설정합니다.


📌 Type Properties

  • 인스턴스 Properties은 특정 Type의 인스턴스에 속하는 Properties입니다. 해당 Type의 새 인스턴스를 만들 때마다 다른 인스턴스와 분리된 고유한 Properties 값 집합이 있습니다.
  • 해당 Type의 인스턴스가 아닌 Type 자체에 속하는 Properties을 정의할 수도 있습니다.
  • 해당 Type의 인스턴스 수에 관계없이 이러한 Properties의 복사본은 하나만 있습니다. 이러한 종류의 Properties을 Type Properties이라고 합니다.
  • Type Properties은 모든 인스턴스가 사용할 수 있는 상수 Properties(예: C의 정적 상수) 또는 해당 Type의 모든 인스턴스에 전역인 값을 Stored하는 변수 Properties(예: C의 정적 변수) 같은 특정 Type의 모든 인스턴스에 보편적인 값을 정의하는 데 유용합니다.
  • Stored된 Type Properties은 변수 또는 상수일 수 있습니다. 계산된 Type Properties은 항상 계산된 인스턴스 Properties과 같은 방식으로 변수 Properties으로 선언됩니다.

    Stored 인스턴스 Properties과 달리 Stored Type Properties에는 항상 기본값을 지정해야 합니다. 타입 자체에는 초기화 시 Stored된 타입 Properties에 값을 할당할 수 있는 이니셜라이저가 없기 때문입니다.
    Stored된 Type Properties은 처음 액세스할 때 느리게 초기화됩니다. 여러 스레드가 동시에 액세스하는 경우에도 한 번만 초기화되는 것이 보장되며 lazy 수정자로 표시할 필요가 없습니다.

📍 Type Property Syntax

  • C 및 Objective-C에서는 정적 상수 및 Type과 관련된 변수를 전역 정적 변수로 정의합니다.
  • 그러나 Swift에서 Type Properties은 Type 정의의 일부로 Type의 외부 중괄호 안에 작성되며 각 Type Properties은 지원하는 Type으로 명시적으로 범위가 지정됩니다.
  • static 키워드를 사용하여 Type Properties을 정의합니다. 클래스 Type에 대한 계산된 Type Properties의 경우 대신 class 키워드를 사용하여 하위 클래스가 상위 클래스의 구현을 재정의하도록 할 수 있습니다.
    struct SomeStructure {
        static var storedTypeProperty = "Some value."
        static var computedTypeProperty: Int {
            return 1
        }
    }
    enum SomeEnumeration {
        static var storedTypeProperty = "Some value."
        static var computedTypeProperty: Int {
            return 6
        }
    }
    class SomeClass {
        static var storedTypeProperty = "Some value."
        static var computedTypeProperty: Int {
            return 27
        }
        class var overrideableComputedTypeProperty: Int {
            return 107
        }
    }

    위의 계산된 Type Properties 예제는 읽기 전용 계산된 Type Properties에 대한 것이지만 계산된 인스턴스 Properties과 동일한 구문으로 읽기-쓰기 계산된 Type Properties을 정의할 수도 있습니다.

📍 Querying and Setting Type Properties

  • Type Properties은 인스턴스 Properties과 마찬가지로 점 구문으로 쿼리되고 설정됩니다.
  • 그러나 Type Properties은 해당 Type의 인스턴스가 아니라 Type에 대해 쿼리되고 설정됩니다.
    print(SomeStructure.storedTypeProperty)
    // Prints "Some value."
    SomeStructure.storedTypeProperty = "Another value."
    print(SomeStructure.storedTypeProperty)
    // Prints "Another value."
    print(SomeEnumeration.computedTypeProperty)
    // Prints "6"
    print(SomeClass.computedTypeProperty)
    // Prints "27"
  • 다음 예제에서는 여러 오디오 채널에 대한 오디오 레벨 측정기를 모델링하는 구조의 일부로 두 개의 Stored된 Type Properties을 사용합니다.
  • 각 채널에는 0에서 10(포함) 사이의 정수 오디오 레벨이 있습니다.
  • 아래 그림은 이러한 오디오 채널 중 두 개를 결합하여 스테레오 오디오 레벨 미터를 모델링하는 방법을 보여줍니다. 채널의 오디오 레벨이 0이면 해당 채널의 표시등이 켜지지 않습니다.
  • 오디오 레벨이 10이면 해당 채널의 모든 표시등이 켜집니다. 이 그림에서 왼쪽 채널의 현재 레벨은 9이고 오른쪽 채널의 현재 레벨은 7입니다.
  • 위에서 설명한 오디오 채널은 AudioChannel 구조의 인스턴스로 표시됩니다.
    struct AudioChannel {
        static let thresholdLevel = 10
        static var maxInputLevelForAllChannels = 0
        var currentLevel: Int = 0 {
            didSet {
                if currentLevel > AudioChannel.thresholdLevel {
                    // cap the new audio level to the threshold level
                    currentLevel = AudioChannel.thresholdLevel
                }
                if currentLevel > AudioChannel.maxInputLevelForAllChannels {
                    // store this as the new overall maximum input level
                    AudioChannel.maxInputLevelForAllChannels = currentLevel
                }
            }
        }
    }
  • AudioChannel 구조는 기능을 지원하기 위해 두 개의 Stored된 Type Properties을 정의합니다.
  • 첫 번째 thresholdLevel은 오디오 레벨이 취할 수 있는 최대 임계값을 정의합니다.
  • 이것은 모든 AudioChannel 인스턴스에 대해 상수 값 10입니다.
  • 오디오 신호가 10보다 높은 값으로 입력되면 이 임계값으로 제한됩니다(아래 설명 참조).
  • 두 번째 Type Properties은 maxInputLevelForAllChannels라는 변수 Stored Properties입니다.
  • 이것은 모든 AudioChannel 인스턴스에서 수신한 최대 입력 값을 추적합니다. 초기값 0으로 시작합니다.
  • AudioChannel 구조는 0에서 10까지의 스케일로 채널의 현재 오디오 레벨을 나타내는 currentLevel이라는 Stored된 인스턴스 Properties도 정의합니다.
  • currentLevel Properties에는 설정될 때마다 currentLevel 값을 확인하는 didSet Properties Observers가 있습니다. 이 Observers는 두 가지 검사를 수행합니다.
    • currentLevel의 새 값이 허용된 thresholdLevel보다 크면 Properties Observers는 currentLevel을 thresholdLevel로 제한합니다.
    • currentLevel의 새 값(캡핑 후)이 이전에 AudioChannel 인스턴스에서 수신한 값보다 높으면 Properties Observers는 maxInputLevelForAllChannels Type Properties에 새 currentLevel 값을 Stored합니다.

      이 두 가지 검사 중 첫 번째에서 didSet Observers는 currentLevel을 다른 값으로 설정합니다. 그러나 이로 인해 Observers가 다시 호출되지는 않습니다.

  • AudioChannel 구조를 사용하여 leftChannel 및 rightChannel이라는 두 개의 새 오디오 채널을 만들어 스테레오 사운드 시스템의 오디오 레벨을 나타낼 수 있습니다.
    var leftChannel = AudioChannel()
    var rightChannel = AudioChannel()
  • 왼쪽 채널의 currentLevel을 7로 설정하면 maxInputLevelForAllChannels Type Properties이 7로 업데이트되는 것을 볼 수 있습니다.
    leftChannel.currentLevel = 7
    print(leftChannel.currentLevel)
    // Prints "7"
    print(AudioChannel.maxInputLevelForAllChannels)
    // Prints "7"
  • 오른쪽 채널의 currentLevel을 11로 설정하려고 하면 오른쪽 채널의 currentLevel Properties이 최대값인 10으로 제한되고 maxInputLevelForAllChannels Type Properties이 10으로 업데이트되는 것을 볼 수 있습니다.
    rightChannel.currentLevel = 11 인쇄(rightChannel.currentLevel) // "10"을 출력 인쇄(AudioChannel.maxInputLevelForAllChannels) // "10"을 출력
profile
I Am Groot

0개의 댓글