티스토리 뷰

 

 

Properties — The Swift Programming Language (Swift 5.5)

Properties Properties associate values with a particular class, structure, or enumeration. Stored properties store constant and variable values as part of an instance, whereas computed properties calculate (rather than store) a value. Computed properties a

docs.swift.org

 

1차 수정 (2022 / 02 / 26) : 의역 및 오타, 스타일 수정

 

 

Properties (프로퍼티)

프로퍼티는 값(value)을 특정 클래스, 구조체, 열거형과 연결됩니다. 저장된 프로퍼티는 변수와 상수를 인스턴스의 일부로 저장하고, 연산 프로퍼티는 값을 계산합니다. 연산 프로퍼티는 클래스, 구조체, 열거형에 의해 제공됩니다. 저장된 프로퍼티는 클래스와 구조체로만 제공됩니다.

 

저장된 프로퍼티와 연산 프로퍼티는 종종 특정 타입의 인스턴스와 연관되어 있습니다. 그렇지만 프로퍼티는 타입 그자체와도 연관될 수 있습니다. 특정 프로퍼티는 타입 프로퍼티로 알려져 있습니다.

 

사용자 액션에 따라 반응해 변경되는 프로퍼티 값을 모니터링하기 위해 프로퍼티 옵져버(observers) 를 정의할 수 있습니다. 프로퍼티 옵져버는 사용자가 직접 정의하는 저장된 프로퍼티에 의해 추가되며, 슈퍼클래스를 상속하는 하위 클래스의 프로퍼티에 추가될 수 있습니다.

 

여러 프로퍼티의 getter, setter 를 재사용하기 위해서 프로퍼티 래퍼(wrapper)를 사용할 수 있습니다.

 

 

 

 

Stored Properties(저장 프로퍼티)

가장 간단한 형태로, 저장 프로퍼티는 특정 클래스 혹은 구조체 인스턴스의 일부로 저장되는 상수, 변수입니다. 저장된 프로퍼티는 변수 저장 프로퍼티(variable constant properties) 혹은 상수 저장 프로퍼티(constant stored properties) 가 될 수 있습니다.

 

저장 프로퍼티를 정의할 때 기본 프로퍼티 값(default property values)을 제공할 수 있습니다. 초기화 과정에서 저장 프로퍼티 값을 설정하고 수정할 수 있습니다. 이는 상수 저장 프로퍼티에도 적용됩니다.

 

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

예제에서 FixedLengtRange라는 구조체를 선언했습니다. 구조체는 firstValue라는 변수 저장 프로퍼티와 length 라는 상수 저장 프로퍼티를 가집니다. length는 상수 프로퍼티이기 때문에 초기화 될 때 값이 저장되고 후에는 값을 변경할 수 없습니다.

 

Stored Properties of Constant Structure Instances(상수 구조체 인스턴스의 저장 프로퍼티)

만약 구조체의 인스턴스를 만들고 상수로 할당하면, 변수 프로퍼티로 선언되었어도 인스턴스의 프로퍼티를 수정할 수 없습니다.

 

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 는 상수로 선언되었으므로 firstValue가 변수 프로퍼티임에도 불구하고 수정할 수 없습니다.

이 동작은 구조체가 값 타입(value type) 이기 때문입니다. 값 타입의 인스턴스를 상수로 표시하면 모든 프로퍼티도 상수로 표현됩니다.

똑같은 일이 참조 타입(reference types)인 클래스에서는 일어나지 않습니다. 만약 상수에 참조 타입의 인스턴스를 할당하면, 인스턴스의 변수 프로퍼티를 바꿀 수 있습니다.

 

 

 

Lazy Stored Properties(지연 저장 프로퍼티)

지연 저장 프로퍼티(lazy stored properties) 는 첫 번째로 프로퍼티가 사용되기 전까지 그 초기값이 계산되지 않는 프로퍼티입니다. 선언할 때 lazy 키워드를 사용해 지연 저장 프로퍼티를 나타냅니다.

 

항상 무조건 지연 저장 프로퍼티는 변수로 선언해야 합니다. 인스턴스의 초기화가 완료될 때까지 초기 값이 검색되지 않을 수 있기 때문입니다. 상수 프로퍼티는 항상 반드시 초기화가 완료되기 전에 값을 가지므로 lazy로 선언할 수 없습니다.

 

지연 프로퍼티는 프로퍼티의 초기값이 `인스턴스의 초기화 완료 전까지 값을 알 수 없는 외부 요인`에 의존할 때 유용합니다. 지연 프로퍼티는 프로퍼티 초기 값에 복잡하거나 계산이 많이 필요한 설정이 필요해서, 필요한 경우가 아니면 수행하지 않아야 할 때 유용합니다.

 

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

예제에서 복잡한 클래스의 불필요한 초기화를 피하기 위해 lazy 키워드를 사용합니다. DataManager 클래스는 빈 String 배열인 data 저장 프로퍼티를 호출합니다. 비록 나머지 기능들이 아직 보이지 않지만, DataManager는 String 배열을 관리하고 접근하는 목적일 것입니다.

 

DataManager 클래스의 기능의 일부는 파일에서 데이터를 가져옵니다. 이 기능은 DataImporter 클래스에 의해 제공되는데, 초기화하는데 많은 시간이 걸릴 것으로 예상됩니다. DataImporter 인스턴스를 초기화할 때 파일을 열고 메모리를 읽어야 하기 때문일 것입니다.

 

DataManager 인스턴스가 데이터를 파일에서 읽어오지 않아도 데이터를 관리할 수 있기 때문에, DataManager는 새로운 DataImporter가 사용될 때 까지 만들지 않습니다. 대신에 DataImporter가 처음 사용될 때 만드는 것이 더 효과적입니다.

 

lazy 키워드를 사용했기 때문에 importer 프로퍼티는 처음으로 사용할 때 만들어집니다.

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

 

만약 lazy 프로퍼티가 동시에 여러 스레드에서 접근되고 초기화되지 않았더라면, 프로퍼티가 1번만 초기화된다고 보장할 수 없습니다.

 

 

 

 

Stored Properties and Instance Variables(저장 프로퍼티와 인스턴스 변수)

만약 Objective-C를 사용해봤다면, 클래스 인스턴스의 일부로 `값과 참조를 저장하기 위한 두가지 방식`이 제공된다는 것을 알 것입니다. 프로퍼티 외에도, 프로퍼티에 저장된 값의 백업 저장소로 인스턴스 변수를 사용할 수 있습니다.

 

Swift는 이러한 개념을 단일 프로퍼티 선언(single property declaration)으로 통합합니다. Swift 프로퍼티에는 해당 인스턴스 변수가 없으며, 프로퍼티에 대한 백업 저장소에 직접 접근할 수 없습니다. 이러한 접근은 다른 상황(context)에서 값에 접근하는 방법에 대한 혼란을 방지하고, 프로퍼티의 선언을 하나의 명확한 문장으로 간단하게 합니다. 프로퍼티에 대한 모든 정보(이름, 타입, 메모리 관리 특성) 은 타입 정의의 일부로 하나의 위치에 저장됩니다.

 

 

 

 

Computed Properties(연산 프로퍼티)

저장 프로퍼티 외에도 클래스, 구조체, 열거형을 `실제로는 값을 저장하지 않는` 연산 프로퍼티로 선언할 수 있습니다. 대신에 다른 프로퍼티와 값을 간접적으로 검색하고 설정할 수 있는 getter와 optional 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
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)"

예제는 도형을 나타내기 위해 3가지 구조체를 정의합니다

  • Point - x,y 좌표 캡슐화
  • Size - width 와 heigth 캡슐화
  • Rect - 원점과 크기로 사각형 정의

Rect 구조체는 center라는 연산 프로퍼티를 제공합니다. 현재 Rect의 중심점은 origin 과 size로 결정되므로, 중심점을 명시적 Point 값으로 저장할 필요가 없습니다. 대신에 Rect는 계산된 변수 center 를 호출하기 위해 getter 와 setter를 정의 합니다. 직사각형의 center를 실제 저장된 프로퍼티처럼 사용할 수 있습니다.

 

출처 : Swift 공식 문서

예제에서 새로운 변수 square에 Rect를 호출합니다. square는 원점 (0,0) width 와 height 이 10으로 초기화 됩니다. 위 그림에서 파란색 사각형으로 표현됩니다. 

 

square의 변수 center 프로퍼티는 현재 프로퍼티 값을 검색합니다. 기존의 값을 리턴하는 대신, getter는 사각형의 중심을 나타내는 Point를 실제로 계산하고 리턴합니다. 위의 그림처럼 중심점 (5, 5) 를 반환합니다.

 

center 프로퍼티는 새로운 값 (15,15)로 설정 되었습니다. setter를 호출해 center 프로퍼티를 설정하고 사각형은 새로운 위치로 이동합니다.

 

 

 

Shorthand Setter Declaration(짧은 setter 선언)

만약 연산 프로퍼티의 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)
        }
    }
}

기존 예제에서 set (newCenter)로 선언해 newCenter를 구문안에서 사용했는데, 위 예제에서는 newCenter를 사용하지않고 기본(default) newValue를 사용했습니다.

 

 

 

 

Shorthand getter Declaration(짧은 getter 선언)

만약 getter 실행문이 단일 표현식(1줄 짜리) 이라면, getter는 암묵적으로 해당 표현식을 리턴합니다. 

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가 1줄이므로 return을 생략했습니다.

 

 

 

 

Read-Only Computed Properties(읽기 전용 연산 프로퍼티)

getter는 있지만 setter가 없는 연산 프로퍼티를 읽기 전용 연산 프로퍼티라고 합니다. 읽기 전용 프로퍼티는 항상 값을 리턴하고, .(점) 을 사용해 접근하지만 다른 값으로 바꿀 수는 없습니다.

 

읽기 전용 프로퍼티는 반드시 var 키워드를 사용해 변수프로퍼티로 사용합니다. let 키워드는 값이 인스턴스 초기화에 의해 선언되고 나서 바뀌지 않는다는 것을 나타내기 위해 상수 프로퍼티에만 사용됩니다.

 

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"

예제는 Cuboid라는 구조체를 정의합니다. 이 구조체는 volume 만을 호출하는 읽기 전용 프로퍼티입니다. 

 

 

 

Property Observers(프로퍼티 옵저버)

프로퍼티 옵저버는 프로퍼티 값을 관찰하고 변화에 응답합니다. 프로퍼티 옵저버는 프로퍼티 값이 설정될 때 매번 호출되며, 새 값이 현재 프로퍼티 값과 동일해도 예외없이 호출됩니다.

 

아래와 같은 곳에 프로퍼티 옵저버를 사용합니다

  • 저장 프로퍼티를 선언한 곳
  • 저장 프로퍼티를 상속한 곳
  • 연산 프로퍼티를 상속한 곳

상속 프로퍼티의 경우, 하위 클래스(subclass)에서 해당 프로퍼티를 재정의하여 프로퍼티 옵저버를 추가합니다.

정의한 연산 프로퍼티의 경우, 옵저버를 만드는 대신, 프로퍼티 setter 를 사용해 관찰하고 변화되는 값에 응답합니다.

 

프로퍼티의 옵저버에 대해 정의할 수 있는 선택지가 있습니다.

  • willSet은 값이 저장되기 바로 직전에 호출됩니다
  • didSet은 값이 저장되고 나서 즉시 호출됩니다

willSet 옵저버를 실행하면, 상수 매개변수(parameter)로써 새로운 프로퍼티를 통과시킵니다. willSet 실행의 일부로 이 매개변수에 대해 이름을 설정 할 수 있습니다. 매개변수 이름을 설정하지 않고 ()를 사용하지 않으면 newValue 라는 기본값(default value)를 기본 매개변수 이름으로 사용할 수 있습니다.

 

비슷하게, didSet 옵저버를 실행하면, 예전 프로퍼티 값을 가지고 있는 상수 매개변수를 통과시킵니다. 매개변수의 이름을 설정해줘도 되고, 기본값 oldValue를 사용해도 됩니다. didSet 옵져버로 프로퍼티에 값을 할당하면, 새로운 값이 예전 값을 대체합니다.

 

프로퍼티가 하위클래스(subclass) 이니셜라이저에 설정된 경우, 슈퍼클래스 이니셜라이져 호출이 끝나면 슈퍼클래스(최상위 클래스) 프로퍼티의 willSet 과 didSet 옵저버가 호출됩니다. 클래스가 자체 프로퍼티를 세팅하는 동안, 즉 슈퍼클래스 이니셜라이져가 호출되기 전까지, 옵저버들은 호출되지 않습니다.

 

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

willSet과 didSet 동작에 대한 예제입니다.

새로운 클래스 StepCounter를 정의합니다. StepCounter는 totalSteps라는 Int 타입 프로퍼티를 선언합니다. totalSteps는 willSet과 didSet 옵져버를 가지는 저장 프로퍼티입니다.

totalSteps를 위한 willSet 과 didSet 옵져버는 프로퍼티가 새로운 값을 할당 받을 때 호출됩니다. 현재 값과 새로 할당되는 값이 같아도 똑같이 호출됩니다.

 

예제의 willSet 옵저버는 새로운 값에 newTotalSteps 라는 사용자 지정 매개 변수 이름을 사용합니다.

didSet 옵저버는 totalSteps 값이 업데이트 된 후 호출됩니다. totalSteps의 새로운 값과 오래된 값을 비교합니다. totalSteps가 증가하면, 증가한 수 만큼 메시지를 출력합니다. 예제에서 didSet 옵저버는 사용자 지정 매개 변수 이름을 설정하지 않았으므로 기본값(default value) 인 oldValue를 사용합니다.

 

 

옵저버가 있는 프로퍼티를 in-out 매개변수로 함수에 전달하는 경우, willSet 과 didSet 옵저버는 항상 호출됩니다. 이는 in-out 매개변수를 위한 copy-in copy-out 메모리 때문입니다. 함수의 끝에 프로퍼티에 값이 쓰여집니다.

 

 

 

 

 

 

 

Property Wrappers(프로퍼티 래퍼)

프로퍼티 래퍼는 `프로퍼티가 어떻게 저장되는지 관리하는 코드`와  `프로퍼티를 정의하는 코드` 사이에 구분하는 계층(layer)을 추가합니다. 예를 들어, 쓰레드-안전 확인을 제공하거나 데이터베이스에 기본 데이터를 저장하는 프로퍼티가 있다면, 모든 코드에 그 프로퍼티를 다 적어야합니다. 만약 프로퍼티 래퍼를 사용하면, 래퍼를 정의할 때 관리코드(management code)를 한 번만 작성하고, 관리코드를 여러 프로퍼티에 대해 재사용합니다.

 

프로퍼티 래퍼를 사용하기 위해, wrappedValue 프로퍼티를 사용해 구조체, 열거형, 클래스를 정의합니다.

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

예제에서 TwelveOrLess 구조체는 감싸고 있는 값이 항상 12보다 작거나 같은 숫자를 포함하도록 보장합니다. 12보다 큰 숫자를 저장하려고 하면, 12를 저장합니다. setter가 새로운 값이 12보다 작은 것을 보장하며, getter가 저장된 값을 리턴합니다.

 

프로퍼티 앞에 속성(attribure)으로 래퍼의 이름을 적음으로써, 프로퍼티에 래퍼를 적용합니다.

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"

예제에서 TeleveOrLess 프로퍼티 래퍼를 사용해 height와 width 프로퍼티를 선언했습니다. height 에 10을 할당하면 12보다 작아 그 값이 그대로 저장 되었지만, 24를 할당하면 12보다 크므로 12 가 저장됩니다.

 

래퍼를 프로퍼티에 적용할 때, 컴파일러는 `래퍼를 위해 저장공간을 제공하는 코드`와 `래퍼를 통해 프로퍼티에 접근을 제공하는 코드`를 합성합니다. (프로퍼티 래퍼는 래핑된 값에 대한 책임이 있으므로, 코드 합성이 일어나지 않습니다)

특수 속성 구문(special attribute syntax) 를 사용하지 않고, 프로퍼티 래퍼 행위를 사용하는 코드를 적을 수 있습니다.

 

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 }
    }
}

예제에서 smallRectange은 명시적으로 TweleveOrLess 구조체로 감쌉니다. _height 과 _width 프로퍼티는 프로퍼티 래퍼의 인스턴스를 저장합니다. height과 width의 getter setter는 wrappedValue 프로퍼티에 대한 접근을 감쌉니다.

 

 

 

 

Setting Initail Value for Wrapped Properties(감싸진 프로퍼티를 위한 초기값 설정)

위의 예제에서 TwelveOrLess의 정의에 nubmer 에 초기 값을 줌으로써, 감싸진 프로퍼티에 초기값을 제공했습니다. 이 프로퍼티 래퍼를 사용한 코드는 TweleveOrLess로 감싸진 프로퍼티에 대해 다른 특정한 초기값을 설정할 수 없습니다. 예를 들어 SmallRectangle 구조체에서 height 과 width에게 초기값을 줄 수 없습니다. 초기 값 또는 사용자 설정을 지원하려면, 프로퍼티 래퍼는 이니셜라이져를 추가해야 합니다.

 

@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)
    }
}

예제에서 TwelveOrLess의 확장 버전인 SmallNumber 구조체는 이니셜라이져를 통해 maximum과 number를 설정합니다. SmallNumber는 3가지 이니셜라이져 init(), init(wrappedValue:), init(wrappedValue:maximum:) 를 가집니다.

 

 

프로퍼티에 래퍼를 적용하고 초기값을 설정하지 않는다면, Swift는 래퍼를 설정하기 위해 init() 이니셜라지져를 사용합니다.

struct ZeroRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int
}

var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"

height 과 width를 감싸는 smallNumber의 인스턴스는 SmallNumber()를 호출함으로써 생성됩니다. 이니셜라이져 내 코드는 초기 감싸진 값(wrapped value)과 초기 최대 값(maximum value)를 설정합니다. 프로퍼티 래퍼는 여전히 모든 초기값을 제공합니다.

 

 

프로퍼티를 위해 초기값을 설정하면, Swift는 init(wrappedValue:)를 사용합니다.

struct UnitRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber var width: Int = 1
}

var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// Prints "1 1"

래퍼를 사용해 프로퍼티를 1 로 작성하면, init(wrappedValue:) 이니셜라이져가 호출됩니다.

 

 

괄호 안의 사용자 지정 속성(attribute) 뒤에 인수를 쓸 때 , Swift는 해당 인수가 있는 이니셜라이져를 호출합니다.

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"

예제에서는 init(wrappedValue:maximum:)를 호출합니다. height는 SmallNumber(wrappedValue: 2, maximum: 5)를 호출 함으로써 생성되고, width는 SmallNumber(wrappedValue: 3, maximum: 4)를 호출함으로써 생성됩니다.

 

프로퍼티 래퍼에 인수를 포함하면, 래퍼의 초기 상태를 설정하거나 래퍼가 생성될 때 다른 옵션을 설정할 수 있습니다. 이 구문은 프로퍼티 래퍼를 사용하는 가장일반적인 방법입니다. 프로퍼티에 필요한 모든 인수를 제공할 수 있으며 해당 변수는 이니셜라이져로 젼달됩니다.

 

프로퍼티 래퍼 인수를 포함할 때, 할당(assignment) 을 이용해 초기값을 설정할 수 있습니다. 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"

예제에서 height는 smallNumber를 그냥 호출했으므로 maximum 값이 12입니다. 하지만 width는 특정 maximum 9를 인수로 넘겨줬으므로 smallNumber(wrappedValue:1, maximum: 9) 가 호출됩니다.

 

 

 

Projecting a Value From a Property Wrapper(프로퍼티에서 값 기획)

프로퍼티 래퍼에서 projected value 를 정의해서 추가 기능을 내보낼 수 있습니다. projected value 이름은 감싸진 값의 이름에 $를 붙여 사용합니다. 코드에서는 $로 시작하는 프로퍼티를 선언할 수 없으므로, projected value는 사용자가 정의하는 프로퍼티를 방해하지 않습니다.

 

위의 SmallNumber 예제에서, number를 너무 크게 설정하려고하면 프로퍼티 래퍼가, 값을 저장하기 전에 그 값을 조정했습니다. 프로퍼티 래퍼가 있는지 추적하기 위해 projectedvalue를 추가한, SmallNumber 구조체는 새로운 값을 저장하기 전에 프로퍼티를 위해 새로운 값을 조정합니다.

@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를 통해 래퍼의 projected value 에 접근합니다. 4라는 숫자를 저장하고 나면 someStructure.$someNumber는 false가 됩니다. 55 라는 큰 숫자를 저장하면 someStructure.$someNumber는 true가 됩니다.

 

프로퍼티 래퍼는 project value 에 대해 모든 타입의 값을 리턴할 수 있습니다. 예제에서는 프로퍼티 래퍼는 숫자가 maximum 값보다 큰지 작은지에 대한 정보만 Boolean 값을 통해 보여줍니다. 다른 데이터 타입을 리턴해 더 많은 정보를 보여줄 수 있으며, self 를 리턴해 래퍼의 인스턴스를 projected value 로써 보여줄 수 있습니다.

 

Projected value에 접근할 때, 프로퍼티의 getter 혹은 인스턴스 메소드를 사용할 경우, self를 생략할 수 있습니다.

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
    }
}

위 예제에서는 $를 사용해 height 과 width에 접근했습니다. 왜냐하면 프로퍼티 래퍼 구문은 getter setter를 가지는 프로퍼티를 위해 쉽게 작성(syntactic sugar) 되었기 때문입니다.

resize의 마지막에서 프로퍼티 래퍼가 height 혹은 width를 조절했는지 확인하기 위해 $height 과 $width를 사용합니다.

 

 

 

Global and Local Variables(전역 변수와 지역변수)

프로퍼티를 계산하고 관찰하기 위해 위에서 설명한 기능들은 전역 변수(gloval variables)와 지역 변수(local variables)에도 동일하게 사용 가능합니다. 전역변수는 함수, 메소드, 클로져, 타입 컨텍스트 밖에 정의된 변수 입니다. 지역 변수는 함수, 메소드, 클로져 컨텍스트 안에 정의된 변수입니다.

 

저장된 프로퍼티와 같이 저장된 변수도 특정 타입 값에 대한 저장소를 제공하고 해당 값을 설정하고 검색할 수 있습니다.

 

전역 또는 지역 범위에서, 연산 변수(computed variables) 를 정의할 수 있고, 저장된 변수를 위한 옵저버도 정의할 수 있습니다. 연산 변수는 값을 저장하기 보다는 계산하며, 연산 프로퍼티와 같은 방식으로 작성됩니다.

 

전역 상수와 변수는 Lazy Stored Properties 처럼 항상 느리게(lazily) 연산됩니다. Lazy Stored Properties와는 다르게 전역 상수와 변수는 lazy 키워드를 사용하지 않습니다. 지역 변수는 절대 느리게(lazily) 연산되지 않습니다.

 

 

지역 저장 변수에는 프로퍼티 래퍼를 적용할 수 있지만, 전역 변수나 연산 변수에는 적용할 수 없습니다.

func someFunction() {
    @SmallNumber var myNumber: Int = 0

    myNumber = 10
    // now myNumber is 10

    myNumber = 24
    // now myNumber is 12
}

smallNumer 를 프로퍼티에 적용했던 것처럼, myNumber를 10으로 설정한 것은 유효합니다. 하지만 12보다 큰 24를 설정하면 프로퍼티 래퍼가 12로 저장합니다.

 

 

 

 

Type Properties(타입 프로퍼티)

인스턴스 프로퍼티는 특정 타입의 인스턴스에 해당하는 프로퍼티입니다. 해당 유형의 새 인스턴스를 만들때 마다, 해당 인스턴스는 다른 인스턴스와 별개로, 고유한 프로퍼티 값 집합(set)이 있습니다.

 

또한 해당 타입의 어떠한 인스턴스에도 속하지 않고, 타입 자체에 속하는 프로퍼티를 정의할 수 있습니다. 생성한 해당 타입의 인스턴스의 수에 상관없이, 프로퍼티의 복사본은 1개만 있습니다. 이러한 종류의 프로퍼티를 타입 프로퍼티라고 합니다.

 

타입 프로퍼티는 `특정 타입의 모든 경우에 공통적인 값`을 정의할 때 유용합니다. 예를 들어 모든 인스턴스가 사용할 수 있는 상수 프로퍼티나, 해당 타입의 모든 인스턴스에 전역 값을 저장하는 변수 프로퍼티에 대해 유용합니다.

 

타입 프로퍼티는 변수 혹은 상수로 저장합니다. 연산 타입 프로퍼티는 항상 변수 프로퍼티로 선언됩니다.

 

저장 인스턴스 프로퍼티(Stored instance properties)와 다르게, 저장 타입 프로퍼티에는 항상 기본 값(default value)를 줘야 합니다. 초기화시 타입 자체에 저장 타입 프로퍼티에 값을 할당할 수 있는 이니셜라이져가 없기 때문입니다.

저장 타입 프로퍼티는 첫 번째 접근에서는 느리게(lazily) 초기화됩니다. 멀티 쓰레드에서 접근하는 경우에도 1번만 초기화 되도록 보장됩니다. lazy 키워드를 사용할 필요가 없습니다.

 

 

 

Type Property Syntax(타입 프로퍼티 구문)

C 언어와 Objective-C 에서는 타입과 관련된 정적(static) 상수와 변수를 전역 정적 변수로 선언합니다. 하지만 Swift에서는 타입프로퍼티는 타입 정의의 일부로 외부 { } 안에 작성되며, 각각의 타입 프로퍼티는 타입으로 명시적으로 범위가 지정됩니다.

 

static 키워드를 사용해 타입 프로퍼티를 정의합니다. 클래스 타입의 연산 타입 프로퍼티를 위해, 슈퍼클래스의 실행을 재정의 하기 위해 상속 클래스를 허용하는 것 대신에 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
    }
}

 

 

 

Querying and Setting Type Properties (타입 프로퍼티 요청과 설정)

타입 프로퍼티는 . 을 사용해 요청되고 설정됩니다. 타입 프로퍼티는 해당 타입의 인스턴스가 아닌 해당 타입에 대해 요청되고 설정됩니다.

 

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"

 

아래의 예제는 두 개의 저장 타입 프로퍼티를 사용합니다. 오디오 채널 2개에 대한 레벨을 모델링했습니다. 각 채널은 10개의 레벨을 가집니다.

https://docs.swift.org/swift-book/LanguageGuide/Properties.html

 

위 그림은 두 오디오 채널을 나타냅니다. 왼쪽 채널의 전류 레벨은 9 이고, 오른쪽 채널의 전류 레벨은 7입니다.

 

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 구조체는 두 개의 저장 타입 프로퍼티를 정의합니다. 첫 번째 threshholdLevel은 오디오 레벨이 가질 수 있는 최대 값을 정의합니다. 오디오 신호가 10보다 높은 값으로 들어오는 경우 thresholLevel로 제한됩니다.

 

두 번째 타입 포로퍼티는  maxInputLevelForAllChannels 라는 변수 저장 프로퍼티입니다. 이를 통해 오디오 채널 인스턴스에 입력된 최대 입력값을 추적합니다. 초기값은 0 으로 설정되어 있습니다.

 

AudioChannel 구조체는 currnetLevel이라는 저장 인스턴스 프로퍼티도 정의합니다. currentLevel은 채널의 현재 오디오 레벨을 1~10 사이로 나타냅니다.

 

currentLevel 프로퍼티는 didSet은 프로퍼티 옵저버를 통해 currentLevel이 설정되면 그 값을 확인합니다. 옵저버는 두 가지를 확인합니다.

  • currentLevel의 새로운 값이 thresholdLevel의 값보다 높다면, 프로퍼티 옵져버는 currentLevel를 thresholdLevel로 조정한다.
  • currentLevel의 새로운 값이 AudioChannel 인스턴스를 통해 받은 값보다 높다면, 프로퍼티 옵저버는 새로운 currentLevel 값을 maxInputLevelForAllChannels  타입 프로퍼티에 저장한다.

 

AudioChannnel 구조체를 활요해 새로운 2개의 오디오 채널을 만듭니다.

var leftChannel = AudioChannel()
var rightChannel = AudioChannel()

 

leftChannel의 currentLevel 값을 7로 설정하면 maxInputLevelForAllChannels 타입 프로퍼티가 7로 업데이트 되는 것을 볼 수 잇습니다.

leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"

 

만약 rightChannel의 currentLevel 값을 11로 설정하면, rightChannel의 currentLevel 프로퍼티가 10으로 조정되고, maxInputLevelForAllChannels 타입 프로퍼티가 10으로 업데이트됩니다.

rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// Prints "10"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "10"

 

 

 

 

 

끝!

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함