[Swift5] Properties 2

Junyoung Park·2022년 3월 15일
0

Swift5 Docs

목록 보기
22/37
post-thumbnail
  • 다음은 Swift 5.6 Doc의 Properties 공부 내용을 정리했음을 밝힙니다.

Properties

프로퍼티 옵저버

프로퍼티 옵저버는 말 그대로 프로퍼티의 값이 수정된지 확인할 수 있다. 프로퍼티 값이 설정될 때마다 프로퍼티 옵저버가 호출되는데, 그 값이 동일한 값이더라도 호출된다.

프로퍼티 옵저버는 직접 정의하거나 상속한 저장 프로퍼티, 상속한 연산 프로퍼티 등에 사용할 수 있다.

상속한 프로퍼티에 프로퍼티 옵저버를 사용할 때에는 서브클래스로 프로퍼티를 오버라이딩해야 한다. 연산 프로퍼티를 쓸 때에는 프로퍼티 세터를 활용한다.

값이 저장되기 전에 호출되는 프로퍼티 옵저버 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

willSetdidSet을 사용한 저장 프로퍼티 totalSteps는 정수 타입으로 새로운 값을 할당받을 때마다 호출된다. 프로퍼티 옵저버 newTotalSteps는 할당받은 값을, didSet 프로퍼티 옵저버는 기존의 값을 저장하고 있다가 새로 할당한 값과의 차를 출력하는 데 사용한다. 옵저버를 가진 프로퍼티를 인-아웃 프로퍼티로 함수에 전달할 때에는 willSetdidSet 옵저버가 항상 호출된다.

프로퍼티 래퍼

프로퍼티 래퍼는 프로퍼티 저장 관리 및 프로퍼티 정의를 담당하는 코드를 분리하는 역할을 한다. 스레드 세이프티를 체크하거나 데이터베이스의 데이터를 저장하려면 프로퍼티 래퍼를 통해 안전성을 확보할 수 있다. 래퍼를 정의할 때 관리 코드를 한 번 쓰고, 다른 프로퍼티에 여러 번 적용하자.

wrappedValue를 정의하는 구조체, 열거형, 클래스를 정의해 래퍼를 사용하자.

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

세터를 통해 할당 가능한 수의 범위를 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"

직접 프로퍼티 래퍼를 일반 구조체처럼 줄 수도 있다. 이때 private를 쓴 까닭은 외부에서 세터가 아니라 . 연산자로 직접 값을 설정하는 것을 방지하기 위해서다.

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

래핑된 프로퍼티에 초깃값 설정

이니셜라이저를 통해 초깃값을 설정하자.

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

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

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

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

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

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"

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"

Java의 생성자 선언 시 파라미터로 주는 값에 따라 다른 초깃값을 줄 수 있는 것과 정확히 동일하다.

프로퍼티 래퍼에서 값을 프로젝팅하기

프로퍼티 래퍼를 통해 프로젝팅한 값을 정의할 수 있다. 예를 들어 데이터베이스 접근을 관리하는 프로퍼티 래퍼는 flushDatabaseConnection() 메소드를 프로젝팅한 값에 드러낼 수 있다. $를 앞에 붙여서 프로젝팅한 값이라는 것을 알리자.

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"

$를 통해 래퍼의 프로젝트된 값에 접근하자. 새로 할당한 수가 12보다 크다면 projectedValue가 참, 작다면 거짓을 리턴한다.

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 리턴문에서 프로퍼티 래퍼가 높이 및 너비를 수정했는지 알려준다.

profile
JUST DO IT

0개의 댓글