Suyeon9911 / TIL

매일 오전에 적는 미라클 TIL 🥣
11 stars 0 forks source link

[Swift] Property의 모든 것 #47

Closed Suyeon9911 closed 2 years ago

Suyeon9911 commented 2 years ago

프로퍼티와 메서드

프로퍼티

저장 프로퍼티

연산 프로퍼티

타입 프로퍼티

프로퍼티 옵저버

Suyeon9911 commented 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)
Suyeon9911 commented 2 years ago

지연저장프로퍼티 Lazy,,,

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)

다중스레드와 지연저장 프로퍼티

Suyeon9911 commented 2 years ago

연산프로퍼티

굳이 메서드를 두고 왜 연산 프로퍼티를 쓸까 ???

Suyeon9911 commented 2 years ago

메서드로 구현된 접근자와 설정자

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
        }
    }
}
Suyeon9911 commented 2 years ago

Property observers

프로퍼티 값이 변경되기 직전 : willSet, 프로퍼티 값이 변경된 직후 : didSet

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원으로 변경되었습니다. 
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 감시자를 호출, 함수 내부에서 값이 변경되든 되지 않든 간에 함수가 종료되는 시점에 값을 다시 쓰기 때문 !!

Suyeon9911 commented 2 years ago

전역변수와 지역변수

Suyeon9911 commented 2 years ago

타입 프로퍼티

타입프로퍼티는 두 가지

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
Suyeon9911 commented 2 years ago

키 경로

func someFunction(paramA: Any, paramB: Any) {
    print("어쩌구")
}

var functionReference = someFunction(paramA:paramB)
functionReference("A","B")
functionReference = anotherFunction(paramA:paramB)

키 경로 타입은 AnyKeyPath라는 클래스로부터 파생된다

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)
Suyeon9911 commented 2 years ago

각 인스턴스의 keyPath 서브스크립트 메서드에 키 경로를 전달하여 프로퍼티에 접근

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은 위처럼 바꿀 수 없다.
Suyeon9911 commented 2 years ago

접근수준과 키 경로

자신을 나타내는 키 경로인 .self를 사용하면 인스턴스 그 자체를 표현하는 키경로가 됩니다.

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)