[Swift] 프로퍼티(Property)- 프로퍼티의 정의, 종류
증말 기본적인 개념인데도 그냥 대애충 두루뭉술하게만 알고 넘어갔었더라고요 🥲
막상 구체적으로 문제가 나오니까 확실히 아는게 없는 것 같아서
이번 기회에 한 번 정리해봅니다 ><
목차
프로퍼티란 어떤 대상(클래스,구조체,열거형etc.)에 값을 할당해주는 존재입니다.
프로퍼티는 크게 저장 프로퍼티, 연산 프로퍼티로 나눌 수 있습니다!
저장 프로퍼티 (Stored Properties)
Stored Properties
► 이름에서 알 수 있듯 가장 단순하고 기본적인 개념의 프로퍼티로, 클래스 또는 구조체의 인스턴스와 연관된 값을 저장하는 프로퍼티
► 클래스 / 구조체에서만 사용 가능!! (열거형x)
- 변수 저장 프로퍼티 (variable stored properties) - var 키워드 사용
- 상수 저장 프로퍼티 (constant stored properties) - let 키워드 사용
struct Point {
var x: Int // 변수 저장 프로퍼티
let y: Int // 상수 저장 프로퍼티
}
정의에서 봤듯이 어떤 대상 - 이경우에는 Point 라는 구조체에 x,y라는 값을 할당해주는 애들이 프로퍼티 입니다.
Lazy Stored Properties
► lazy라는 키워드를 사용하는 지연 저장 프로퍼티는 호출이 있어야 값을 초기화 합니다.
그 말은, 값이 처음으로 사용 되기 전에는 계산되지 않는다는 말이죠!
► 지연 저장 프로퍼티는 반드시! var로 선언해야 합니다. let 선언은 초기화가 되기 전에 항상 값을 갖는 프로퍼티이기 때문!!!
► 어떨 때 유용할까? 프로퍼티가 특정 요소에 의존적이어서, 그 요소가 끝나기 전에 값을 알 수 없는 경우.
혹은 복잡한 계산, 부하가 많이 걸리는 작업을 lazy로 선언하면 인스턴스 초기화 시점에 복잡한 계산을 피할 수 있읍니당.
► 지연 프로퍼티가 여러 스레드에서 사용되면? 한번만 실행된다는 보장이 x. Thread safe 하지 않음!!
► 단일 스레드에서 사용될 경우 초기화는 한 번!
class DataImporter {
/*
겁 나 오 래 걸 리 는 초기화 작업
*/
var filename = "data.txt"
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
/*
데이터를 관리하는 기능
*/
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 인스턴스는 이 시점에 생성돼 있지 x
//이렇게 직접 importer 인스턴스에 접근할때야 생성됨
print(manager.importer.filename)
연산 프로퍼티 (Computed Properties)
► 클래스, 구조체, 열거형은 저장 프로퍼티 뿐만 아니라 연산 프로퍼티도 선언할 수 있습니다.
► 저장 프로퍼티와 다르게 실제 값을 저장하고 있는 것이 아니라, 다른 프로퍼티와 간접적으로 값을 검색하고 세팅합니다
그게 바로 getter 와 setter(optional)라는 연산 프로퍼티!
► 계산값에 따라 값이 변할 수 있는 프로퍼티이기 때문에 항상 var 으로 선언!!!
►set(newValue) { ... } 와 같은 구조에서 (newValue)라고 인자이름을 지정하지 않아도 기본 이름인 newValue를 사용할 수 있습니다!
like this
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
► 예제 1
class P {
var x: Int {
get { return x }
set(newValue) { x = newValue * 2 }
}
}
var p = Point()
p.x = 12 // error!
이렇게 하면 에러가 납니다!!!!!
x값이 자기 자신을 리턴하고 자기 자신을 두배로 만들고 있기 때문에 이렇게 사용하면 오류가 납니다.
그래서 getter setter에는 필수적으로 변수가 더 있어야하는데, 이처럼 내부에서 사용하는 변수용으로 사용하는 stored Property는 언더바를 붙인 형태로 구분합니다.
class Test {
private var _myProperty: Int
var myProperty: Int {
get {
return _myProperty
}
set {
return _myProperty = newValue
}
}
}
► 예제2
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 {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
center라는 Point형 변수에 접근할 때는 origin, size라는 다른 프로퍼티를 이용하여 계산하고, center를 직접 설정할 때는 size를 이용해서 origin을 변경하고 있습니다.
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center //getter로 square의 center값 가져옴
square.center = Point(x: 15.0, y: 15.0) //setter로 center값 변경
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// "square.origin is now at (10.0, 10.0)" 출력
프로퍼티 옵저버 (Property Observers)
► 프로퍼티에 새 값이 설정(set)될 때마다 이벤트를 감지할 수 있습니다.
► 지연 저장 프로퍼티(lazy stored properties)에서는 사용할 수 없음!
► 연산 프로퍼티는 setter에서 값의 변화를 감지하므로 따로 옵저버를 정의할 필요가 없음!
► willSet : 값이 저장되기 바로 직전에 호출 (연산프로퍼티의 set에서와 마찬가지로 기본 값으로 newValue라는 파라미터명 사용)
didSet : 값이 저장되고 난 직후에 호출 (oldValue라는 파라미터 사용)
► 예제
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