아래의 글은 The swift programming language과 bbiguduk님, jusung님의 번역본을 보며 공부한 내용입니다.
특정 클래스 또는 구조체의 인스턴스의 부분으로 저장된 상수 혹은 변수
struct FixedLengthRange {
var firstValue: Int
let length: Int // 길이는 변경되지 않는다는 것을 표현하기 위해
}
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
let property는 초기화 전에 반드시 값을 가지고 있어야 하기 때문에 lazy로 선언이 불가능함
따라서 지연 프로퍼티는 반드시var(변수)
로 선언해야함
언제 사용될까?
예시
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 has not yet been created
주의해야할 점
예시
// x, y 좌표의 위치
struct Point {
var x = 0.0, y = 0.0
}
// width 와 height
struct Size {
var width = 0.0, height = 0.0
}
// 원점과 크기로 사각형을 정의
struct Rect {
var origin = Point()
var size = Size()
// center는 항상 origin과 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)"
newValue
라는 이름을 사용할 수 있음set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
get {
Point(x: origin.x + (size.width / 2),y: origin.y + (size.height / 2))
}
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"
정의
적용가능한 경우
stored property, computed property 모두 가능한데 내가 정의한 computed property만 안된다고 보면 됨
Stored properties that you define(내가 정의한 stored property)
Stored properties that you inherit(상속한 stored property)
Computed properties that you inherit(상속한 computed property)
상속된 프로퍼티에 관찰자를 적용하고자 하는 경우에는 하위 클래스(상속한 클래스)의 프로퍼티를 재정의하여 프로퍼티 관찰자를 추가하면 됨
여기에 없는 정의한 계산된 프로퍼티의 경우, 관찰자를 적용하지 않고 setter를 이용해서 값 변경을 관찰 및 응답할 수 있음
종류
참고 1
수퍼 클래스(상속된 클래스) 프로퍼티의 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
참고 2
관찰자를 가진 프로퍼티를 in-out 파라미터로 함수에 전달하면 willSet
과 didSet
관찰자는 항상 호출됩니다. 이것은 in-out 파라미터에 대한 copy-in-copy-out 메모리 모델 때문에 그렇습니다. 값은 함수 끝에서 프로퍼티에 항상 다시 작성됩니다.
정의
프로퍼티가 저장되는 방법을 관리하는 코드와 프로퍼티를 정의하는 코드 사이에 분리 계층을 추가함
예시
@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)
// Prints "0"
rectangle.height = 10
print(rectangle.height)
// Prints "10"
rectangle.height = 24
print(rectangle.height)
// Prints "12"
활용
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"
@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"
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
}
}
return $height || $width
는 프로퍼티 래퍼가 height 혹은 width를 변경했는지 판단하기 위하여 체크정의
전역변수: 함수, 메서드, 클로저, 타입 컨텍스트 외부에 정의된 변수
지역변수: 함수, 메서드, 클로저, 타입 컨텍스트 내부에 정의된 변수
참고
전역 상수, 변수는 lazy property처럼 항상 느리게 계산됨 하지만 lazy 키워드가 필요한것은 x
특징
예시
func someFunction() {
@SmallNumber var myNumber: Int = 0
myNumber = 10
// now myNumber is 10
myNumber = 24
// now myNumber is 12
}
정의
참고
→ 초기화 시 저장된 타입 프로퍼티에 값을 할당할 수 있는 초기화를 가지고 있지 않기 때문에~
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
}
}
접근
타입.프로퍼티
로 접근할 수 있음
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
}
}
}
}