프로퍼티(porperty)
Stored property(저장 프로퍼티)
- 타입(클래스, 구조체)의 인스턴스에서 값을 저장하는 프로퍼티.
- 저장 프로퍼티가 옵셔널이 아니면 반드시 클래스, 구조체 내에서 초기값을 설정하거나 생성자를 통해 초기화해야 한다. 옵셔널이면 반드시 초기화할 필요가 없다.
- 클래스 또는 구조체의 인스턴스는 사용되기 전에 반드시 생성자를 통해 초기화를 해야 한다.
- memberwise 생성자: 저장 프로퍼티 명들을 매개변수로 하는 생성자. 구조체는 memberwise 생성자를 기본적으로 제공하며 재정의할 수 있다.
*Lazy stored property(지연 저장 프로퍼티)
저장 프로퍼티가 객체 안에서 인스턴스가 생성될 때 초기화되는 것이 아니라 사용되는 시점에 초기화된다.
메모리를 효율적으로 사용할 수 있다. 메모리를 많이 사용하는 시스템일 때 쓰면 좋다. 좀 좋은 컴파일러는 대부분 이렇게 한다.
struct CoordinatePoint {
var x: Int = 0
var y: Int = 0
}
class Position {
lazy var point: CoordinatePoint = CordinatePoint()
let name: String
init(name: String) {
self.name = name
}
}Computed property(연산 프로퍼티)
- 저장 프로퍼티의 get 메서드와 set 메서드를 수행한다.
- get: 저장 프로퍼티 값을 읽어 온다.
- set: 저장 프로퍼티에 값을 저장한다.
- 변수(var)에만 선언할 수 있다. 상수(let)에는 선언할 수 없다.
- get 메서드만 구현해도 되지만 set 메서드만 구현할 수는 없다.
- get 메서드는 매개변수가 필요없고, 반환만 잘하면 된다. 내부 코드가 한 줄이다. 반환값의 자료형이 연산 프로퍼티의 자료형과 동일하면 return 키워드를 생략할 수 있다.
- set의 자료형이 연산 프로퍼티의 자료형과 동일하면 자료형을 작성하지 않는다. 매개변수가 생략되면 newValue라는 매개변수가 자동으로 지정된다.
- 일반적으로 get/set 메서드의 자료형은 연산 프로퍼티의 자료형과 동일해서 생략해도 되는 일이 많다.
- get이랑 set을 나누는 이유는 보안 때문이다.
struct CoordinatePoint {
var x: Int
var y: Int
func getOppositePoint() -> Self {
return CoordinatePoint(x: -x, y: -y)
}
mutating func setOppositePoint(_ opposite: CoordinatePoint) {
self.x = -opposite.x
self.y = -opposite.y
}
}
var aPosition: CoordinatePoint = CoordinatePoint(x: 10, y: 20)
print(aPosition) //CoordinatePoint(x: 10, y: 20)
print(aPosition.getOppositePoint()) //CoordinatePoint(x: -10, y: -20)
aPosition.setOppositePoint(CoordinatePoint(x: 15, y: 10))
print(aPosition) //CoordinatePoint(x: -15, y: -10)//위 구조체를 연산 프로퍼티로 구현
sturct CoordinatePoint {
var x: Int
var y: Int
var oppositePoint: CoordinatePoint {
get {
return CoordinatePoint(x: -x, y: -y)
}
set(opposite) {
x = -opposite.x //x = -newValue.x
y = -opposite.y //y = -newValue.y
}
}
}
- mutating: 구조체와 열거형에서 메서드가 프로퍼티를 변경한다는 것을 명시하기 위해 사용한다. 네? 구조체에 함수를 잘 안 넣는다고요?
뭔소린지 모르겠음.
print 쓰면 get 메서드가 호출되고 값을 저장하면 값이 저장되는 게 아니라 set 메서드가 호출된다. 연산 프로퍼티 이름은 껍데기일 뿐...😢
Type property(타입 프로퍼티)
- 타입의 프로퍼티. 인스턴스 없이 타입을 통해 접근할 수 있다. C의 static 변수와 유사하다.
- 타입만 선언해도 그 자체로 메모리가 존재한다.
- static 키워드를 붙여서 저장 프로퍼티와 연산 프로퍼티를 타입 프로퍼티로 만들 수 있다. 이때 저장 타입 프로퍼티는 반드시 초기값이 설정되어 있어야 하며 기본적으로 지연 연산이 되므로 lazy 키워드를 붙일 필요가 없다.
Property observer(프로퍼티 감시자)
- willSet 메서드: 저장 프로퍼티가 변경되기 직전, set 메서드가 실행되기 전에 호출된다. 매개변수가 없으면 newValue를 사용할 수 있다.
- didSet 메서드: 저장 프로퍼티가 변경된 직후, set 메서드가 실행된 후에 호출된다. 매개변수가 없으면 oldValue를 사용할 수 있다.
값이 어떻게 변경되는지 관찰하기 편하다.
연산 프로퍼티는 클래스를 상속받았을 때만 연산 프로퍼티를 재정의하여 프로퍼티 감시자를 구현할 수 있다.
재정의(override)하면서 willSet과 didSet도 재정의할 수 있다.
import Foundation
class Account {
var credit: Int = 0 {
willSet {
print("1 저장 willSet - 잔액이 \(credit)원에서 \(newValue)원으로 변경될 예정입니다.")
}
didSet {
print("2 저장 didSet - 잔액이 \(oldValue)원에서 \(credit)원으로 변경되었습니다.")
}
}
var dollarValue: Double {
get {
return Double(credit / 1000)
}
set {
credit = Int (newValue * 1000)
print("3 연산 set - 잔액을 \(newValue)달러로 변경 중입니다.")
}
}
}
class ForeignAccount: Account {
override var dollarValue: Double {
willSet {
print("4 감시자 willSet - 잔액이 \(self.dollarValue)달러에서 \(newValue)달러로 변경될 예정입니다.")
}
didSet {
print("5 감시자 didSet - 잔액이 \(oldValue)달러에서 \(self.dollarValue)달러로 변경되었습니다.")
}
}
}
let myAccount: ForeignAccount = ForeignAccount()
myAccount.credit = 1000
myAccount.dollarValue = 2
/* 출력 결과
1 저장 willSet - 잔액이 0원에서 1000원으로 변경될 예정입니다.
2 저장 didSet - 잔액이 0원에서 1000원으로 변경되었습니다.
4 감시자 willSet - 잔액이 1.0달러에서 2.0달러로 변경될 예정입니다.
1 저장 willSet - 잔액이 1000원에서 2000원으로 변경될 예정입니다.
2 저장 didSet - 잔액이 1000원에서 2000원으로 변경되었습니다.
3 연산 set - 잔액을 2.0달러로 변경 중입니다.
5 감시자 didSet - 잔액이 1.0달러에서 2.0달러로 변경되었습니다.
*/
처음에 호출되는 건 ForeignAccount의 willSet -> Account의 willSet 2000으로 변경-> Account의 didSet -> dollarValue의 set -> ForeignAccount의 didSet
키 경로(key path)
\타입.프로퍼티,프로퍼티.프로퍼티
타입(클래스, 구조체) 내의 프로퍼티의 주소 경로
- WritableKeyPath<Root, Value>: 구조체(값 타입) 내의 읽고 쓸 수 있는 프로퍼티의 주소 경로의 자료형
- ReferenceWritableKeyPath<Root, Value>: 클래스(참조 타입) 내의 읽고 쓸 수 있는 프로퍼티의 주소 경로의 자료형
- 서브스크립트: 인스턴스 뒤에 대괄호[]를 사용하여 인스턴스 내의 프로퍼티에 접근 가능하게 한다.
메서드(method)
인스턴스 메서드(instance method)
인스턴스가 생성되었을 때 인스턴스를 통해 호출할 수 있는 호출 가능하다. 인스턴스를 생성해야 사용할 수 있다.
메서드가 프로퍼티를 변경한다는 것을 명시하기 위해 mutating을 써야할지 생각을 해야 한다. 구조체 또는 열거형에서만 명시한다는 점을 기억하자~
callAsFunction: 인스턴스를 함수처럼 호출하도록 하는 메서드. 다중정의(overloading)가 가능하다. - 메서드 명은 동일하지만 매개변수와 반환 타입을 다르게 해서 여러 개의 메서드를 생성할 수 있다.
타입 메서드(type method)
인스턴스 없이 타입을 통해 호출 가능한 메서드.
프로퍼티는 뇌고 메소드는 몸이라고 누가 그랬대.
설명이 막 길게 써있는데 우리는 프로그래머니까 코드를 보면 알겠지.(ㅋㅋㅋ)
클래스 타입의 메서드는 static 키워드를 사용하면 상속 후 재정의 불가, class 키워드를 사용하면 가능하다.
구조체는 static 키워드를 사용하여 메서드를 타입 메서드로 정의한다.
import Foundation
struct SystemVolume {
static var volume: Int = 5
static func mute() {
Self.volume = 0
}
}
class Navigation {
var volume: Int = 5
func guideWay() {
SystemVolume.mute()
}
func finishGuideWay() {
SystemVolume.volume = self.volume
}
}
SystemVolume.volume = 10
SystemVolume.mute()
var myNavi: Navigation = Navigation()
myNavi.guideWay()
print(SystemVolume.volume)
myNavi.finishGuideWay()
print(SystemVolume.volume)
/* 결과
0
5
*/
SystemVolume은 실행하자마자 메모리에 자리가 생겼음.
종속되어 있는 프로퍼티인지(?)
self 프로퍼티와 Self 프로퍼티
- Self: 타입(클래스, 구조체) 자기 자신을 나타내는 프로퍼티
- self: 인스턴스 자기 자신을 나타내는 프로퍼티. 클래스의 인스턴스는 참조 타입이기 때문에 변경할 수 없다. 구조체와 열거형의 인스턴스는 값 타입이라서 self 프로퍼티를 변경할 수 있다.
0 댓글