본문 바로가기
iOS

[Swift] 프로퍼티

by GODOLs 2024. 6. 17.

목차

    프로퍼티란?

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

    저장 프로퍼티 (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는 프로퍼티에 간접적으로 접근할 수 있는 강력한 기능을 제공하며, 코드의 유연성과 재사용성을 높이는 데 유용합니다.

    반응형