References
Contents
- Stored Properties
- Computed Properties
- Property Observers
- Property Wrappers
- Global and Local Variables
- Type Properties
[Swift] Structures and Classes (구조체와 클래스)
구조체 및 클래스에 이어서 속성에 대해 알아보겠습니다.
속성(프로퍼티, properies)는 특정 클래스, 구조체 또는 열거형과 연관된 값입니다. 속성의 종류에는 여러가지가 있는데, 저장 속성(Stored properties)는 인스턴스의 일부로 상수나 변수 값을 저장하는 것이고, 연산 속성(Computed properties)는 값을 저장하는 것이 아닌 계산합니다. 연산 속성은 클래스, 구조체, 열거형에서 사용할 수 있지만, 저장 속성은 오직 클래스나 구조체에서만 사용할 수 있습니다.
저장 및 연산 속성은 일반적으로 특정 타입의 인스턴스와 연관됩니다. 하지만, 타입 그 자체와 연관될 수도 있는데, 이러한 속성을 타입 속성(type properties)라고 합니다.
추가로, 속성의 값들의 변화를 모니터링하는 옵저버라는 속성을 정의할 수 있는데, 사용자 정의 동작으로 반응할 수도 있습니다. 옵저버 속성은 정의한 저장 속성에 추가할 수 있고, super class에서 상속받는 속성에 추가할 수도 있습니다.
또한, 래퍼(wrapper) 속성을 사용하여 여러 속성의 getter와 setter에서 코드를 재사용할 수도 있습니다.
Stored Porperties
저장 속성은 위에서 설명한대로 단순히 값을 저장할 수 있는 속성입니다. 이 속성은 let 또는 var 키워드를 사용해 상수 또는 변수를 선언하여 사용할 수 있습니다.
정의의 일부로 저장 속성의 초기값을 설정할 수 있는데, 이는 나중에 초기화 파트에서 다시 살펴보겠습니다. 초기화 중에 저장 속성의 초기값을 설정하고 수정할 수도 있습니다. 이는 상수 저장 속성도 마찬가지입니다.
아래 예제코드는 FixedLengthRange라는 구조체를 정의하고 있습니다. 이 구조체는 생성된 이후 정수 범위를 수정할 수 없도록 length가 상수로 선언되어 있습니다.
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
FixedLengthRange의 인스턴스는 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가 비록 변수 속성이지만, firstValue의 값을 수정할 수 없습니다. 이는 구조체가 value types이기 때문인데, value type의 인스턴스가 상수이면, 이 인스턴스의 모든 속성도 상수로 취급됩니다.
Lazy Stored Properties
lazy stored properties는 이 속성이 사용되기 전까지는 초기값이 계산되지 않는 속성을 의미합니다. lazy modifier를 선언하기 전에 작성하여 lazy 저장 속성을 가리킬 수 있습니다.
인스턴스 초기화 완료 후에도 초기값을 가지지 않을 수 있으므로, lazy 속성은 항상 변수(var 키워드)로 선언해야 합니다. 상수 속성은 초기화가 완료되기 전에 초기값을 가져야 하므로, lazy로 선언될 수 없습니다.
lazy 속성은 초기값이 인스턴스의 초기화가 완료될 때까지 값을 알 수 없는 외부 요인에 의존하는 경우 유용합니다. lazy 속성은 속성의 초기값이 계산하는데 복잡하고 비용이 많이 소모될 때, 이 속성이 필요할 때까지 수행하지 않는 경우에도 유용합니다.
아래 예제 코드는 복잡한 클래스의 불필요한 초기화를 방지하기 위해 lazy 속성 사용 예시를 보여줍니다. DataImporter와 DataManager라는 두 개의 클래스를 정의하고 있는데, 내부 정의는 많이 생략되어 있습니다.
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 클래스는 String 값의 빈 배열로 초기화되는 data라는 저장 속성을 가지고 있습니다. DataManager 클래스의 기능 중 하나는 파일로부터 data를 import하는 것인데, 이 기능은 DataImporter 클래스에 의해서 제공되는데, 파일을 열고 메모리로 읽어야 하기 때문에 초기화하는데 상당한 시간이 걸린다고 가정하겠습니다.
DataManager 인스턴스는 파일로부터 데이터를 가지고 오지 않고도 데이터를 관리할 수 있기 때문에 DataManager가 생성될 때, DataImporter 인스턴스를 생성하지 않고, DataImporter 인스턴스를 처음 사용할 때 생성하는 것이 더 효율적입니다. 이때, lazy modifier를 사용해서 DataImporter 인스턴스는 아래 코드처럼 처음 액세스될 때 생성되도록 할 수 있습니다.
print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"
lazy modifier로 표시된 속성이 여러 스레드에 의해서 동시에 액세스되는데 아직 초기화되지 않은 경우, 속성이 한 번만 초기화된다는 보장은 없습니다.
Stored Properties and Instance Variables
Objective-C에서는 값을 저장하기 위해 dot 문법(instance.property = value)나 set 연산(instance.setProperty(value))로 값을 저장한다는 것을 알고 있습니다. 뿐만 아니라 메모리 관리와 관련된 개념도 속성에 함께 명시합니다 (@property (nonatomic, retain) NSString *propertyName;). Swift에서는 이런 컨셉을 하나의 속성에 녹여 속성의 선언과 사용의 혼란을 피합니다. 속성의 이름, 타입, 메모리 관리 등의 모든 정보를 속성을 선언하는 한곳에 정의하게 됩니다.
Computed Properties
클래스, 구조체, 열거형에는 연산 속성(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)"
위의 예제 코드는 Point, Size, Rect라는 3개의 구조체를 정의하고 있습니다.
Rect 구조체에는 center라는 연산 속성을 제공하는데, Rect의 현재 center position은 항상 origin과 size에 의해서 결정되고, 명시적인 Point 값으로 center point의 값을 저장할 필요가 없습니다. 대신 Rect는 center라고 불리는 연산 속성에 custom getter와 setter를 정의해서 저장 속성인 것처럼 직사각형의 중심(center)를 작업할 수 있도록 해줍니다.
위 예제 코드에서 square라는 Rect 변수를 생성하고, square 변수는 원점 (0,0)과 width, height가 10으로 초기화됩니다. 따라서 푸른색 사각형을 나타냅니다. square 변수의 center 속성은 dot 문법 (square.center)를 통해서 액세스할 수 있고, 이는 center의 getter가 호출되어 현재 속성의 값을 검색할 수 있습니다. 단순히 값을 리턴하는 것이 아닌 getter는 square의 center를 나타내는 새로운 Point를 계산하고 반환합니다. 예제 코드에서는 (5, 5)의 center point를 반환합니다.
line 25에서는 square의 center 속성이 (15, 15)의 새로운 값으로 설정됩니다. 이는 사각형이 오른쪽 위로 이동하여 주황색 사각형의 위치가 된다는 것을 의미합니다. center 속성을 설정하는 것은 center의 setter를 호출하고, 이 setter는 origin 저장 속성의 x, y값을 수정합니다.
Shorthand Setter Declaration
만약 연산 속성의 setter에서 설정되는 새로운 값을 위한 이름(위에서 newCenter)이 정의되지 않았다면, newValue라는 기본 이름을 사용합니다. 아래의 Rect 구조체의 다른 버전은 이러한 축약된 표기의 이점을 보여줍니다.
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
만약 getter가 하나의 구문으로 구성된다면, getter는 암시적으로 이 구문을 반환합니다. 아래의 Rect 구조체의 다른 버전은 축약된 형태의 getter와 setter를 보여주고 있습니다.
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)
}
}
}
함수에서 return을 생략하는 것과 동일한 규칙을 따릅니다.
Read-Only Computed Properties
getter는 있지만 setter가 없는 연산 속성은 read-only 연산 속성입니다. 읽기 전용인 연산 속성은 항상 값을 리턴하고 dot 문법으로 액세스할 수 있지만, 다른 값을 설정할 수는 없습니다.
연산 속성은 그 값이 고정되어 있지 않기 때문에 var 키워드를 사용하여 변수로 정의해야 합니다. let 키워드를 사용하면 초기화 이후에 값을 변경할 수 없습니다.
읽기 전용 연산 속성은 get 키워드를 제거하고, 중괄호를 사용하여 간단하게 선언할 수 있습니다.
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라는 새로운 구조체를 정의하였고, 이 구조체는 3차원 사각형 박스를 나타내는 width, height, depth 속성을 가지고 있습니다. 이 구조체는 읽기 전용인 연산 속성 volume이 있는데, 이 속성은 cuboid의 현재 부피를 리턴합니다.
Property Observers
옵저버 속성은 속성의 값의 변화에 대해 관찰하고 반응(대응) 합니다. 옵저버 속성은 속성의 값이 설정될 때마다 호출되는데, 속성의 현재 값가 동일한 값으로 설정되어도 호출됩니다.
옵저버 속성은 다음의 장소에 추가할 수 있습니다.
- Stored properties that you define
- Stored properties that you inherit
- Computed properties that you inherit
상속된 속성은 subclass에서 속성을 override하여 옵저버 속성을 추가할 수 있습니다. 정의한 연산 속성에서는 새로운 옵저버를 만드는 것 대신 속성의 setter를 사용하여 값의 변화를 관찰하고 대응할 수 있습니다.
속성에는 아래의 옵저버 옵션을 제공합니다.
- willSet : 값이 저장되기 전에 호출됨
- didSet : 새로운 값이 저장되고 난 직후에 호출됨
willSet 옵저버를 구현하면, 새로운 속성 값이 상수 파라미터로 전달됩니다. willSet 구현의 일부로 이 파라미터의 이름을 지정할 수 있습니다. 만약 파라미터 이름을 정의하지 않으면 기본 파라미터 이름인 newValue로 사용할 수 있습니다.
비슷하게 didSet 옵저버를 구현하는 경우에는 이전의 속성 값을 포함하는 상수 파라미터가 전달됩니다. 이 매개변수의 이름을 지정하거나 지정하지 않는다면 oldValue라는 기본 이름으로 사용할 수 있습니다. 만약 didSet 옵저버 내에서 속성의 값을 할당하는 경우, 할당한 새로운 값이 방금 설정된 값을 대체합니다.
아래는 willSet과 didSet의 동작을 보여줍니다. StepCounter라는 새로운 클래스를 정의했는데, 이 클래스는 step의 총 개수를 추적합니다.
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
Property Wrappers
래퍼 속성(Property Wrappers)은 속성이 저장되는 방법을 관리하는 코드와 속성을 정의하는 코드 사이에 분리된 레이어를 추가합니다. 예를 들어, thread-safe 체크를 제공하거나 데이터베이스에 속성의 data를 저장하는 속성이 있는 경우, 모든 속성에 이러한 코드를 작성해야합니다. 그러나 래퍼 속성을 사용하면, 래퍼를 정의할 때 한번만 관리 코드를 작성하면 되고, 여러 속성에 이 코드를 재사용할 수 있습니다.
래퍼 속성을 추가하기 위해서 먼저, warpperValue 속성을 정의하는 구조체/열거형/클래스를 만듭니다. 아래 코드는 TwleveOrLess 구조체이며, 이 구조체가 래핑하는 값이 12보다 같거나 작도록 보장해줍니다. 만약 12보다 크다면 12가 저장됩니다.
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
setter에 의해서 새로운 값은 12 이하라는 것을 보장하고, getter는 저장된 값을 반환합니다.
이렇게 정의한 래퍼를 attribute처럼 속성 앞에 래퍼 이름을 작성하여 적용할 수 있습니다. 다음은 TwelveOrLess 래퍼 속성을 사용하여 크기가 항상 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"
height와 width 속성은 TwelveOrLess의 정의로부터 초기값(TwelveOrLess.number=0)이 설정됩니다(line 6-7).
line 10에서 rectangle.height에 10이라는 값을 저장하려고 하는데, TwelveOrLess의 setter는 10이라는 값이 유효한 값인지 확인하고, 유효하다면 10이라는 값을 height에 저장합니다. 그러나 line 14에서 24라는 값은 TwelveOrLess가 허용하는 값보다 크기 때문에 height의 값은 24가 아닌 12가 저장됩니다.
(속성에 래퍼를 적용할 때, 컴파일러는 래퍼를 위한 저장공간을 제공하는 코드와 래퍼를 통해 속성에 액세스하는 코드를 합성합니다.)
특별한 attribute 문법을 사용하지 않고 래퍼 속성의 동작을 사용하는 코드를 작성할 수도 있습니다. 아래 예제 코드에서 SmallRectangle의 새로운 버전은 @TwelveOrLess attribute를 사용하는 대신, 명시적으로 TwelvelOrLess 구조체를 속성을 래핑합니다.
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 속성은 래퍼 속성(TwelveOrLess)의 인스턴스를 저장합니다. height와 width의 getter와 setter는 wrappedValue 속성에 대한 액세스를 래핑합니다.
Setting Initial Values for Wrapped Properties
위의 예제 코드에서는 TwelveOrLess 정의에서 number에 초기값을 설정하여 래핑된 속성의 초기값을 설정합니다. 래퍼 속성을 사용하는 코드는 TwelveOrLess로 래핑된 속성의 다른 초기값을 지정할 수 없습니다. 예를 들어, SmallRectangle의 정의에서 height와 width의 초기값을 지정할 수 없습니다. 초기값을 설정하거나 다른 커스터마이즈를 지원하려면 래퍼 속성에 이니셜라이저를 추가해야합니다. 다음 예제 코드는 래퍼의 값과 최대값을 설정하는 이니셜라이저가 정의된 확장된 TwelvelOrLess의 새로운 버전, SmallNumber 구조체입니다.
@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:)이라는 3개의 이니셜라이저가 포함되어 있습니다. 이 이니셜라이저들은 래핑된 값과 최대값을 설정하는데 사용됩니다.
래퍼를 속성에 적용할 때, 초기값을 따로 지정하지 않는다면 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()를 호출함으로써 생성됩니다. 이니셜라이저 내부의 코드는 초기 래핑된 값과 초기 최대 값을 0과 12의 기본값을 사용하여 설정합니다. 이는 위에서 살펴본 TwelveOrLess와 동일합니다.
그러나 SmallNumber는 속성을 정의할 때 다른 초기갑사도 설정할 수 있습니다. 아래 코드에서 속성에 초기값을 지정해주면, 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:) 이니셜라이저가 호출됩니다. SmallNumber의 인스턴스는 SmallNumber(wrappedValue: 1)을 호출함으로써 생성됩니다. 이 이니셜라이저는 래핑된 값을 지정된 값으로 설정하고, 최대값은 기본값인 12로 설정합니다.
커스텀 attribute 뒤에 괄호로 인자들을 작성해주면, 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"
height를 래핑하는 SmallNumber의 인스턴스는 SmallNumber(wrappedValue: 2, maximum: 5)를 호출하면서 생성되며, width를 래핑하는 SmallNumber의 인스턴스는 SmallNumber(wrappedValue: 3, maximum: 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"
height를 래핑하는 SmallNumber는 SmallNumber(wrappedValue: 1)을 호출하면서 생성되고, width를 래핑하는 인스턴스는 SmallNumber(wrappedValue: 2, maximum: 9)를 호출하면서 생성됩니다.
Projecting a Value From a Property Wrapper
래핑된 값 이외에 래퍼 속성은 projected value(투영 값)를 정의하여 새로운 기능을 추가할 수 있습니다. projected value의 이름은 이름 앞에 달러 표시($)가 있다는 것을 제외하고는 wrapped value와 같습니다. $로 시작하는 속성은 코드에서 정의할 수 없으므로 projected value는 사용자가 정의하는 속성에 간섭할 수 없습니다.
위에서 살펴본 SmallNumber 예제에서 number 속성의 값을 너무 크게 설정하면 래퍼 속성이 값을 저장하기 전에 조정합니다. 아래 코드는 projectedValue 속성을 추가하여 래퍼 속성에서 새로운 값을 저장하기 전에 그 값을 조정했는지 여부를 추적합니다.
@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와 같은 큰 값을 저장하려고 시도한 이후에는 proejcted value의 값은 true가 됩니다.
래퍼 속성은 모든 타입의 값을 proejcted value으로 반환할 수 있습니다. 위 예제 코드에서는 래퍼 속성에서 number가 조정되었는지 여부만을 표시하므로 Boolean 값을 사용하여 projected value를 반환합니다. 더 많은 정보를 노출해야하는 래퍼는 다른 데이터 타입의 인스턴스를 반환하거나 래퍼 인스턴스 자체를 반환하여 노출시킬 수도 있습니다.
getter 속성이나 인스턴스의 메소드와 같이 self.을 생략하여 projected value에 액세스할 수 있습니다. 아래 예제 코드는 height, width를 래핑하는 래퍼의 projected value의 값을 $height, $width로 참조하고 있습니다.
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
}
}
Global and Local Variables
위에서 설명한 연산 속성이나 옵저버 속성의 기능은 전역 변수(Global Variable)나 지역 변수(Local Variable)에서도 사용할 수 있습니다. 전역 변수는 어떠한 함수, 메소드, 클로저, 또는 타입 컨텍스트 외부에 정의되는 변수이며, 지역 변수는 함수, 메소드, 클로저 문맥 내부에 정의되는 변수입니다.
이전까지 마주했던 전역 변수나 지역 변수는 모든 값을 저장하는 변수(Stored variables)였습니다. 이러한 저장 변수는 저장 속성과 같이 특정 타입의 값을 위한 저장 공간을 제공하고 값을 설정하거나 탐색할 수 있도록 해줍니다.
그러나 전역 또는 지역 범위(scope)에서 연산 변수(computed variable)를 정의하거나 저장 변수를 관찰하기 위한 옵저버를 정의할 수도 있습니다. 연산 변수는 값을 저장하지 않고 값을 계산하는데, 연산 속성과 동일한 방법으로 작성됩니다.
전역 상수나 변수는 Lazy 저장 속성처럼 호출될 때 연산됩니다. 다만 명시적으로 lazy를 붙여줄 필요는 없습니다.
지역 상수나 변수는 절대 lazy로 연산되지 않습니다.
지역 저장 변수에 래퍼 속성을 적용할 수 있는데, 래퍼 속성은 전역 변수나 연산 변수에는 적용할 수 없습니다. 아래 코드는 위에서 살펴본 SmallNumber라는 래퍼 속성을 muNumber에 적용한 것을 보여줍니다.
func someFunction() {
@SmallNumber var myNumber: Int = 0
myNumber = 10
// now myNumber is 10
myNumber = 24
// now myNumber is 12
}
Type Properties
인스턴스 속성(Instance properties)는 특정 타입의 인스턴스에 속한 속성입니다. 이러한 타입의 새로운 인스턴스를 만들 때마다, 다른 인스턴스와는 별도로 고유한 속성 값의 집합을 갖습니다.
또한, 해당 타입의 인스턴스가 아닌 타입 자체에 속하는 속성을 정의할 수도 있습니다. 해당 타입의 인스턴스 수에 관계없이 이러한 속성은 하나의 복사본만을 가집니다. 이런 종류의 속성을 타입 속성(type properties)라고 합니다.
타입 속성은 모든 인스턴스가 사용할 수 있는 상수 속성(C언어에서 static 상수) 또는 해당 타입의 모든 인스턴스에 전역 값을 저장하는 변수 속성(C언어에서 static 변수)과 같은 특정 타입의 모든 인스턴스에서 사용될 수 있는 전역 값을 정의하는데 유용합니다.
저장된 타입 속성은 변수 또는 상수일 수 있는데, 연산 타입 속성(computed type properties)는 항상 변수 속성으로 정의되어야 하며, computed instance 속성도 동일합니다.
Type Property Syntax
C나 Objective-C에서 전역 static 변수와 같은 타입의 static 상수나 변수를 정의합니다. 그러나 Swift에서, 타입 속성은 타입 정의의 일부로 작성됩니다.
타입 속성은 static 키워드를 사용하여 정의할 수 있습니다. 클래스 타입에서 computed 타입 속성은 class 키워드를 사용할 수 있는데, 이를 사용하여 superclass의 구현을 오버라이드할 수 있도록 해줍니다.
아래 예제 코드는 저장 타입 속성과 연산 타입 속성의 문법을 보여줍니다.
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
타입 속성은 인스턴스 속성처럼 dot 문법을 통해 쿼리하고 설정할 수 있습니다.
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"
이어지는 예제는 두 개의 저장 타입 속성을 구조체의 일부로 사용하여 각 오디어 채널의 음량 레벨을 모델링하는 것을 보여줍니다. 각 채널은 정수형의 음량을 0에서 10까지 표현합니다.
아래 이미지는 오디오 채널 두 개를 결합하여 스테레오 음량 미터를 모델링하는 것을 보여줍니다. 채널의 음량 레벨이 0일 때 채널의 표시등은 켜지지 않고, 음량 레벨이 10이면 채널의 모든 표시등이 켜집니다. 이 그림에서 왼쪽 채널의 음량은 9이고, 오른쪽 채널의 음량은 7입니다.
위에서 설명한 오디오 채널을 구현한 AudioChannel 구조체입니다.
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 구조체는 두 개의 저장 타입 속성을 정의합니다. 먼저 threasholdLevel은 최대로 설정할 수 있는 음량 레벨을 정의합니다. 이는 AudioChannel 인스턴스에서 10이라는 상수 값이 됩니다. 만약 음량 레벨이 10보다 높은 값으로 설정되는 threshold 값에 의해서 체크됩니다.
두 번째 타입 속성은 maxInputLvelForAllChannels라는 variable stored property 입니다. 이는 모든 AudioChannel 인스턴스에서 입력받은 최대 입력값을 추적할 수 있습니다. 초기값은 0입니다.
AudioChannel 구조체는 또한 저장 인스턴스 속성인 currentLevel을 정의하는데, 이 변수는 채널의 현재 오디오 음량 레벨을 나타냅니다. currentLevel 속성은 설정되는 값을 체크하기 위한 didSet 옵저버 속성을 가지고 있습니다. 이 옵저버 속성은 두 가지 검사를 수행합니다.
- 만약 currentLevel의 새로운 값이 thresholdLevel보다 크다면 thresholdLevel의 값으로 설정합니다.
- currentLevel의 값이 설정된 후에 이 값이 이전의 모든 AudioChannel 인스턴스에서 전달받은 어떠한 값보다 크다면, currentLevel의 값을 maxInputLevelForAllChannels 타입 속성의 값에 저장합니다.
그럼 이제 leftChannel과 rightChannel을 생성해봅시다.
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
그리고 left 채널의 currentLevel은 7로 설정합니다. 이제 maxInputLevelForAllChannels 타입 속성은 7로 설정될 것입니다.
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"
만약 right 채널의 currentLevel을 11로 설정한다면, currentLevel 속성의 값이 최대 값인 10으로 설정된 것을 볼 수 있습니다. 그리고 maxInputLevelForAllChannels의 값 또한 10으로 변경됩니다.
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// Prints "10"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "10"
'프로그래밍 > Swift' 카테고리의 다른 글
[Swift] Subscripts (서브스크립트) (0) | 2021.12.30 |
---|---|
[Swift] Methods (0) | 2021.12.26 |
[Swift] Structures and Classes (구조체와 클래스) (0) | 2021.12.24 |
[Swift] Enumerations (열거형, 열거자) (0) | 2021.12.22 |
[Swift] Closures (클로저) (0) | 2021.12.22 |
댓글