콘텐츠로 건너뛰기

[야곰 Swift 프로그래밍] 10 프로퍼티와 메소드 – 1) 프로퍼티

10.1 프로퍼티

프로퍼티는 클래스, 구조체 또는 열거형 등에 관련된 값을 뜻함.

  • 저장 프로퍼티(stored properties): 인스턴스의 변수 또는 상수
  • 연산 프로퍼티(computed properties): 값을 저장한 것이 아니라, 특정 연산을 실행한 결과값을 의미
  • 타입 프로퍼티(type properties):  각각의 인스턴스가 아닌 타입 자체에 속하는 프로퍼티. 타입 자체에 영향을 미치는 프로퍼티. 저장 타입 프로퍼티와 연산 타입 프로퍼티가 있음

더불어, 프로퍼티의 값이 변하는 것을 감시하는 프로퍼티 감시자(Property Observers)도 있음. 프로퍼티 감시자는 프로퍼티의 값이 변할 때 값의 변화에 따른 특정 작업을 실행함. 프로퍼티는 저장 프로퍼티에 적용할 수 있으며 부모클래스로부터 상속받을 수 있음

10.1.1 저장 프로퍼티 – 10.1.3 연산 프로퍼티

// 10.1.1 저장 프로퍼티 - 10.1.3 연산 프로퍼티

// 저장 프로퍼티:
// 클래스 또는 구조체의 인스턴스와 연관된 값을 저장하는 가장 단순한 개념의 프로퍼티
// var 키워드를 사용하면 변수 저장 프로퍼티, let 키워드를 사용하면 상수 저장 프로퍼티

// 구조체와 클래스의 저장 프로퍼티, 이니셜라이저
// 구조체: 저장 프로퍼티를 모두 포함하는 이니셜라이저를 자동으로 생성 (저장 프로퍼티가 옵셔널이 아니더라도)
// 클래스: 저장 프로퍼티가 옵셔널이 아니라면 프로퍼티 기본값을 지정해주거나, 이니셜라이저를 사용자 정의하여 프로퍼티를 반드시 초기화해주어야 함


// 연산 프로퍼티:
// 값을 저장하는 프로퍼티가 아니라, 특정 상태에 따른 값을 연산하는 프로퍼티
// 메서드 형식보다 훨씬 더 간편하고 직관적
// 쓰기(set) 전용 상태로 구현할 수 없음. 읽기(get) 전용 상태로는 구현 가능

struct CoordinatePoint{
    // 구조체는 이니셜라이저 제공
    
    // 저장 프로퍼티
    var x: Int
    var y: Int
    
    // 연산 프로퍼티
    var oppositePoint: CoordinatePoint{
        get{
            // return CoordinatePoint(x: -x, y: -y)와 동일. return 생략 가능
            CoordinatePoint(x: -x, y: -y)
        }
        
        set{
            x = -newValue.x
            y = -newValue.y
        }
    }
}

class Position{
    lazy var point: CoordinatePoint = CoordinatePoint()
    let name: String
    
    init(name: String){ // 클래스는 이니셜라이저 제공X
        self.name = name
    }
}

var yagomPosition: CoordinatePoint = CoordinatePoint(x: 10, y: 20)
print(yagomPosition.oppositePoint)
yagomPosition.oppositePoint = CoordinatePoint(x: 15, y:15)
print(yagomPosition)

10.1.4 프로퍼티 감시자(property observers)

//10.1.4 프로퍼티 감시자. property observers
// 프로퍼티 감시자는 프로퍼티의 값이 새로 할당될 때마다 호출
// 변경되는 값이 현재의 값과 같더라도 호출
// willSet, didSet

class Account{

    // 저장 프로퍼티
    var credit: Int = 0{
        willSet{
            print("잔액이 \(credit)에서 \(newValue)원으로 변경될 예정입니다")
        }
        didSet{
            print("잔액이 \(oldValue)에서 \(credit)원으로 변경되었습니다")
        }
    }
    
    // 연산 프로퍼티
    var dollarValue: Double{
        get{
            Double(credit)
        }
        set{
            credit = Int(newValue*1000)
            print("잔액을 \(newValue)달러로 변경 중입니다.")
        }
    }
}

class ForeignAccount: Account{
    // 기존 연산 프로퍼티를 재정의하여 프로퍼티 감시자를 구현
    // 연산 프로퍼티를 재정의해도 기존의 연산 프로퍼티 기능(접근자와 설정자, get과 set 메서드)는 동작합니다
    override var dollarValue: Double{
        willSet{
            print("잔액이 \(dollarValue)에서 \(newValue)달러로 변경될 예정입니다")
        }
        didSet{
            print("잔액이 \(oldValue)에서 \(dollarValue)달러로 변경되었습니다")
        }
    }
}

let myAccount: ForeignAccount = ForeignAccount()
myAccount.credit = 1000

myAccount.dollarValue = 2

10.1.5 전역변수와 지역변수

//: [Previous](@previous)

import Foundation

// 10.1.5 전역변수와 지역변수
// 연산 프로퍼티와 프로퍼티 감시자는 전역변수와 지역변수 모두에 사용할 수 있음
// 전역변수 또는 전역 상수는 지연 저장 프로퍼티(lazy)처럼 처음 접근할 때 최초 연산이 이루어짐
// 반대로, 지역변수 및 지역 상수는 절대로 지연 연산되지 않음

var wonInPocket: Int = 2000{
    willSet{
        print("주머니 속 돈이 \(wonInPocket)에서 \(newValue)원으로 변경될것임")
    }
    didSet{
        print("주머니 속 돈이 \(oldValue)에서 \(wonInPocket)원으로 변경되었음")
    }
    
}

var dollarInPocket: Double{
    get{
        Double(wonInPocket)/1000.0
    }
    set{
        wonInPocket = Int(newValue * 1000.0)
    }
}

print(dollarInPocket)
dollarInPocket = 2.5

print(wonInPocket)

10.1.6 타입 프로퍼티 (type property)

//: [Previous](@previous)

// 10.1.6 타입프로퍼티
// 각각의 인스턴스가 아닌 타입 자체에 속하는 프로퍼티. 타입 자체에 영향을 미치는 프로퍼티
// 타입 프로퍼티의 값은 하나이며, 그 타입의 모든 인스턴스가 공통으로 사용하는(C언어의 static constant와 유사), 모든 인스턴스에서 공용으로 접근하고 값을 변경할 수 있는 변수(C언어의 static 변수와 유사) 등을 정의할 때 유용
// 타입 프로퍼티의 종류: 저장 타입 프로퍼티, 연산 타입 프로퍼티
import Foundation

class AClass{
    // 저장 타입 프로퍼티
    // 반드시 초기값을 설정해야함
    // 지연 연산됨. 지연 연산 된다고해서 lazy 키워드로 표시해주지는 않음
    static var typeProperty: Int = 0

    var instanceProperty: Int = 0 {
        didSet{
            Self.typeProperty = instanceProperty + 100
        }
    }

    // 연산 타입 프로퍼티
    static var typeComputedPropety: Int {
        get{
            typeProperty
        }
        set{
            typeProperty = newValue
        }
    }

}

// 타입 프로퍼티는 인스턴스를 생성하지 않고도 사용할 수 있음
AClass.typeProperty = 123

let classInstance: AClass = AClass()
classInstance.instanceProperty = 100

print(AClass.typeProperty)
print(AClass.typeComputedPropety)


// 타입 프로퍼티를 타입 상수로 사용할 수도 있음
class Account{
    
    // 타입 상수
    static let dollarExchangeRate: Double = 1000.0
    
    var credit: Int = 0
    
    var dollarValue: Double{
        get{
            return Double(credit) / Self.dollarExchangeRate
            // Self.dollarExchangeRate는 Account.dollarExchangeRate와 같은 표현
            // Self는 Account 타입을 가리킴. self와 Self의 차이점: https://wodyios.tistory.com/2

        }
        set{
            // Account는 Self와 같은 표현
            credit = Int(newValue * Account.dollarExchangeRate)
            print("잔액을 \(newValue)달러로 변경 중입니다")
        }
    }
    
    
}

10.1.7 키 경로 (Key path)

//: [Previous](@previous)

import Foundation


// 10.1.7 키 경로

// 프로퍼티에서 값을 바로 꺼내오는 것이 아니라 어떤 프로퍼티의 위치만 참조하도록 할 수 있습니다
// 키 경로(Key path)를 활용하는 방법입니다
// 키 경로를 사용하여 간접적으로 특정 타입의 어떤 프로퍼티 값을 가리켜야 할지 미리 지정해두고 사용할 수 있습니다

// 키 경로는 역슬래시(\)와 타입, 마침표(.) 경로로 구성됩니다. 여기서 경로는 프로퍼티 이름이라고 생각하면 됩니다

class Person{
    var name: String
    init(name: String){
        self.name = name
    }
}

struct Stuff{
    var name: String
    var owner: Person
}

print(type(of: \Person.name))
print(type(of: \Stuff.name))


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])
print(iMac[keyPath: stuffNameKeyPath])
print(iPhone[keyPath: stuffNameKeyPath])

print(macbook[keyPath: ownerNameKeyPath])
print(iMac[keyPath: ownerNameKeyPath])
print(iPhone[keyPath: ownerNameKeyPath])

// 키 경로와 서브스크립트를 이용해 프로퍼티에 접근하여 값을 변경
iMac[keyPath: stuffNameKeyPath] = "iMac Pro"
iMac[keyPath: ownerKeyPath] = hana
print(iMac[keyPath: stuffNameKeyPath])
print(iMac[keyPath: ownerNameKeyPath])

// 어디다 쓰일까?
// 키 경로를 잘 활용하면 프로토콜과 마찬가지로 타입 간의 의존성을 낮추는 데 많은 도움을 줍니다.
// 또, 애플의 프레임워크는 키-값 코딩 등 많은 곳에 키 경로를 활용하므로, 애플 프레임워크 기반의 애플리케이션을 만든다면 잘 알아두기 바랍니다. 많은 도움이 될 것입니다.