Closed Suyeon9911 closed 2 years ago
클래스 또는 구조체의 인스턴스와 연관된 값을 저장하는 가장 단순한 개념의 프로퍼티
필요에 따라 적절히 사용한 예시 !!!!
struct CoordinatePoint {
// 위치는 x, y 값이 모두 있어야 하므로 옵셔널이면 안 됩니다.
var x: Int
var y: Int
}
class Position {
// 현재 사람의 위치를 모를 수도 있습니다. - 옵셔널
var point: CoordinatePoint?
let name: String
init(name: String) {
self.name = name
}
}
// 이름은 필수지만 위치는 모를 수 있습니다.
let eunseoPosition: Position = Position(name: "eunseo")
// 위치를 알게되면 그때 위치 값을 할당해줍니다.
eunseoPosition.point = CoordinatePoint(x: 3, y: 6)
인스턴스를 생성할 때 프로퍼티에 값이 필요없다면 프로퍼티를 옵셔널로 선언해줄 수 있다 !
조금 다른용도로 ... 필요할 때 값이 할당되는 지연저장프로퍼티가 있다.
호출이 있어야 값을 초기화하며, lazy 키워드 사용
상수는 인스턴스가 완전히 생성되기 전에 초기화해야하므로 맞지 않음
var로 정의
주로 복잡한 클래스나 구조체를 구현할때 많이 사용된다 !@
클래스 인스턴스의 저장 프로퍼티로 다른 클래스 인스턴스나 구조체 인스턴스를 할당해야 할 때가 있음 !
이럴 떄 인스턴스를 초기화하면서 저장 프로퍼티로 쓰이는 인스턴스들이 한번에 생성되어야한다면 :?
또 굳이 모든 저장 프로퍼티를 사용할 필요가 없다면???
지ㅈ연저장 프로퍼티 사용하자 ~
잘 사용하면 불필요한 성능저하나 공간 낭비를 줄일 수 있다
struct CoordinatePoint {
var x: Int = 0
var y: Int = 0
}
class Position {
lazy var point: CoordinatePoint = CoordinatePoint()
let name: String
init(name: String) {
self.name = name
}
}
let eunseoPosition = Position(name: "eunseo")
// 이 코드를 통해 point 프로퍼티로 처음 접근할 때
// point 프로퍼티의 CoordinatePoint가 생성됩니다.
print(eunseoPosition.point)
struct CoordinatePoint {
var x: Int // 저장 프로퍼티
var y: Int // 저장 프로퍼티
// 대칭점을 구하는 메서드 - 접근자
// Self는 타입 자기 자신을 뜻합니다.
// Self 대신 CoordinatePont를 사용해도 됩니다.
func oppositePoint() -> Self {
return CoordinatePoint(x: -x, y: -y)
}
// 대칭점을 설정하는 메서드 - 설정자
mutating func setOppositePoint(_ opposite: CoordinatePoint) {
x = -opposite.x
y = -opposite.y
}
}
var yagomPosition: CoordinatePoint = CoordinatePoint(x: 10, y: 20)
yagomPosition // 현재좌표
yagomPosition.oppositePoint() // 대칭 좌표
yagomPosition.setOppositePoint(CoordinatePoint(x: 15, y:10))
yagomPosition // 대칭좌표를 15,10으로 설정하면 현재는 -15, -10 이 된다.
oppositePoint() 로 대칭점을 구할 수 있고, setOppositePoint()로 대칭점을 설정해 줘야한다.
연산프로퍼티를 사용해서 더 간결하게 표현하자
struct CoordinatePoint {
var x: Int
var y: Int
// 대칭좌쵸
var oppositePoint: CoordinatePoint {
// 연산 프로퍼티
get {
return CoordinatePoint(x: -x, y: -y)
}
set(opposite) {
x = -opposite.x
y = -opposite.y
}
}
}
이런식으로 연산 프로퍼티를 사용하며 하나의 프로퍼티에 접근자와 설정자가 모두 모여있고, 해당 프로퍼티가 어떤 역할을 한느지 좀 더 명확하게 표현 가능하다. 인스턴스를 사용하는 입장에서느 마치 저장 프로퍼티인 것 처럼 편하게 사용할 수 있다.
설정자의 매개변수로 원하는 이름을 소괄호 안에 명시해주면 set 메서드 내부에서 전달받은 전달인자를 사용할수 있다.
관용적인 표현으로 newValue로 매개변수 이름을 대신할 수 있다. 그럴경우에는 매개변수를 따로 표기하지 말아야함
접근자 내부의 코드가 단 한 줄이고, 그 결괏값의 타입이 프로퍼티 타입과 같다면 return 키워드를 생략해도 반환값이 된다.
굳이 대칭점을 설정해줄 필요가 없다면 읽기전용으로 연산프로퍼티 사용. get 메서드만.
class Account {
var credit: Int = 0
willSet {
print("잔액이 \(credit)원에서 \(newValue)원으로 변경될 예정입니다.")
}
didSet {
print("잔액이 \(oldValue)원에서 \(credit)로 변경되었습니다.")
}
}
}
let myAccount: Account = Account()
// 잔액이 0원에서 1000원으로 변경될 예정입니다.
myAccount.credit = 1000
// 잔액이 0원에서 1000원으로 변경되었습니다.
클래스를 상속 받았다면 기존의 연산 프로퍼티를 재정의 하여 프로퍼티 감시자를 구현할수도 있습니다.
연산 프로퍼티를 재정의 해도 기존의 연산 프로퍼티 기능은 동작
dollarValue가 포함되어 있는 Account 클래스를 상속받은 ForeignAccount 클래스에서 기존 dollarValue 프로퍼티를 재정의하여 프로퍼티 감시자를 구현하는 예제
class Account {
var credit: Int = 0
willSet {
print("잔액이 \(credit)원에서 \(newValue)원으로 변경될 예정입니다.")
}
didSet {
print("잔액이 \(oldValue)원에서 \(credit)로 변경되었습니다.")
}
}
var dollarValue: Double {
get {
return Double(credit) / 1000.0
}
set {
credit = Int(newValue * 1000)
print("잔액을 \(newValue)달러로 변경중")
}
}
}
class ForeignAccount: Account {
override var dollarValue: Double {
willSet {
print("잔액이 \(dollarValue)에서 \(newValue)로 변경될 예정")
}
didSet {
print("잔액이 \(oldValue)원에서 \(dollarValue)로 변경되었습니다.")
}
}
}
let myAccount: ForeignAccount = ForeignAccount()
// 잔액이 0원에서 1000원으로 변경될 예정
myAccount.credit = 1000
// 잔액이 0원에서 1000으로 변경되었습니다.
// 잔액이 1.0달러에서 2.0달러로 변경될 예정
// 잔액이 1000원에서 2000원으로 변경될 예정
// 잔액이 1000원에서 2000원으로 변경됨
myAccount.dollarValue = 2 // 잔액을 2.0달러로 변경중
//잔액이 1.0달러에서 2.0달러로 변경되었습니다.
-만약 프로퍼티 감시자가 있는 프로퍼티를 함수의 입출력 매개변수의 전달인자로 전달한다면 항상 willSet과 didSet 감시자를 호출, 함수 내부에서 값이 변경되든 되지 않든 간에 함수가 종료되는 시점에 값을 다시 쓰기 때문 !!
타입프로퍼티는 두 가지
class AClass {
// 저장 타입 프로퍼티
static var typeProperty: Int = 0
// 저장 인스턴스 프로퍼티
var instanceProperty: Int = 0 {
didSet {
// Self.typeProperty는
// AClass.typeProperty 와 같은 표현
Self.typeProperty = instanceProperty + 100
}
}
// 연산 타입 프로퍼티
static var typeComputedProperty: Int {
get {
return typeProperty
}
set {
typeProperty = newValue
}
}
}
AClass.typeProperty = 123
let classInstance: AClass = AClass()
classInstance.instanceProperty = 100
print(AClass.typeProperty) // 200
print(AClass.typeComputedProperty) // 200
func someFunction(paramA: Any, paramB: Any) {
print("어쩌구")
}
var functionReference = someFunction(paramA:paramB)
functionReference("A","B")
functionReference = anotherFunction(paramA:paramB)
키 경로 타입은 AnyKeyPath라는 클래스로부터 파생된다
제일 많이 확장된 키 경로 타입은 WritableKeyPath<Root, Value> 와 ReferenceWritableKeyPath<Root, Value> 타입
WritableKeyPath<Root, Value> : 값 타입에 키 경로 타입으로 읽고 쓸 수 있는 경우에 적용
ReferenceWritableKeyPath<Root, Value> : 참조타입, 즉 클래스 타입에 키 경로 타입으로 읽고 쓸수 있는 경우에 적용
키경로는 역슬래스와 타입, 마침표 경로로 구성됨
\타입이름.경로.경로.경로
여기서 경론느 프로퍼티 이름
class Person {
var name: String
init(name: String) {
self.name = name
}
}
struct Stuff {
var name: String
var owner: String
}
print(type(of: \Person.name)) // ReferenceWritableKeyPath<Person,String>
print(type(of: \Stuff.name)) // WritableKeyPath<Stuff, String>
// 기존의 키 경로에 하위 경로 덧붙이기
let keyPath = \Stuff.owner
let nameKeyPath = keyPath.appending(path: \.name)
class Person {
var name: String
init(name: String) {
self.name = name
}
}
struct Stuff {
var name: String
var owner: String
}
let yagom = Person(name: "yagom")
let hana = Person(name: "hana")
let macbook = Stuff(name: "MacBook Pro", owner: yagom)
var iMac = Stuff(name: "iMac", owner: yagom)
let iPhone = Stuff(name: "iPhone", owner: hana)
let stuffNameKeyPath = \Stuff.name
let ownerkeyPath = \Stuff.owner
// \Stuff.owner.name 과 같은 표현
let ownerNameKeyPath = ownerkeyPath.appending(path: \.name)
// 키 경로와 서브스크립트를 이용해 프로퍼티에 접근하여 값을 가져온다.
print(macbook[keyPath: stuffNameKeyPath]) // MacBook Pro
print(iMac[keyPath: stuffNameKeyPath]) // iMac
print(iPhone[keyPath: stuffNameKeyPath]) // iPhone
print(macbook[keyPath: ownerNameKeyPath]) // yagom
print(iMac[keyPath: ownerNameKeyPath]) // yagom
print(iPhone[keyPath: ownerNameKeyPath]) // hana
// 키 경로와 서브스크립트를 이용해 프로퍼티에 접근하여 값을 변경
iMac[keyPath: stuffNameKeyPath] = "iMac Pro"
// 상수로 지정한 값 타입과 읽기 전용 프로퍼티는 키 경로 스크립트로도 값을 바꿔줄수 없다.
// macbook 의 stuffName은 위처럼 바꿀 수 없다.
struct Person {
let name: String
let nickname: String
let age: Int
var isAdult: Bool {
return age > 18
}
}
let yagom: Person = Person(name: "yagom", nickname: "bear", age: 100)
let hana: Person = Person(name: "hana", nickname: "na", age: 100)
let happy: Person = Person(name: "happy", nickname: nil, age: 3)
let family: [Person] = [yagom, hana, happy]
let names: [String] = family.map(\.name)
let nicknames: [String] = family.compactMap(\.nickname)
let adults: [String] = family.filter(\.isAdult).map(\.name)
프로퍼티와 메서드
프로퍼티
저장 프로퍼티
연산 프로퍼티
타입 프로퍼티
프로퍼티 옵저버