-
Swift Language ) PropertiesSwift/Swift Language 2022. 1. 17. 14:12
Properties
Properties 는 어떤 class, structure 또는 enumeration 과 values 를 연관시킨다. Stored properties 는 constant or variable values 를 instance 의 일부로 저장하고, computed properties 는 value 를 계산한다. Computed properties 는 classes, structures, enumerations 에서 사용 가능하고, Stored properties 는 classes, structures 에서만 사용 가능하다.
Stored, computed properties 는 보통 특정 타입의 instance 에 관련되지만, type 자체와도 관련될 수 있다. 이러한 properties 를 type properties 라 부른다.
추가로, property 의 값 변화를 모니터링 하기 위해 property observers 를 정의할 수 있고, custom action 으로 반응하게 할 수 있다. Property observers 는 자신이 정의한 stored properties 뿐만 아니라, superclass 로부터 inherit 한 subclass 의 properties 에도 추가될 수 있다.
또한 여러 properties 의 getter 와 setter 에 사용된 코드를 재사용하기 위해 property wrapper 을 이용할 수 있다.
Stored Properties
가장 단순한 형태로, a stored property 는 특정 class or structure 의 instance 의 일부로서 저장된 constant or variable 이다. Stored properties 는 variable stored properties 이거나 constant stored properties 이다.
Definition 의 일부로, stored property 에 default value 를 줄 수 있다. 또한, initialization 과정에서 stored property 의 initial value 를 수정할 수 있고, constant stored properties 의 경우에도 그렇다.
아래 예시에서는 FixedLengthRange structure 를 정의한다. 이 structure 에서 integers 의 범위는 생성 후에는 수정될 수 없다.
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
Instances of FixedLengthRange have a variable stored property called firstValue and a constant stored property called length. In the example above, length is initialized when the new range is created and can’t be changed thereafter, because it’s a constant property.
FixedLengthRange 의 instnaces 는 firstValue 라는 stored property 와 length 라는 constant store property 를 가진다. 위 예시에서 length 는 새로운 range 가 생성될 때 initialized 되고, 그 후에는 constant property 이기 때문에 변할 수 없다.
Stored Properties of Constant Structure Instances
만약 어떤 structure 의 instance 를 생성한 후 어떤 constant 에 해당 instance 를 assign 하게되면, properties 가 variable 로 선언되었다고 하더라도 instance 의 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 가 constant 로 선언되었기 때문에, firstValue property 가 variable 이라 하더라도 값을 바꾸는 것은 불가하다.이러한 behavior 는 structures 가 value type 이라서 그렇다. Value type 의 instance 가 constant 일때는 그 instance 의 모든 properties 도 constant 가 된다.
Reference types 인 classes 의 경우는 이와 다르다. 만약 reference type 의 instance 를 constant 에 할당하였을 때에는 여전히 instance 내 variable properties 의 값을 바꿀 수 있다.Lazy Stored Properties
Lazy stored 의 initial value 는 처음으로 사용되는 시점까지 calculated 되지 않는다. 'lazy' modifier 를 declaration 앞부분에 써줌으로 lazy stored property 임을 나타낸다.
NOTE
lazy property 는 instance 의 initialization 이 끝난 후에도 retrieved 되지 않을 수 있기 때문에 반드시 variable 로 선언해야한다. Constant properties 는 항상 반드시 initialization 이 끝나기 전에 값을 가져야하고, 따라서 lazy 로 선언될 수 없다.Lazy properties are useful when the initial value for a property is dependent on outside factors whose values aren’t known until after an instance’s initialization is complete.(Lazy properties 는 property 의 initial value 가 주위 상황에 따라 달라지는 경우 유용하다. ) 또한, lazy properties 는 property 의 initial value 가 복잡하거나 computationally expensive setup 이 필요해서 정말 필요한 경우가 아니면 시행되지 않아야 더 좋은 경우 사용하면 유용하다.
아래 예시에서는 DataImporter, DataManager 두 classes 를 정의하고, 복잡한 class 의 initialization 를 불필요한경우 실행하지 않기 위해 lazy stored property 를 사용한다.
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")
DataManager class 는 stored property 인 data 를 가지고있고, data 는 빈 String array 로 초기화된다. 기능들을 모두 보여주진 않았지만, DataManager class 의 목적은 [String] 인 data 에 대한 access 를 제공하고 관리하는 것이다. DataManager class 의 기능 중 일부는 file 로부터 data 를 import 하는 것이다. 해당 기능은 DataImporter class 로부터 제공되고, 이는 initialize 하는 데에 어느정도 시간이 걸릴 것으로 가정된다. DataImport instance 가 initialized 될 때 file 을 열고 contents 를 memory 로 읽어야 하기 때문일 것이다.
DataManater instance 가 file 로부터 data 를 importing 하지 않고 data 를 manage 하게 될수도 있기 때문에, DataManager 자체가 생겨날 때 DataImporter instance 를 만들지는 않는다. 대신 DataImporter instance 가 처음 사용될 때 생성하는게 더 맞다.
Lazy modifier 로 marked 되어있기에 DataImporter instance 는 importer property 로 처음 접근될 때 생성된다. (ex: filename property 가 사용될 때)
print(manager.importer.filename) // the DataImporter instance for the importer property has now been created. // prints "data.txt"
NOTE
만약 lazy modifier 가 쓰인 property 가 여러 threads 에서 동시에 접근하고, property 가 initialized 되지 않은 경우에는 property 가 한번만 initialized 되지 않을 수 있다.Stored Properties and Instance Variables
If you have experience with Objective-C, you may know that it provides two ways to store values and references as part of a class instance. In addition to properties, you can use instance variables as a backing store for the values stored in a property.
Swift unifies these concepts into a single property declaration. A Swift property doesn’t have a corresponding instance variable, and the backing store for a property isn’t accessed directly. This approach avoids confusion about how the value is accessed in different contexts and simplifies the property’s declaration into a single, definitive statement. All information about the property—including its name, type, and memory management characteristics—is defined in a single location as part of the type’s definition.
(Objective-C 에 대한 경험이 있다면 값과 references (as part of a class instance) 를 저장하는 데에 두가지 방법이 있다는 것을 알 것이다. )
Computed Properties
Stored properties 에 더하여, classes, structures, 그리고 enumerations 는 computed properties 를 정의할 수 있다. Computed properties 는 실제로 값을 저장하지 않는다. 대신 간접적으로 값을 가져오고, 다른 properties 와 values 를 set 하기 위해 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)")
위 예시에서는 geometric shapes 를 다루기 위해 3 가지 structures 를 정의한다.
Point - 점의 x- y- 좌표
Size - 너비와 높이
Rect - origin point 와 size 로 rectangle 을 정의.
Rect structure 은 또한 computed property center 를 제공한다. 현재 Rect 의 center position 은 항상 origin 과 size 에 의해 결정될 수 있기때문에, center point 를 explicit Point value 로서 저장할 필요가 없다. 대신, Rect 는 computed variable center 에 대해 custom getter 와 setter 을 정의한다. 이로써 center 가 실제 stored property 인 것처럼 활용할 수 있다.
위 예시에서 Rect variable 로서 square 를 만든다. square variable 은 (0,0) 의 origin point 와 10 의 너비와 높이를 갖는 Size 로 initialized 된다. 이 square 는 아래 diagram 에서 파란 square 로 표현된다. square 의 center property 는 dot syntax(square.center) 으로 접근할 수 있고, center 의 getter 이 호출되며, 이어서 현재 property 의 value 를 가져온다. Getter는 이미 있는 값을 return 하기보다, 는 실제로 새로운 Point (square 의 center)을 계산 후 return 하게된다. 아래에서 보는 것과 같이 getter 는 (5,5) 인 center point 를 return 한다.
후에 center property 는 새로운 값인 (15,15) 로 설정되고, diagram 에서 orange square 로 나타낼 수 있다. center property 를 수정하면 center 의 setter 가 호출되고, setter 는 stored origin property 의 x, y 값을 수정하고, square 를 새로운 position 으로 이동시킨다.
Shorthand Setter Declaration
Computed property 의 setter 에서 set 될 값에 대해 새로운 값 (new Value ) 에 대한 이름을 지어주지 않으면, default name 'newValue' 가 사용된다. 아래는 Rect structure 에서 shorthand notation 을 이용한 다른 버전이다.
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
If the entire body of a getter is a single expression, the getter implicitly returns that expression. Here’s an another version of the Rect structure that takes advantage of this shorthand notation and the shorthand notation for setters:
Getter 의 body 전체가 single expression 일 때, getter 는 implicitly 그 expression 을 return 한다. 아래는 getter 와 setter 에 대해 모두 shorthand notaiton 을 이용한 다른 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 에서 return 을 빼는 것은 function 에서 return 을 빼는 것과 같은 rule 을 따른다.
Read-Only Computed Properties
Getter 만 있고 setter 가 없는 computed property 를 read-only computed property 라 부른다. Read-only computed property 는 항상 값을 return 하고 dot syntax 를 통해 접근 가능하지만, 다른 값으로 set 될 수는 없다.
NOTE
Computed properties 의 값은 고정되어있지 않기때문에 반드시 variable properties (with 'var') 로 선언되어야 한다. let keyword 는 constant properties 에만 사용되고, 이는 값이 instance 의 initialization 의 일부로 설정된 후에는 변하지 못한다는 것을 의미한다.get keyword 와 괄호들을 제거하면 read-only computed property 을 단순한 형태로 선언할 수 있다.
struct Cuboid { var width = 0.0, height = 0.0, depth = 0.0 var volume: Double { return width * height * depth } } let fourByFiveByTwo = Cuboid(width: 4, height: 5, depth: 2) print("volume of Cuboid : \(fourByFiveByTwo.volume)") // volume of Cuboid : 40.0
위 예시에서는 width, height, depth properties 를 가진 3D 직육면체 Cuboid structure 을 정의한다. 이 structure 는 read-only computed property volume 을 가지고, 이는 계산 후 현재 cuboid 의 volume 을 return 한다. 이때, volume 이 settable 이 되도록 하는 것은 적절하지 못하지만, external users 가 현재 calculated volume 을 알 수 있도록 read-only computed property 를 제공해줄 수는 있다.
Property Observers
Property Observers 는 property's value 의 변화를 관찰하고 이에 따라 respond한다. Property observers 는 property's value 가 설정될 때마다 호출된다 (같은 값으로 set 되어도 호출) .
아래 경우에 대해 property observers 를 추가해 줄 수 있다.
- Stored properties that you define
- Stored properties that you inherit
- Computed properties that you inherit
Inherited property 의 property observer 는 subclass 에서 해당 property 를 overriding 하면서 추가해 줄 수 있다. 본인이 정의한Computed property 에 대해서는, 값의 변화를 observe and respond 하기 위해서는 observer 를 만드는 대신 setter 를 사용하면 된다.
Property 에 하나 혹은 모든 아래 observers 를 정의할 수 있다.
- willSet 은 값이 저장되기 직전에 호출된다.
- didSet 은 새로운 값이 저장된 직후 호출된다.
willSet observer 를 implement 하는 경우 새로운 property value 가 constant parameter 로 주어진다. willSet implementation 을 하는 도중에 해당 parameter 의 이름을 지정할 수 있다. 만약 parameter name 과 괄호를 implementation 에서 적어주지 않으면, 'newValue' 라는 default parameter name 을 사용하게 된다.
이와 유사하게 didSet observer 를 implement 할 때에도 이전 property value 가 constant parameter 로 주어지게 된다. parameter 의 이름을 지어주지 않으면 'oldValue' 라는 이름을 사용하게 된다. property 자신의 didSet observer 내에서 어떤 값 그 property 에 할당하게 하면, 해당 값이 바로 전 'set' 된 값을 대체하게 된다.
NOTE
Superclass properties 의 willSet 과 didSet observers 는 superclass initializer 가 호출되고 난 후, subclass initializer 내에서 set 될 때 호출된다. 이들은 superclass initializer 가 끝마치기 전, class 가 자신의 properties 를 setting 할 때 호출되지 않는다.For more information about initializer delegation, see Initializer Delegation for Value Types and Initializer Delegation for Class Types.
아래는 willSet 과 didSet 이 활용되는 예시이다. 예시에서 걷는 중 총 걸음수를 track 하는 StepCounter class 를 정의한다. 해당 class 는 만보기 또는 다른 step counter 로부터 받는 데이터를 사용하여 사람의 걸음 수를 track 할 수 있을 것이다.
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 stepCounter.totalSteps = 360 stepCounter.totalSteps = 896 // About to set totalSteps to 200 // Added 200 steps // About to set totalSteps to 360 // Added 160 steps // About to set totalSteps to 896 // Added 536 steps
StepCounter class 는 Int type 의 totalSteps property 를 선언한다. totalSteps 는 willSet 과 didSet observers 를 가진 stored property 이다. totalSteps 의 willSet 과 didSet observers 는 property 가 새로운 값으로 할당될 때마다 호출된다. 새로운 값이 전과 같은 값일 때에도 마찬가지다. 이 예제에서 willSet observer 는 newTotalSteps 라는 custom parameter name 을 새로운 값을 대신해 사용하게 된다. 이 예제는 단순히 설정될 값을 출력하는 데에 이용한다. didSet observer 는 totalSteps 가 업데이트 된 후 호출된다. 새로운 값과 이전 값을 비교하여 만약 값이 증가한 경우라면 얼마나 증가하였는지 나타낸다. didSet observer 에서는 oldValue 에 대한 custom parameter name 을 지정해주지 않았기 때문에, 'oldValue' 가 쓰이게 되었다.
NOTE
Observers 가 있는 property 를 function 에 in-out parameter 로 넘겨주게 되면 willSet 과 didSet observers 가 항상 호출된다. 이는 in-out parameters 의 copy-in copy-out memory model 로 인한 것이다 : function 이 끝날 때 값이 항상 property 에 다시 쓰여지게 된다.Property Wrappers
Property wrapper 는 property 가 저장되는 방식을 관리하는 코드와 property 를 정의하는 code 사이 layer of separation 을 제공해준다. 예를 들어 thread-safety checks 를 제공하거나 underlying data 를 database 에 저장하는 properties 가 있다고 할 때, 해당 코드를 모든 property 에 써주어야한다. 하지만 이때 property wrapper 를 사용하게되면, management code 를 wrapper 를 정의할 때 단 한번만 쓰고, 해당 management code 를 여러 properties 에 적용할 수 있다.
Property wrapper 를 제공하기 위해서는 wrappedValue property 를 정의하는 structure, enumeration, or class 를 만들어야 한다. 아래 코드에서 TwelveOrLess structure 는 wrapper 가 wrap 하는 value 가 항상 12 보다 작거나 같음을 보장한다. 더 큰 값을 넣으려고 한다면, 여기에는 12가 대신 저장된다.
@propertyWrapper struct TwelveOrLess { private var number = 0 var wrappedValue: Int { get { return number } set { number = min(newValue, 12) } } }
여기서 setter 는 새로운 값들이 12 보다 작거나 같다는 것을 보장하고 getter 는 저장된 값을 return 한다.
NOTE
예시에서 number 는 private 으로 선언되었고, 이는 TwelveOrLess 의 implementation 내에서만 쓰임을 보장한다. 다른곳에서 쓰인 코드는 wrappedValue 의 getter 와 setter 을 이용해서 접근하며, number 를 직접적으로 사용할 수 없다.Property 의 attributed 로서 wrapper 의 이름을 적으면 wrapper 를 해당 property 에 적용할 수 있다. 아래는 dimensions 이 12보다 항상 작거나 같게 하기 위해서 TwelveOrLess property wrapper 를 적용한 rectangle 의 structure 이다.
@propertyWrapper struct TwelveOrLess { private var number = 0 var wrappedValue: Int { get { return number } set { number = min(newValue, 12) } } } struct SmallRectangle { @TwelveOrLess var height: Int @TwelveOrLess var width: Int } var rectangle = SmallRectangle() print(rectangle.height) rectangle.height = 10 print(rectangle.height) rectangle.height = 24 print(rectangle.height)
height 와 width properties 는 initial values 를 TwelveOrLess 의 정의로부터 갖는다 (number = 0 ). TwelveOrLess 의 setter 은 10 을 유효한 값으로 여기기 때문에 10을 rectangle.height 에 저장하는 것은 쓰여진 대로 처리한다. 그러나 24는 TwelveOrlEss 가 허용한 값보다 크기때문에, 24 를 저장하려고 하면 결국 rectangle.height 의 값을 대신 12로 set 한다 (the largest allowed value) .
Wrapper 를 property 에 적용할 때 compiler 는 wrapper 에 대한 storage 를 제공하는 code 와, wrapper 를 통해 property 에 대한 접근을 제공하는 code 를 synthesize 한다. (Property wrapper 는 wrapped value 를 저장하는 일을 맡기 때문에, 이에 대한 synthesized code 는 없다.)
Special attribute syntax 의 이점을 이용하지 않은 채 property wrapper 의 behavior 를 사용하는 code 를 작성할 수도 있다.
For example, here’s a version of SmallRectangle from the previous code listing that wraps its properties in the TwelveOrLess structure explicitly, instead of writing @TwelveOrLess as an attribute:
(예를들어, 아래는 @TwelveOrLess attribute 를 쓰는 대신 structure 내에서 explicitly properties 를 wraps 하는 code 를 listing 한 것이다. )
struct SmallRectangle2 { private var _height = TwelveOrLess() private var _width = TwelveOrLess() var height: Int { get { return _height.wrappedValue } set { print("newValue: \(newValue)") _height.wrappedValue = newValue} } var width: Int { get { return _width.wrappedValue } set { _width.wrappedValue = newValue } } }
_height 와 _width properties 는 property wrapper 의 instance TwelveOrLess 를 저장한다. height 와 width 의 getter 와 setter 는 wrappedValue property 에 대한 접근을 wrap 한다.
Setting Initial Values for Wrapped Properties
위 예시에서는 wrapped property 에 대한 initial value 에 TwelveOrLess 의 initial value 값을 주었다. 해당 wrapper 를 쓰는 code 는 TwelveOrLess 로 property 를 wrap 할 때 다른 initial value 를 지정할 수 없다. 예를 들어, SmallRectangle 의 정의 부분에서 height 이나 width 에 initial values 를 줄 수 없다. Initial value 나 다른 customization 을 할 수 있게 하기 위해서는 property wrapper 에 initializer 를 추가해주어야한다. 아래 TwelveOrLess 의 확장된 버전 SmallNumber 에서는 wrapped value 와 maximum value 를 설정하는 initializers 를 정의하였다.
@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 의 definition 은 세가지 initializers 를 가지고있다.
Wrapper 를 property 에 적용하고 initial value 를 지정해주지 않을 때, Swift 는 wrapper 를 설정하기 위해 init() initializer 를 사용한다.
struct ZeroRectangle { @SmallNumber var height: Int @SmallNumber var width: Int } var zeroRectangle = ZeroRectangle() print(zeroRectangle.height, zeroRectangle.width)
height 와 width 를 wrap 하는 SmallNumber 의 instances 는 SmallNumber() 를 호출함으로 생성된다. Initialzer 내의 코드는 기본 값 0 와 12 를 이용해서 initial wrapped value 와 initial maximum value 를 설정한다. property wrapper 는 여전히 모든 initial values 를 제공한다. 이전과 다른것은 SmallNumber 가 property 를 선언하는 부분에서 initial values 또한 지정할 수 있다는 점이다.
property 에 대해 initial value 를 지정할 때, Swift 는 wrapper 의 set up 에 init(wrappedValue:) initializer 를 이용한다.
struct UnitRectangle { @SmallNumber var height: Int = 1 @SmallNumber var width: Int = 1 } var unitRectangle = UnitRectangle() print(unitRectangle.height, unitRectangle.width)
Wrapper 를 가진 property 에 =1 을 쓰게되면 이는 init(wrappedValue:) initializer 로 변환되어 호출된다. height 와 width 를 wrap 하는 SmallNumber 의 instances 는 SmallNumber(wrappedValue: 1) 을 호출함으로 생긴다. Initializer 는 여기서 주어진 wrapped value 를 사용하며, default maximum value 12 를 또한 사용한다.
Custom attributed 뒤 괄호 안에 arguments 를 쓰면 Swift 는 해당 값들을 받는 initializer 를 사용하여 wrapper 를 만든다. 예를들어 초기 값과 maximum value 을 주었을 때, Swift 는 init(wrappedValue:maximum:) initializer 를 사용한다.
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) // print 2 3 narrowRectangle.height = 100 narrowRectangle.width = 100 print(narrowRectangle.height, narrowRectangle.width) // print 5 4
width, height 를 wrap 하는 SmallNumber 의 instances 는 각각 SmallNumber(wrappedValue: 3, maximum: 4)., SmallNumber(wrappedValue: 2, maximum:5)를 호출하여 만들어진다. property wrapper 에 arguments 를 넣음으로써, wrapper 의 초기 state 를 set up 하거나 또는 다른 options 를 wrapper 가 만들어질 때 전달할 수 있다. 이 syntax 가 property wrapper 를 이용할 때 가장 일반적인(general) 방법이다.
Property wrapper arguments 를 포함할 때, 할당을 통해 초기 값을 지정해 줄 수도 있다. Swift 는 '할당' 을 wrappedValue argument 로 취급하고, 포함한 arguments 를 받는 initializer 를 사용한다.
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 를 wrap 하는 SmallNumber 의 instance 는, maximum value 로 12 을 사용하는 SmallNumber(wrappedValue: 1) 을, width 는 SmallNumber(wrappedValue: 2, maximum: 9) 을 호출하여 각각 생성되었다.
Projecting a Value From a Property Wrapper
Wrapped value 에 더하여 property wrapper 는 projected value 을 정의함으로써 다른 추가적인 기능을 노출시킬 수 있다. (projected value —for example, a property wrapper that manages access to a database can expose a flushDatabaseConnection() method on its projected value. ) projected value 의 이름은 앞이 dollar sign($) 으로 시작한다는 것 외에는 wrapped value 와 같다. Code 에서 properties 를 $ 로 시작하는 것은 정의할 수 없으므로 projected value 는 본인이 정의한 어떠한 properties 와도 이름이 겹치지 않는다.
위 SmallNumber 예시에서 number 에 너무 큰 값을 넣으면 property wrapper 가 저장하기 전에 값을 조정하였다. 아래 코드에서는 SmallNumber structure 에 projectedValue 를 넣어 property wrapper 가 새로운 값을 조정을 했는지 여부에 대해 keep track 하도록 하였다.
@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 은 wrapper 의 projected value 에 접근한다. 작은 숫자 (4) 를 저장한 후에 someStructure.$someNumber 는 false 이고, 그렇지 않은 경우 ( 12 를 초과하는 값을 저장하는 경우 ) 에는 true 가 된다.
Property wrapper 는 projected value 로 어떠한 type 의 값이든 return 할 수 있다. 예시에서 property wrapper 는 하나의 정보만을 expose 하였고 (숫자가 조정되었는지 ) 그 Boolean value 를 exposes wrapper 의 projected value 로 expose 하였다. 더 많은 정보를 expose 해야하는 wrapper 의 경우 다른 data type 의 instance 를 return 할 수 있고, 또는 wrapper 의 instance 를 projected value 로 하고싶을 때에 self 를 return 할 수도 있다.
Type 의 일부로서 projected value 에 접근할 때, property getter 나 다른 instance method 와 같이 property name 전에 self. 를 생략할 수 있다. 아래 코드에서는 height, width 의 projected value 를 각각 $height, $width 로 refer 하고있다.
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 } }
Property wrapper 의 syntax 는 그저 getter 와 setter 가 있는 property 의 syntactic sugar 이기 때문에, height 와 width 에 접근하는 것과 다른 어떤 property 에 접근하는 방법은 같다. 예를 들어, resize(to:) 에서는 height 와 width 에 property wrapper 를 이용해서 접근한다. 만약 resize(to: .large) 를 호출하게 되면, .large 에 대한 switch case 에서 height 와 width 를 100 으로 set 한다. Wrapper 는 해당 properties 의 값이 12보다 커지는 것을 막고, projected value 를 true 로, 값을 바꾸었음을 나타내기 위해 설정한다. resize(to:) 의 마지막에서, return statement 는 property wrapper 가 height 혹은 width 를 바꾸었는지 알기 위해 $height 와 $width 를 확인한다.
Editor Comment :
SwiftUI 를 공부하면서 @Published, @State, @Binding 등 여러 property wrappers 를 만나게 된다. SwiftUI 를 안쓸 때에도 유용하게 쓸 수 있겠지만, SwiftUI 를 공부할 때에는 절대 안쓸수가 없는 개념이다.'Swift > Swift Language' 카테고리의 다른 글
Swift KVO ( Key Value Observing, Reactive Programming) (0) 2022.04.14 Assertions & Preconditions (0) 2021.11.24 Swift Language ) Error Handling (0) 2021.11.23 Swift Language ) Advanced Operators (0) 2021.11.11 Structures And Classes (Swift) (0) 2021.11.01