본문 바로가기

Swift/꼬꼬무

Properties

Property는 값을 특정 클래스, 구조체, 열거형과 연결합니다.

Stored Property는 상수 및 변수 값을 인스턴스의 일부로 저장하고 있는 프로퍼티

  • 클래스, 구조체에서만 사용 가능

Computed Property는 값을 저장하는 대신 계산하여 반환해주는 프로퍼티

  • 클래스, 구조체, 열거형 사용 가능

저장 프로퍼티와 연산 프로퍼티는 특정 타입의 인스턴스와 연결됩니다.

프로퍼티는 타입 자체와 연관될 수도 있습니다. 이런 타입을 Type Property라고 합니다.

Stored Properties

저장 프로퍼티는 클래스와 구조체에서만 사용 가능합니다.

가장 간단한 형태의 저장 프로퍼티는 클래스나 구조체에 사용되는 상수(let) 또는 변수(var) 입니다.

var를 사용하면 변수 저장 프로퍼티

let을 사용하면 상수 저장 프로퍼티

또한 저장 프로퍼티에 대한 기본값을 제공할 수도 있습니다.

또한 저장프로퍼티가 초기화 중에 초기 값을 수정할 수도 있습니다.

struct FixedLengthRagne {
        var firstValue: Int
        let length: Int
}

var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
rangeOfThreeItems.firstValue = 6

// 상수 저장 프로퍼티 (let length), 변수 저장 프로퍼티(var firstValue)가 선언
// 두 프로퍼티 모두 초기값은 없음
// FixedLengthRange(firstValue: 0, length: 3), 초기화 중에 값을 firstValue: 0, length: 3으로 수정
// length는 상수이기때문에 생성될때 초기화 되고 이후에 수정이 불가능

 

 

Stored Properties of Constant Structure Instances

구조체를 상수(let)로 선언하면 구조체 인스턴스의 프로퍼티를 변경할 수 없습니다.

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)

rangeOfFourItems.firstValue = 6
// 에러 발생
// rangeOfFourItems는 상수(let)으로 선언되어 프로퍼티 변경이 불가능합니다.
// 반면 클래스는 let 으로 선언해도 프로퍼티 변경이 가능합니다. 클래스 인스턴스는 참조 타입이기 때문에

 

Lazy Stored Properties

지연 저장 프로퍼티는 값이 처음 사용 되기 전에는 계산되지 않는 프로퍼티.

지연 저장 프로퍼티로 선언하기 위해서는 프로퍼티 앞에 lazy 키워드를 붙이면 됩니다.

지연 프로퍼티는 반드시 변수(var)로 선언해야 합니다. 이유는 상수는 초기화가 되기전에 항상 값을 가져야 하는데 지연 프로퍼티는 처음 사용되기 전에는 값을 갖지 않는 프로퍼티기 때문입니다.

지연 프로퍼티는 프로퍼티가 특정 요소에 의존적이어서 그 요소가 끝나기 전에 적절한 값을 알지 못하는 경우에 유용합니다. 또 복잡한 계산이나 부하가 많이 걸리는 작업을 지연 프로퍼티로 선언해 사용하면 실제 사용되기 전에는 실행되지 않아 인스턴스의 초기화 시점에 복잡한 계산을 피할 수 있습니다.

class DataImporter {
    // DataImporter는 외부 파일에서 데이터를 가져오는 클래스, 초기화 하는데 매우 많은 시간이 소요된다고 가정
    
    var filename = "data.txt"
}

class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // 데이터 관리하는 기능이 여기 구현되어 있다고 가정
}

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 인스턴스는 이 시점에 생성되어 있지 않습니다.

print(manager.importer.filename)
// DataImporter 인스턴스가 생성되었습니다.
// "data.txt" 파일을 출력합니다.

manager.importer.filename이 실행되어 실제 importer 프로퍼티에 처음 접근할 때 importer 인스턴스가 생성됩니다.

만약 지연 프로퍼티가 여러 스레드에서 사용되면 지연 프로퍼티가 한번만 실행되는 걸 보장하지 않습니다. 만약 지연 프로퍼티가 단일 스레드에서 사용되면 초기화는 한번만 하게 됩니다.

Stored Properties and Instance Variables

Objective-C와 다르게 Swift에서는 프로퍼티의 이름, 타입, 메모리 관리 등의 모든 정보를 프로퍼티를 선언하는 한곳에서 정의하게 됩니다.

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))")
// "suqre.origin is now at (10.0, 10.0)" 출력

위 코드는 좌표와 크기를 갖는 사각형을 표현하는 구조체에 관한 코드입니다.

여기서 Rect 구조체는 사각형의 중점을 표현하는 center 라는 계산 프로퍼티를 제공합니다.

(origin, size는 저장 프로퍼티)

이 center 프로퍼티는 get을 통해 다른 좌표와 크기 프로퍼티들로 부터 값을 연산해서 반환 받을 수 있습니다.

또 set을 통해 origin 프로퍼티의 좌표 값을 변경할 수 있습니다.

 

Shorthand Setter Declaration

앞에 코드에서 처럼 set(newCenter)와 같이 인자 이름을 설정하지 않으면 기본 인자 이름인 newValue 를 사용할 수 있습니다.

struct AlternativeRect {
    var origin = Origin()
    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 {
            // 인자 이름 설정하지 않으면 newValue라는 이름으로 사용이 가능
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

 

Shorthand Getter Declaration

getter는 전체 본문이 단일 표현식인 경우 암묵적으로 타입을 리턴합니다.

struct AlternativeRect {
    var origin = Origin()
    var size = Size()
    var center: Point {
        get {
            Point(x: origin.x + (size.width / 2),
                  y: origin.y + (size.height / 2))
        }
        set {
            // 인자 이름 설정하지 않으면 newValue라는 이름으로 사용이 가능
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

Read-Only Computed Properties

getter만 있고 setter를 제공하지 않는 계산 프로퍼티를 읽기 전용 프로퍼티라고 합니다.

즉, 반드시 반환 값을 제공하고 다른 값을 지정할 수 없는 프로퍼티

읽기전용 계산 프로퍼티를 포함해 계산 프로퍼티를 선언시에는 반드시 let이 아니라 var로 선언해야 합니다. 보통 읽기전용이라 함은 한번 값이 정해지면 변하지 않기 때문에 let으로 선언하는 것이 맞으나 계산 프로퍼티는 읽기전용이라 하더라고 계산 값이 따라 값이 변할 수 있기 때문에 var로 선언합니다.

struct Cuboid {
    // var로 선언
    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)")

 

Property Observers

프로퍼티 옵저버는 프로퍼티에 새 값이 설정(set) 될 때마다 이 이벤트를 감지할 수 있는 옵저버

이 옵저버는 이전 값과 새 값이 같더라고 항상 호출 됩니다.

이 프로퍼티 옵저버는 지연 저장 프로퍼티(lazy stored properties) 에서는 사용할 수 없습니다.

프로퍼티 옵저버를 추가할 수 있는 위치

  • 사용자가 정의한 저장 프로퍼티
  • 상속하는 저장 프로퍼티
  • 상속하는 계산 프로퍼티

프로퍼티 옵저버의 종류

  • willSet: 값이 저장되기 바로 직전에 호출 됨
  • didSet: 새 값이 저장되고 난 직후에 호출 됨

willSet 에서는 새 값의 파라미터명을 지정할 수 있는데, 지정하지 않으면 기본 값으로 newValue를 사용

didSet 에서는 바뀌기 전 값의 파라미터명을 지정할 수 있는데, 지정하지 않으면 기본 값으로 oldValue를 사용

서브클래스에서 특정 프로퍼티의 값을 설정했을 때, 수퍼클래스의 초기자가 호출 된 후 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 에서는 새로운 값의 파라미터 명을 newTotalSteps로 지정하여 사용했고

didSet 에서는 기본 파라미터 명인 oldValue를 이용하였습니다

print문을 보면 willSet이 호출된 후 didSet이 호출된 것을 확인할 수 있습니다.

만약 in-out 파라미터로 선언된 함수의 인자에 프로퍼티를 넘기면 willSet과 didSet이 항상 실행됩니다. 이유는 in-out 파라미터이기 때문에 프로퍼티가 항상 복사(copy)되기 때문입니다. 이 in-out 파라미터의 프로퍼티는 항상 원래 값에 새 값을 다시 덮어쓰게 됩니다.

 

Property Wrappers (Swift 5.1 ~)

Property Wrapper는 프로퍼티가 저장되는 방법을 관리하는 코드와 프로퍼티를 정의하는 코드 사이의 계층을 구분합니다.

예를들어 스레드 안전 검사를 제공하거나 기본 데이터를 데이터베이스에 저장하는 프로퍼티가 있는 경우 모든 프로퍼티에 해당 코드를 작성해야 합니다.

Property Wrapper를 활용하면 관리 코드 하나를 작성하여 여러개의 프로퍼티에 적용해서 재사용합니다.

Property Wrapper를 정의하려면 wrappedValue property를 정의하는 구조체, 열거형 또는 클래스를 만듭니다.

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

// setter는 새 값이 12보다 작거나 같도록 하고, getter는 저장된 값을 반환합니다.

위 예에서 number 변수는 private 로 선언하여 TwelveOrLess 구조체에서만 사용되도록 보장합니다. 다른 곳에서 작성된 코드는 getter와 rappedValue의 setter를 사용하여 값에 액세스하며 숫자를 직접 사용할 수 없습니다.

 

TweleveOrLess Property Wrapper를 사용하여 치수가 12 이하인지 확인하는 직사각형을 저장하는 구조체

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"

다음은 속성으로 @TwelveOrLess를 사용하는 대신 TwelveOrLess 구조체를 사용하는 방법

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 속성은 Property Wrapper인 TwelveOrLess의 인스턴스가 저장됩니다.

width, height 에 대한 getter 및 setter는 TwelveOrLess의 wrappedValue에 대한 액세스를 제공합니다.

Setting Initial Values for Wrapped Properties

위의 예에서 TwelveOrLess의 정의에서 number에 초기값을 제공하여 wrapped property에 초기 값을 설정합니다.

이 property wrapper는 다른 초기 값을 지정할 수 없습니다.

예를들어 SmallRectangle의 height 또는 width의 초기 값을 제공할 수 없습니다

초기 값 또는 사용자의 설정을 지원하려면 Property Wrapper에 이니셜라이저를 추가해야 합니다.

다음 코드는 최대값을 설정하는 이니셜라이저를 정의한 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:) 세 가지 이니셜라이저가 있습니다.

아래 예제에서 wrappedValue와 최대값을 설정하는데 사용합니다.

Property에 wrapper를 적용하고 초기 값을 지정하지 않으면 init() 이니셜라이저를 사용하여 래퍼를 설정합니다.

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

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

/*
 "0 0"이 나오는 이유로 SmallNumber의 wrappedValue의 setter는
 Int 프로퍼티의 값을 min(newValue, maximum)을 하게 되는데
 */

height와 width를 wrap하는 SmallNumber의 인스턴스는 SmallNumber()를 호출하여 만들어집니다.

여기까지 이해한 바로는 Property Wrapper를 이용하여 number, maximum, wrappedValue 프로퍼티가 있는데 ZeroRectangle에서 보게되면 height, width는 각각 @SmallNumber 로 선언되어 wrappedValue의 getter, setter를 통해서 private 프로퍼티인 number, maximum을 이용하여 값을 세팅하거나 반환받음.

프로퍼티의 초기 값을 지정하면 스위프트는 init(wrappedValue:)를 사용하여 wrapper의 초기화를 진행합니다.

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

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

wrapper를 사용하여 프로퍼티에 = 1을 사용하면 init(wrappedValue:) 이니셜라이저가 호출됩니다.

height와 width wrapper는 SmallNumber(wrappedValue: 1)를 호출하여 생성됩니다.

이니셜라이저는 이때 지정된 wrapped value를 사용하며 기본 maximum값인 12가 사용됩니다.

사용자 지정 속성 뒤 괄호 안에 arguments를 작성하면 Swift는 wrapper를 설정하기 위해 해당 arguments를 허용하는 이니셜라이저를 사용합니다.

초기값과 최대값을 전달하게 되면, 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(narrowRecangle.height, narrowRectangle.width)
// Prints "5 4"

height를 래핑하는 SmallNumber 인스턴스는 SmallNumber(wrappedValue: 2, maximum: 5)를 호출하여 생성되고, width를 래핑하는 인스턴스는 SmallNumber(wrappedValue: 3, maximum: 4)를 호출하여 생성됩니다.

Property wrapper에 arguments를 포함하면 래퍼에서 초기 상태를 설정하거나 생성 시 래퍼에 다른 옵션을 전달할 수 있습니다.

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

Property에 필요한 모든 arguments를 제공할 수 있으면 이니셜라이저에 전달됩니다.

Property wrapper arguments를 포함할때 초기값을 지정할 수도 있습니다.

 

Swift는 할당된 초기 값을 WrappedValue argument 처럼 처리하고 포함된 인수를 허용하는 이니셜라이저를 사용합니다.

struct MixedRectangle {
	@SmallNumber var height: Int = 1
	@SmallNumber(maximum: 9) var width: Int = 2
	// width의 초기값 2와, argument로 전달받은 maximum 9를 포함하여 init(wrappedValue:maximum:)을 호출함
}

var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// Prints "1"

mixedRectangle.height = 20
print(mixedRectangle.height)
// Prints "12"

height를 래핑하는 SmallNumber의 인스턴스는 SmallNumber(wrappedValue: 1)를 호출하여 생성되며 기본 최대값인 12를 사용합니다.

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

argumentd와 parameter의 차이 Parameter: 매개변수 (함수와 메서드의 입력 변수(Variable)명) Argument: 전달인자, 인자 (함수와 메서드의 입력 값(Value))

 

Projecting a Value From a Property Wrapper

wrapped value외에 추가로 property wrapper는 projected value를 정의하여 추가 기능을 표시할 수 있습니다.

예를 들어, 데이터베이스에 대한 액세스를 관리하는 property wrapper는 flushDatabaseConnection() 메서드를 projevted value에 표시할 수 있습니다.

projected value의 이름은 $로 시작하는 것을 제외하고 wrapped value와 동일합니다.

(그냥 쓰면 wrapped value, 앞에 $를 붙이면 projected value)

프로퍼티를 $기호로 시작하게 정의할 수 없기 때문에 projected value는 사용자가 정의한 프로퍼티와 간섭될 가능성이 없습니다.

위 SmallNumber 예제는 프로퍼티에 너무 큰 수를 할당하려고 하는 경우 property wrapper가 해당 숫자를 저장하기 전에 조정합니다. (max보다 크면 max 값으로 저장)

아래 코드는 SmallNumber에 projected value를 추가하여 새 값을 저장하기 전에 property wrapper가 해당 프로퍼티에 새 값을 저장했는지 여부를 추적합니다.

@propertyWrapper
struct SmallNumber {
    private var number: Int
    private(set) var projectedValue: Bool
    
    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projcetedValue = 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(someStructrue.$someNumber)
// Prints "false"

someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"

// 그냥 someNumber: wrappedValue 접근, $someNumber: projectedValue 접근

someStructure의 someNumber에 4와 55를 대입하여 wrapped value 값을 set 하게 되면

4는 12보다 크지 않아서 projectedValue가 false가 되고 55는 12보다 커서 projectedValue가 true가 됨

그리고 $기호를 붙여서 $someNumber에 접근하게 되면 projected value에 접근이 가능합니다.

property wrapper는 어떤 타입도 projected value로 return 할 수 있습니다.

예제 에서는 숫자의 조정 여부에 따라 Boolean 타입을 projected value로 사용하였지만.

더 많은 정보를 반환해야 하는 경우에는 다른 타입의 인스턴스로 반환하거나 self로 반환하여 projected value로 나타낼 수 있습니다.

property getter나 인스턴스 메소드 처럼 특정 타입 안에서 projected value에 액세스 할때는 프로퍼티 이름 앞에 .self를 생략할 수 있습니다.

다음 예제에서 $height 및 $width로 height 및 width 래퍼의 projected value를 나타냅니다.

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
    }
    // SmallNumber property wrapper의 wrapped value에 높이 너비 값을 넣으면 12보다 큰 경우는 projected value가 true로 변하고 값은 12로 고정된다.
    // return 문에서 $height || $width를 반환하면 SmallNumber의 projected value(값이 조정되었는지 Boolen) 값을 비교하여 height나 width의 값이 조정된 여부를 반환한다.
}

Property wrapper 문법은 프로퍼티의 getter 와 setter를 쉽게 해주는 문법일 뿐이므로 위 예에서 높이와 너비에 액세스하는 것은 다른 프로퍼티에 액세스 하는 것과 동일하게 동작합니다.

예를 들어 resize(to:) 코드는 property wrapper를 사용하여 height와 width에 액세스합니다.

resize(to: .large)를 호출하면 switch 문에서 large의 케이스가 높이와 너비를 100으로 설정합니다.

wrapper는 해당 속성 값이 12보다 큰 것을 방지하고 projected value를 true로 설정하여 값이 조정되었다고 저장합니다.

resize(to:)가 끝나면 return 문은 $height 및 $width를 확인하여 property wrapper가 height 또는 width를 조정했는지 여부를 확인합니다.

 

Global and Local Variables

앞서 소개한 프로퍼티, 프로퍼티 옵저버 기능은 전역변수와 지역변수 모두에서 이용 가능합니다.

전역 변수란 함수, 메소드, 클로저, 혹스 타입 컨텍스트 밖에 정의된 변수이고 지역 변수는 그 안에 선언된 변수

전역 상수와 변수는 지연 저장 프로퍼티와 같이 지연 계산 됩니다. 하지만 지연 저장 프로퍼티와 다르게 lazy 키워드를 붙일 필요가 없습니다. 반면 지역 상수와 변수는 지연 계산된 수 없습니다.

지역 저장 변수에도 property wrapper를 적용할 수 있습니다. 하지만 전역 변수나 계산 변수에는 사용할 수 없습니다.

아래 코드에서 myNumer는 SmallNumber property wrapper를 사용합니다.

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

	myNumber = 10
	// now myNumber is 10

	myNumber = 24
	// now myNumber is 12
}

 

Type Property

인스턴스 프로퍼티는 특정 타입의 인스턴스에 포함되는 프로퍼티 입니다.

해당 타입의 새로운 인스턴스를 만들 때마다 다른 인스턴스와는 별도로 고유한 프로퍼티 값을 설정합니다.

또한 해당 타입의 인스턴스가 아닌 타입 자체에 속하는 프로퍼티를 정의할 수도 있습니다. 생성하는 해당 타입의 인스턴스 수에 관계없이 이러한 프로퍼티의 복사본은 하나만 있습니다.이런 프로퍼티의 종류를 타입 프로퍼티 (type properties) 라고 합니다.

 

타입 프로퍼티는 C에서 static 상수 처럼 모든 인스턴스에서 사용할 수 있는 상수 프로퍼티나 C에서 static 변수 처럼 타입의 모든 인스턴스에 전역인 값을 저장하는 프로퍼티 변수와 같은 특정 타입에 모든 인스턴스에 보편적인 값을 정의하는데 유용합니다.

  • 저장 타입 프로퍼티는 변수(var)거나 상수(let)일 수 있습니다.
  • 계산 타입 프로퍼티는 항상 변수(var)로 선언됩니다. (계산 인스턴스 프로퍼티와 같음)
  • 저장 타입 프로퍼티는 타입 자체에 initializer가 없기 때문에 항상 기본값을 지정해야합니다.
    • 저장 인스턴스 프로퍼티와 다름, 저장 인스턴스 프로퍼티는 기본값이 없어도 됨
  • 저장 타입 프로퍼티는 처음 액세스할때 lazily initialized 되며, 여러 스레드에서 동시에 액세스 하는 경우에도 한번만 초기화되며 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 SomeNumeration {
    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

타입 프로퍼티는 인스턴스 프로퍼티와 마찬가지고 dot 구문을 사용하여 쿼리되고 설정됩니다.

그러나 타입 프로퍼티는 해당 타입의 인스턴스가 아닌 해당 타입에 대해 쿼리 및 설정됩니다.

print(SomeStructure.storedTypeProperty)
// Prints "Some value."

SomeStructure.storetedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// Prints "Another value."

print(SomeEnumeration.computedTypeProperty)
// Prints "6"

print(SomeClass.computedTypeProperty)
// Prints "27"

 

다음 예제에서 여러 오디오 채널에 대한 오디오 level meter를 모델링하는 구조의 일부로 저장된 두 가지 타입 프로퍼티를 사용합니다.

각 채널의 오디오 level meter는 0과 10 사이의 정수입니다.

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

위에서 설명한 오디오 채널은 오디오 채널 struct 인스턴스로 표시됩니다.

 

오디오 채널 struct는 해당 기능을 지원하기 위해 두 개의 저장 타입 프로퍼티를 정의합니다.

첫번째인 thresholdLevel은 오디오 레벨이 취할 수 있는 최대 값을 정의, 모든 AudioChannel 인스턴스에 대한 상수 값이 10 입니다.

오디오 신호가 10보다 높은 값으로 수신되면 최대 값으로 제한됩니다.

두번째 타입 프로퍼티는 maxInputLevelForAllChannels 라는 변수 타입 프로퍼티 입니다.

AudioChannel 인스턴스에서 수신한 최대 입력 값을 추적합니다. 초기 값은 0으로 시작

AudioChannel struct는 채널의 현재 오디오 레벨을 0에서 10까지로 나타내는 currentLevel이라는 저장 인스턴스 프로퍼티도 정의합니다.

currentLevel 프로퍼티는 설정될 때마다 currentLevel 값을 확인하는 didSet property observer가 있습니다.

 

이 observer는 두 가지 검사를 수행합니다.

  • currentLevel의 새 값이 허용된 thresholdLevel 보다 큰 경우 프로퍼티 옵저버는 currentLevel을 thresholdLevel로 제한합니다.
  • currentLevel의 새 값(제한 후)이 AudioChannel 인스턴스에서 이전에 수신한 값보다 높으면 프로퍼티 옵저버는
  • maxInputLevelForAllChannels 타입 프로퍼티에 새 currentLevel 값을 저장합니다.

 

 

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

 

AudioChannel struct를 사용하여 leftCannel 및 rightChannel 이라는 두 개의 새로운 오디오 채널을 만들어 스테레오 사운드 시스템의 오디오 레벨을 나타낼 수 있습니다.

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

 

왼쪽 채널의 currentLevel을 7로 설정하면 maxInputLevelForAllChannels 타입 프로퍼티가 7로 업데이트되는 것을 볼 수 있습니다.

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

 

오른쪽 채널의 currentLevel을 11로 설정하려고 하면 오른쪽 채널의 currentLevel 프로퍼티의 최대값인 10으로 제한되고 maxInputLevelForAllChannels 타입 프로퍼티가 10으로 업데이트 되는 것을 볼 수 있습니다.

'Swift > 꼬꼬무' 카테고리의 다른 글

Methods  (0) 2023.05.23
PopLast & RemoveLast & DropLast  (0) 2023.05.22
DispatchQueue with weak self  (0) 2023.05.20
Choosing Between Structures and Classes  (0) 2023.05.19
Structures and Classes  (0) 2023.05.18