[Swift 문법] 속성 (Property)

2na·2025년 6월 2일

Swift 🍎

목록 보기
7/9

프로퍼티의 종류에 대해 정리합니다 ✍🏻

저장 속성

값이 저장되는 일반적인 속성입니다. var, let 으로 선언 가능하며, 객체를 초기화할 때 각 저장속성은 반드시 값을 가져야합니다. (nil로 초기화하는 것도 가능함)

지연 저장 속성

lazy 키워드로 해당 속성의 초기화를 지연시킵니다. 그래서 인스턴스가 초기화되는 시점에 해당 속성이 초기화되는 것이 아닌 해당 속성에 접근하는 순간에 개별적으로 초기화됩니다. 그래서 let으로는 선언할 수 없습니다. (let은 초기화할 때 바로 값을 고정시켜주고 싶은데, lazy는 나중으로 값 초기화를 미루니깐..)

lazy를 사용하는 이유는 다음과 같이 정리해볼 수 있습니다.

  • 초기화 비용이 많이 필요한 뷰를 생성할 때

    • 바로 메모리에 올려버리면 생성 비용이 많이 들기 때문에 나중에 필요할 때 메모리에 올리는 방식으로 사용할 수 있습니다.
  • 항상 필요한 것이 아니고, 특정 시점 이후에 필요한 뷰를 생성할 때

  • 다른 속성을 이용해야 할때

    private let formatter = CalendarDateFormatter()
    private lazy var selectedIndex = formatter.getTodayDay()
    public lazy var selectedDate: String = weekDates[selectedIndex]
    • formatter를 사용하여 변수를 생성하고 있는데, 만약에 lazy 없이 그냥 생성하게 된다면

      이런 에러가 나게 됩니다. self가 available하기 전에 초기화할 수 없다고 하는데요.

    • 무슨 말이냐면….

      Swift에서는 모든 저장 속성이 초기화된 이후에서야 self(현재 인스턴스 자기 자신)가 유효해집니다. 원래는 self.fomatter.getTodayDay() 이렇게 써야하는데, 사실상 생략되어서 쓰이고 있는거죠. 그래서 self를 쓸 수 없는데 사용했다고 에러가 나는 것입니다.

계산 속성

속성의 형태를 가진 실질적 메소드입니다. 우리는 메소드를 만들 때, get, set 메소드를 많이 만들잖아요. 그런 비슷한 역할을 하는 속성입니다.

var로만 선언 가능하고, 자료형은 필수로 선언해야합니다.

struct Rectangle {
    var width: Double
    var height: Double
    
    var area: Double {
        get {
            return width * height
        }
        set {
            height = newValue / width
        }
    }
}

Rectangle이라는 구조체를 선언했고, 그 안에 area라는 계산 프로퍼티를 만들었습니다.

get 접근자는 area에 접근될때마다 area 값을 리턴해줍니다. set 접근자는 속성에 새로운 값이 할당되면 그 값(new Value)을 기반으로 다른 속성의 값을 조정할 수 있습니다.

var rect = Rectangle(width: 5.0, height: 10.0)

print("첫번째 height: ", rect.height)
print("첫번째 area: ", rect.area)

rect.area = 100.0

print("두번째 height: ", rect.height)
print("두번째 area: ", rect.area)

첫번째 area를 출력할 때는 get을 통해 접근하여 계산된 값을 리턴하게 됩니다.

그 이후에는 100으로 면적을 바꿔주었기 때문에, set에서 다른 속성들이 값이 바뀌게 됩니다.

타입 속성

타입 프로퍼티를 쉽게 말하면, 저장 속성과 계산 속성 앞에 static 키워드를 붙인 것입니다. 그러면 저장 타입 프로퍼티 & 계산 타입 프로퍼티가 됩니다. 이렇게 static을 붙이게 되면 자동으로 lazy하게 작동합니다.

이렇게 static이 붙으면 전역변수처럼 사용되는데요. 코드를 통해 보겠습니다.

class Person {
    static let name: String = "nayeon"
    var age: Int = 23
}

print(Person.name)

let person = Person()
print(person.age)

name에만 static을 붙여 저장 타입 프로퍼티로 만들어줬고, age는 그냥 var로 선언해주었습니다.

이렇게 static으로 선언하게 되면 객체를 선언할 필요 없이 Person.name으로 바로 접근이 가능합니다.

반면에 static으로 선언하지 않은 age는 객체를 생성해야만 age에 접근이 가능합니다.


person.name으로 하면 에러가 납니다

이때, 저장 타입 프로퍼티의 경우 항상 초기값을 선언해주어야합니다. 왜냐하면, 타입 프로퍼티는 초기화될 때 한번만 불려서 메모리에 올라가면 그 뒤로는 생성되지 않습니다. 대신 언제 어디서든 그 타입 프로퍼티에 접근할 수 있게 됩니다. 그래서 class 내의 init 함수와는 상관 없이 static으로 된 타입 프로퍼티는 무조건 초기값을 가져야합니다.

또한 위에서 static이 붙은 타입 프로퍼티는 lazy하게 작동한다고 했는데요. Person.name으로 불렸을 때, 그때 메모리에 올라가서 초기화됩니다.

하지만 static은 오버라이딩이 불가능합니다. 하지만! 앞에 static 대신 class 키워드가 붙은 계산 타입 프로퍼티는 오버라이딩이 가능합니다.

class Person {
    class var age: Int {
        get {
            return 23
        }
    }
}

class Nayeon: Person {
    override class var age: Int {
        get {
            return 24
        }
    }
}

속성 감시자 (Property Observer)

프로퍼티 옵저버는 말그대로 프로퍼티를 감시하는데요. 내가 관찰하는 프로퍼티의 값이 변경되는 것을 감지하고 알려줍니다. 이 속성 감시자는 저장 프로퍼티에 한해 추가할 수 있습니다.

속성 감시자는 두가지가 있는데요.

  • willSet : 값이 저장되기 직전에 호출됨
    • 값이 저장되기 직전에, 새로운 값이 파라미터 newValue로 전달됩니다.
  • didSet: 새 값이 저장된 직후에 호출됨
    • 값이 저장된 직후에, 이전 값이 파라미터 oldValue로 전달됩니다.
var name: String = "나연" {
    willSet {
        print("현재 이름: \(name), 바뀔 이름 : \(newValue)")
    }
    didSet {
        print("현재 이름: \(name), 이전 이름 : \(oldValue)")
    }
}

name = "효은"

profile
Studying Frontend 👩🏻‍💻

0개의 댓글