Introduction
: property는 class, structure, enumeration의 속성을 나타내는 값이다. Stored property는 변수와 상수를 저장하는 용도로 쓰이고, computed property는 저장하는 용도 보다는 값을 계산하는 용도로 쓰인다. 그리고 Stored property는 class, structure에만 쓰일수 있지만, Computed property는 class, structure, enumeration에서 모두 쓰일 수 있다.
Stored Properties
: instance를 구성하는 값을 저장하는 용도로 사용하는 property이다. 선언과 동시에 값을 할당할 수 있으며 let으로 선언된 상수라 하더라도 initialization 과정에서 값을 변경할 수 있다.
class, structure만 사용이 가능하다.
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
Stored Properties of Constant Structure Instances
: structure를 let으로 선언하면 var property라 하더라도 property 값을 변경하지 못한다.
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// compile 에러 발생!!
이는 structure가 value type 이기 때문이다. value type이 constant로 선언되면 property들 또한 constant 성질을 가진다.
Lazy Stored Properties
: lazy stored property는 처음 사용되기 전까지 해당 property가 초기값을 가지지 않는 성질을 가지고 있다.
lazy stored property는 반드시 var로 선언되어야 한다. let으로 선언 한다는 의미는 initialization이 끝나기 전에 초기값을 가지고 있어야 한다는 뜻이다. 그러나 lazy stored property는 initialization이 끝난 이후에 값을 가지기 때문이다.
lazy property는 외부 요인에 의해서 initialization 과정이 끝난 이후에 해당 값을 알 수 있을경우 사용하면 유용하다. 또한, lazy property가 initialization 과정에서 하기에는 부담스러운 작업을 가지고 있을경우, 즉, 필요할 때만 해당 작업을 하도록 하기 위해서 사용하는 데에도 유용하다.
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")
// 아직 DataImporter instance는 만들어지지 않았다.
위에 예제에서 data.txt 파일에 있는 데이터를 DataManager의 data 배열에 넣지 않을 거라면 굳이 DataImporter를 생성할 필요가 없다. DataImporter의 initialization 작업이 굉장히 무거울 것이기 때문에 lazy property로 생성하여 instance 생성을 꼭 필요할때만 하도록 한것이다.
print(manager.importer.filename)
// Prints "data.txt"
// 이때 DataImporter instance가 생성된다.
* lazy property가 멀티 스레드에서 접근이 가능하고, 해당 property가 아직 초기화 되지 않았다면, property가 한번만 초기화 되리라고는 보장하지 못한다.
Computed Properties
: 실제로 값을 저장하지 않고 getter, optional setter를 이용해서 stored property를 계산해서 원하는 값을 get, set하는 용도로 사용한다.
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)"
Rect의 center는 computed property이다. 자신의 stored property인 origin과 size를 이용해서 원하는 값을 추출하거나 세팅하고 있다.
Shorthand Setter Declaration
: computed property의 setter로 전달되는 파라미터의 이름을 명시하지 않으면, Swift가 해당 파라미터 이름을 "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)
}
}
}
Read-Only Computed Properties
: computed property에 setter가 없으면 당연히 read-only property가 된다.
그리고 computed property는 애초에 값이 하나로 고정된 것이 아니기 때문에 var로 선언해야 한다.(read-only 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.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"
Property Observers
: property observer는 property의 값이 변경되면 자동으로 invoke 되어 호출된다. 심지어 기존값과 값은값이 set 되더라도 invoke 된다.
property observer는 자신이 정의한 모든 stored property에 적용이 가능하다.(lazy stored property는 적용 불가능 하다.) 또한 상속된 stored, computed property를 overriding 해서 observer를 적용할 수도 있다. 다만, 자신이 만든 computed property 에는 observer를 만들 필요가 없다. setter에서 observing을 하면 되기 때문이다.
willSet과 didSet 메서드를 통해 observing을 할 수 있다.
willSet은 값이 저장되기 직전에 invoke되고 didSet은 값이 저장된 직후에 invoke 된다. 둘다 구현해도 되고 하나만 구현해도 된다.
willSet의 default parameter 이름은 "newValue"이고 didSet의 default parameter 이름은 "oldValue"이다. 직접 parameter 이름을 지정할 수도 있다.
super class property의 willSet, didSet observer 메서드는 child class의 initialization 과정에서 값을 변경하면 invoke 되지만, 자신의 class initialization 과정에서 property가 초기화 될때는 invoke 되지 않는다.
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
*observer를 가지고 있는 property가 in-out parameter로 넘겨지면, observer method는 무조건 불린다. in-out parameter가 동작하는 copy-in copy-out 모델에 의해 메서드가 종료될 때 property에 값이 다시 쓰여지기 때문이다.
Global and Local Variables
: Global, local 변수도 computing, ,observing 변수로 정의하는 것이 가능하다.
* Global 변수 및 상수는 lazily stored property처럼 생성되는 시점이 delay 된다. 다만, 앞에 "lazy" 키워드를 붙이지 않아도 된다.
Type Properties
: Type Property는 클래스 변수라고 생각하면 된다. instance의 갯수와는 무관하게 클래스내에 하나의 set만 존재하는 변수이다. let으로 선언한 Type Property는 C의 static constant와 비슷하고 var로 선언한 Type Property는 C의 static variable과 비슷하다.
stored instance property와 다르게 stored type property는 default 값을 가지고 있어야 한다.
type property는 instance init 시점에 생성되는것이 아니기 때문에 default 값을 넣어줄만한 타이밍이 딱히 존재하지 않기 때문이다.
그리고 stored type property는 lazy initialized 되더라도 멀티 스레드에서 동시 접근해도 한번만 init 되는것을 보장한다.
Type Property Syntax
: C와 Objective-C 에서는 static 변수, 상수를 global static 변수, 상수로 선언하여 global scope를 가졌는데, Swift 에서는 Type Property로 선언하여 해당 scope를 클래스 내부로 한정시킨다.
선언 문법은 Type property 앞에 "static" 키워드를 붙이면 된다. 다만, computed type property의 경우 자식클래스에서 override가 가능하도록 "class" 키워드를 대신 붙여도 된다.
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
: type property에 접근할때는 앞에 Type이름 뒤에 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"