iOS

[Swift] 프로퍼티

GODOLs 2024. 6. 17. 15:01

프로퍼티란?

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

저장 프로퍼티 (Store properties)

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

class Person {
    // 저장 프로퍼티 선언
    var name: String = "david" 
    var age: Int = 37 

    // 초기화 메서드 - 만약 초기값이 할당되어있다면 init을 생략해도 됩니다.
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    // 메서드
    func description() -> String {
        return "\(name) is \(age) years old."
    }
}

// Person 클래스의 인스턴스 생성
let person = Person(name: "Alice", age: 30)
print(person.description())  // 출력: Alice is 30 years old.

struct Car {
    // 저장 프로퍼티 선언
    var make: String
    var model: String
    var year: Int

    // 초기화 메서드는 기본 제공되므로 생략 가능
    // init(make: String, model: String, year: Int) {
    //     self.make = make
    //     self.model = model
    //     self.year = year
    // }

    // 메서드
    func description() -> String {
        return "\(year) \(make) \(model)"
    }
}

// Car 구조체의 인스턴스 생성
let car = Car(make: "Toyota", model: "Camry", year: 2022)
print(car.description())  // 출력: 2022 Toyota Camry

구조체는 프로퍼티에 맞는 생성자(Initializer)를 제공하지만 클래스는 달리 저장 프로퍼티를 사용하는 것이 좀 번거롭지만
만약 미리 초기값을 설정해준다면, 생성자를 직접 구현해줄 필요는 없습니다.

지연 저장 프로퍼티 (Lazy stored properties)

인스턴스를 생성할 때 프로퍼티에 값이 필요 없다면 프로퍼티를 옵셔널로 선언해줄 수 있습니다. 조금 다른 용도로 지연 저장 프로퍼티 (Lazy stored properties) 가 있습니다. 지연 저장 프로퍼티는 호출이 있을 때만 값을 초기화 하고, 이때 lazy 키워드를 사용합니다.

class ComplexCalculator {
    // 복잡한 계산을 수행하는 저장 프로퍼티를 lazy로 선언
    lazy var result: Int = {
        var sum = 0
        for i in 1...1000000 {
            sum += i
        }
        return sum
    }()

    init() {
        print("ComplexCalculator initialized")
    }
}

let calculator = ComplexCalculator()
print("Calculator created")
// 'result'에 처음 접근할 때 계산 수행 
print("Result: \(calculator.result)")

예시 코드를 보시면 result에 접근 하고 나서야 변수가 초기화 되는 것을 볼 수 있습니다.

연산 프로퍼티 (Computed properties)

연산 프로퍼티는 실제 값을 저장하는 프로퍼티가 아니라, 특정 상태에 따른 값을 연산하는 프로퍼티입니다. 인스턴스 내/외부의 값을 연산하여 적절한 값을 돌려주는 접근자(getter)의 역할이나 은닉화된 내부의 프로퍼티 값을 간접적으로 설정하는 설정자(setter)의 역할을 할 수 도 있습니다.
연산 프로퍼티는 클래스, 구조체, 열거형에서 사용할 수 있습니다.

struct Rectangle {
    var width: Double
    var height: Double

    // 연산 프로퍼티 area 정의
    var area: Double {
        get {
            return width * height
        }
        set {
            // 새로운 면적 값을 통해 너비를 설정
            width = newValue / height
        }
    }
}

var rect = Rectangle(width: 10, height: 5)
print("Area: \(rect.area)")  // 출력: Area: 50.0

rect.area = 100
print("New width: \(rect.width)")  // 출력: New width: 20.0

예시 코드를 보시면 area가 변경됨 (100) 에 따라 set 매서드 내부에서 width를 변경하는 형태로 구현되어 있다는 것을 보실 수 있습니다.
또한 이미 rect 변수의 초기화 값인 width와 height 값이 바뀜에 따라 area값을 get했을때는 50.0 으로 받아온다는 것을 보실 수 있을 것입니다.


주의
설정자 (set) 매서드를 정의 하지 않았다면 읽기 전용으로 새로운 값을 설정한다면 오류가 발생합니다.

struct Circle {
    var radius: Double

    // 읽기 전용 연산 프로퍼티 circumference 정의
    var circumference: Double {
        get {
            return 2 * .pi * radius
        }
    }
}

var circle = Circle(radius: 5)
print("Circumference: \(circle.circumference)")  // 출력: Circumference: 31.41592653589793

// circumference는 읽기 전용이므로 값을 설정하려고 하면 에러가 발생함
circle.circumference = 40  // 컴파일 에러 발생

프로퍼티 감시자 (Property observers)

프로퍼티 감시자(Property Observers)는 프로퍼티 값이 변경될 때 호출되는 코드 블록입니다. Swift에서는 willSet과 didSet이라는 두 가지 프로퍼티 감시자를 제공합니다. willSet은 새로운 값이 설정되기 직전에 호출되고, didSet은 새로운 값이 설정된 후에 호출됩니다.

프로퍼티 감시자는 저장 프로퍼티에 대해 사용할 수 있으며, 연산 프로퍼티에는 사용할 수 없습니다. 프로퍼티 감시자를 사용하면 값의 변경을 감지하고 필요한 추가 작업을 수행할 수 있습니다.

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 = 100
// 출력:
// About to set totalSteps to 100
// Added 100 steps

stepCounter.totalSteps = 200
// 출력:
// About to set totalSteps to 200
// Added 100 steps

이 예시 코드에서는 totalSteps 값이 변경될 때마다 해당 값의 변경 사항을 출력하는 기능을 구현했습니다. willSet은 새로운 값이 설정되기 직전에 실행되고, didSet은 새로운 값이 설정된 후에 실행됩니다. 이를 통해 값이 변경될 때 필요한 추가 작업을 수행할 수 있습니다.

타입 프로퍼티 (Type properties)

타입 프로퍼티(Type Properties)는 특정 타입의 모든 인스턴스에서 공통으로 사용하는 프로퍼티입니다. 타입 프로퍼티는 클래스, 구조체, 또는 열거형에서 정의될 수 있습니다. 타입 프로퍼티는 저장 프로퍼티와 연산 프로퍼티 모두 가능하며, 저장 프로퍼티는 반드시 기본값을 지정해야 합니다. 타입 프로퍼티는 인스턴스가 생성되지 않아도 접근할 수 있습니다.

타입 프로퍼티는 static 키워드를 사용하여 정의하며, 클래스의 연산 타입 프로퍼티는 class 키워드를 사용할 수도 있습니다.

타입 프로퍼티의 예시
구조체에서의 타입 프로퍼티

struct SomeStruct {
    // 저장 타입 프로퍼티
    static var storedTypeProperty = "Some value."

    // 연산 타입 프로퍼티
    static var computedTypeProperty: Int {
        return 1
    }
}

print(SomeStruct.storedTypeProperty)  // 출력: Some value.
print(SomeStruct.computedTypeProperty)  // 출력: 1

클래스에서의 타입 프로퍼티

class SomeClass {
    // 저장 타입 프로퍼티
    static var storedTypeProperty = "Some value."

    // 연산 타입 프로퍼티
    static var computedTypeProperty: Int {
        return 27
    }

    // 클래스에서의 연산 타입 프로퍼티 (서브클래스에서 재정의 가능)
    class var overridableComputedTypeProperty: Int {
        return 42
    }
}

print(SomeClass.storedTypeProperty)  // 출력: Some value.
print(SomeClass.computedTypeProperty)  // 출력: 27
print(SomeClass.overridableComputedTypeProperty)  // 출력: 42

타입 프로퍼티의 사용 예시

타입 프로퍼티는 모든 인스턴스에서 공유되는 데이터를 관리하는 데 유용합니다. 다음은 은행 계좌 클래스에서 전체 계좌의 고객 수를 관리하는 예제입니다.

class BankAccount {
    // 저장 타입 프로퍼티
    static var totalAccounts = 0

    // 인스턴스 저장 프로퍼티
    var accountNumber: Int
    var balance: Double

    init(accountNumber: Int, initialBalance: Double) {
        self.accountNumber = accountNumber
        self.balance = initialBalance
        BankAccount.totalAccounts += 1
    }

    // 인스턴스 메서드
    func deposit(amount: Double) {
        balance += amount
    }

    func withdraw(amount: Double) {
        balance -= amount
    }
}

// 새로운 계좌를 생성할 때마다 totalAccounts 증가
let account1 = BankAccount(accountNumber: 12345, initialBalance: 1000.0)
let account2 = BankAccount(accountNumber: 67890, initialBalance: 500.0)

print("Total bank accounts: \(BankAccount.totalAccounts)")  // 출력: Total bank accounts: 2

이 예제 코드에서는 BankAccount 클래스에 totalAccounts라는 저장 타입 프로퍼티를 정의하여, 생성된 모든 계좌의 총 수를 추적합니다. 각 인스턴스가 생성될 때마다 totalAccounts가 증가합니다.

타입 프로퍼티를 사용하면 특정 타입의 모든 인스턴스가 공통적으로 사용하는 데이터를 효율적으로 관리할 수 있습니다. 이를 통해 전체 인스턴스의 상태를 나타내거나 공유 데이터를 저장할 수 있습니다.

키 경로 (Keypath)

KeyPath에 대한 설명
Swift의 KeyPath는 타입 내의 특정 프로퍼티를 참조하는 방법을 제공하는 기능입니다. KeyPath를 사용하면 코드에서 프로퍼티를 간접적으로 접근하고 조작할 수 있습니다. 이는 주로 데이터 바인딩, 프로퍼티 관찰 및 표현식에서 유용합니다.

KeyPath는 다음과 같은 세 가지 유형이 있습니다:

KeyPath<Root, Value>: 읽기 전용 KeyPath
WritableKeyPath<Root, Value>: 읽기 및 쓰기 가능한 KeyPath
ReferenceWritableKeyPath<Root, Value>: 클래스 인스턴스에 대한 읽기 및 쓰기 가능한 KeyPath
KeyPath는 점(.) 구문을 사용하여 정의되며, 타입 안전하게 프로퍼티에 접근할 수 있게 해줍니다.

KeyPath 예시 코드입니다.

class Car {
    var make: String
    var model: String

    init(make: String, model: String) {
        self.make = make
        self.model = model
    }
}

let car = Car(make: "Toyota", model: "Camry")

// ReferenceWritableKeyPath 정의
let modelKeyPath = \Car.model

// KeyPath를 통해 값 읽기
let carModel = car[keyPath: modelKeyPath]
print("Car model: \(carModel)")  // 출력: Car model: Camry

// KeyPath를 통해 값 쓰기
car[keyPath: modelKeyPath] = "Corolla"
print("Updated car model: \(car.model)")  // 출력: Updated car model: Corolla

이 예제들을 통해 KeyPath가 어떻게 사용되는지 쉽게 이해할 수 있습니다. KeyPath는 프로퍼티에 간접적으로 접근할 수 있는 강력한 기능을 제공하며, 코드의 유연성과 재사용성을 높이는 데 유용합니다.

반응형