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
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
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을 정의할 수 있습니다.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)"
계산된 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)
}
}
}
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)
}
}
}
읽기 전용 계산 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의 현재 값과 같더라도 Properties 값이 설정될 때마다 Properties Observers가 호출됩니다.
willSet은 값이 Stored되기 직전에 호출됩니다.
didSet은 새 값이 Stored된 직후에 호출됩니다.
newValue
와 함께 매개변수를 사용할 수 있습니다.oldValue
를 사용할 수 있습니다.슈퍼클래스 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
totalSteps에 대한 willSet 및 didSet Observers는 Properties에 새 값이 할당될 때마다 호출됩니다.
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에 대한 액세스를 래핑합니다.
위 예제의 코드는 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)를 호출하여 생성됩니다.
래핑된 값 외에도 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가 높이 또는 너비를 조정했는지 여부를 결정합니다.
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로 설정합니다.
Stored 인스턴스 Properties과 달리 Stored Type Properties에는 항상 기본값을 지정해야 합니다. 타입 자체에는 초기화 시 Stored된 타입 Properties에 값을 할당할 수 있는 이니셜라이저가 없기 때문입니다.
Stored된 Type Properties은 처음 액세스할 때 느리게 초기화됩니다. 여러 스레드가 동시에 액세스하는 경우에도 한 번만 초기화되는 것이 보장되며 lazy 수정자로 표시할 필요가 없습니다.
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을 정의할 수도 있습니다.
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"
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
}
}
}
}
이 두 가지 검사 중 첫 번째에서 didSet Observers는 currentLevel을 다른 값으로 설정합니다. 그러나 이로 인해 Observers가 다시 호출되지는 않습니다.
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"
rightChannel.currentLevel = 11 인쇄(rightChannel.currentLevel) // "10"을 출력 인쇄(AudioChannel.maxInputLevelForAllChannels) // "10"을 출력