Danny의 iOS 컨닝페이퍼
article thumbnail

속성(Properties)의 종류

크게 5가지로 나눌 수 있습니다.

  • 저장 속성 - Stored Properties
  • 지연 저장 속성- Lazy Stored Properties
  • 계산 속성 - Computed Properties
  • 타입 속성 - Type Properties
  • 속성 감시자 - Property Observer

 

 

저장 속성(Stored Properties)

값이 저장되는 일반적인 속성. 이 자체가 메모리 공간을 갖습니다.

변수(var)상수(let)로 선언 가능합니다.

 

선언

class Person {
    let name: String = "Daniel"
    var age: Int = 30
}

let daniel = Person()

daniel.name  // "Daniel"
daniel.age   // 30

 

 

지연 저장속성(Lazy Stored Properties)

처음부터 초기화가 필요하지 않은 경우에 지연 저장속성을 통하여 초기화를 지연시킵니다.

 

특징

  • 오직 변수(var)로만 선언 가능합니다
  • 해당 변수에 접근하는 시점에 초기화됩니다.(메모리 공간이 생기고 저장됩니다)
  • 항상 default값(초기값, 기본값)반드시 필요합니다. 표현식도 가능합니다. (ex. 함수, 클로저)

 

지연 저장 속성을 사용하는 이유

  1. 이미지와 같이 일반적으로 메모리공간을 많이 차지하는 곳에서 사용합니다.
  2. 다른 프로퍼티를 의존(이용) 해야 할 때 사용합니다.

 

선언

lazy var lazyStoredProperties: SomeType = SomeType()
struct IPhone {
    var name: String
    init(name: String) {
        self.name = name
        print("\(name)는 IPhone을 갖고 있습니다")
    }
}

class Person {
    let name: String = "Daniel"
    var age: Int = 30
    lazy var phone = IPhone(name: name)
}

let daniel = Person()

daniel.name   // "Daniel"
daniel.age    // 30
daniel.phone  // Daniel는 IPhone을 갖고 있습니다

이 코드에서 지연 저장속성의 특징을 전부 알 수 있습니다.

 

1. 지연속성을 통해 같은 클래스 내부의 다른 프로퍼티를 이용하기.

보통 Person의 객체생성하게 되면 컴파일 시 각각의 속성들은 동시에 메모리에 올라가기 때문에 내부 속성인name, age, phone은 각각 다른 속성에 접근하지 못합니다.

하지만 지연 특성을 사용하면 다른 속성에 접근이 가능해지죠. 왜냐하면 호출이 되지 않는 한 메모리가 생성되지 않기 때문입니다.

(예제 - 속성 phone에서 name으로 접근이 가능!)

 

2. 지연속성은 그 속성에 접근하지 않는 이상 메모리가 생성되지 않는다.

  • class Person에서 lazy var로 IPhone 객체생성 (메모리 생성 x)
  • danielPerson의 객체 생성 (메모리 생성 x)
  • daniel.phone을 호출 (메모리 생성 O)

즉, 지연 속성인 daniel.phone으로 접근하면 그제야 메모리가 생성되고 저장이 되죠!

 

 

아래 그림을 보면서 한 번 더 이해해 봅시다.

이와 같이 초기화가 되더라도 메모리가 없다가 그 속성으로 접근하는 순간 메모리가 생성됩니다.

 

 

계산속성(Computed Properties)

계산속성은 실질적으로는 메서드(함수)입니다.

 

특징

  • gettersetter가 있습니다.
    • getter는 무조건 구현을 해줘야 합니다.
    • setter는 생략이 가능합니다.
  • 메모리 구조가 메서드와 동일합니다 (메모리 공간을 갖지 않고 속성에 접근할 때 계산을 한 뒤, 결과를 나타냅니다)
  • 자료형 선언을 해야 합니다.(형식추론 안 돼요) 왜냐하면 메서드이기 때문에 input, return이 필요한 개념이죠.
  • 입력(input)출력(return)을 동시에 사용하는 함수를 만들려면 2개의 함수를 따로 만들어 줘야 합니다.
    하지만 계산속성은 두 가지 메서드를 한 개만으로 구현이 가능합니다 (깔끔해지죠)
  • 외부에서 보기에 속성이름으로 설정가능하므로 보다 명확해 보입니다.

 

선언

var computedProperties: Type {
    get {
        // getter
        return SomeType
    }
    set {
        // setter
    }
}

 

예제

 

예제들을 보면서 이해해 봅시다.

 

Q : 현재 몇 살인가요?

Q : 나이를 입력해 태어난 날짜 구해보기

class Person {
    
    var birth: Int = 1991
    
    var age: Int {
        get {  // getter는 반드시 구현 해야함
            return 2023 - birth
        }
        set(age) {  // setter는 생략 가능
            self.birth = 2023 - age
            
            // set의 기본 파라미터는 set(newValue)이다. 파라미터 생략 가능
            //self.birth = 2023 - newValue
        }
    }
}

var daniel: Person = Person()

// get
daniel.age        // 31

// set
daniel.age = 50
daniel.birth      // 1973

 

다음 예제는 집에 벽에 페인트를 칠해 봅시다.

 

Q : 페인트 한통당 1.5m^2의 면적을 칠할 수 있습니다.

Q : 집의 면적을 칠하려면 몇 개의 페인트 통이 필요할까요? 

Q : 5개의 페인트 통으로는 얼마만큼의 면적을 칠할 수 있을까요?

var width: Float = 1.8
var height: Float = 2.6

// 집의 면적을 칠하려면 몇개의 페인트 통이 필요할까? (get)
// 5개의 페인트 통으로는 얼마만큼의 면적을 칠할 수 있을까? (set)
var bucketsOfPaints: Int {
    get {
        let areaCoveredPerBuckets: Float = 1.5
        let area = width * height                             // 면적 4.68
        let numberOfBuckets = area / areaCoveredPerBuckets    // 3.12개의 페인트 통
        // 페인트 통을 쪼갤 순 없으니 올림을 해준다.
        let roundUpBuckets = ceilf(numberOfBuckets)        
        return Int(roundUpBuckets)
    }
    set {
        let areaCoveredPerBuckets: Float = 1.5
        let areaCanCover = areaCoveredPerBuckets * Float(newValue)
        print("\(newValue)통을 갖고 칠할 수 있는 면적 : \(areaCanCover)")
    }
}

print(bucketsOfPaints)

bucketsOfPaints = 5

// 4
// 4통을 갖고 칠할 수 있는 면적 : 7.5

 

 

타입속성(Type Properties)

타입속성을 두 가지로 분류할 수 있다.

  • 저장 타입속성(Stored Type Properties
    • 상속 시 재정의(override)가 불가합니다.
    • 항상 기본값이 필요합니다.
  • 계산 타입속성(Computed Type Properties)
    • 상속 시 재정의(override) 가능한 속성이 됩니다. 계산 타입속성에서는 static대신 class키워드를 사용해 줍니다.

 

특징

  • static 키워드 사용합니다.
  • 데이터 영역에 저장되어 모든 인스턴스(복제품)들과 공유가 가능한 프로퍼티입니다.
  • 타입속성으로 접근 시 타입자체로 접근을 해야 합니다 (Type. 타입속성)
  • 보통 모든 타입이 공통적인 값을 정의하는 데 유용하게 쓰입니다
  • 저장 타입속성은 기본적으로 지연 속성을 갖고 있습니다 (속성에 처음 접근하는 순간에 초기화시킵니다)
    (참고: 여러 스레드에서 동시에 액세스 하는 경우에도 한 번만 초기화되도록 보장됩니다. Thread-Safe)

 

선언

class Person {
    static let name: String = "Daniel"     // 저장 타입 프로퍼티
    
    static var fullName: String {          // 계산 타입 프로퍼티
        return name + " Yang"
    }
}

// 사용시 Type 자체로 접근 후 사용
Person.name          // Daniel
Person.fullName      // Daniel Yang

 

예제

 

1. 간단하게 새로운 객체 생성 시 카운트를 해보자

class Person {
    var name: String
    static var count: Int = 0  // 타입 자체에 count라는 프로퍼티가 속해 있다. 
                               // (Person.count 이런식으로 불러올 때 메모리에 생성)
    init(name: String) {
        self.name = name
        // 인스턴스 생성시 카운트 +1 (타입속성 이용)
        Person.count += 1  // 인스턴스 생성시 타입속성에 접근하기 위해서 타입에 타입속성으로 접근 Person.count
    }
 
}

// 인스턴스 생성시 카운트가 늘어난다.
var daniel = Person(name: "Daniel")
Person.count                             // 1

var catherin = Person(name: "Catherin")
Person.count                             // 2

var donald = Person(name: "Donald")
Person.count                             // 3

 

2. 계산 타입속성에서 오버라이딩(override)을 해보자

 

Person을 상속하여 Sophia를 만들어 봅시다.

 

일단 실패 예제부터 보겠습니다.

 

실패 이유

  1. 저장 타입속성은 오버라이딩이 불가능합니다
  2. 재정의 시 static 키워드 대신에 class라는 키워드를 넣어 줘야 합니다.
class Person {
    // 저장 타입속성
    // ⭐️ 저장 타입속성은 class키워드를 지원하지 않는다.
    class var name: String = "Daniel"    
    
    // 계산 타입속성      
    // ⭐️ 계산 타입속성에서 오버라이드를 하려면 static대신 class키워드를 사용해야 한다.
    static var species: String {           
        return "\(Person.name)은 남자입니다"  
    }
 
}

class Sophia: Person {
    // Error : Cannot override mutable property with read-only property 'name'
    override class var name: String {      
        return "Sophia"
    }
    // Error : Cannot override static property
    override class var species: String {
        return "\(Sophia.name)은 여자입니다"
    }
}

 

아래 코드는 에러 없이 잘되는 걸 확인했습니다.

이와 같이 계산 타입에서만 재정의가 가능합니다

class Person {
    // 계산 속성
    var name: String {
        return "Daniel"
    }
    // 계산 타입속성
    class var species: String {
        return "\(person.name)은 남자입니다"
    }
}

class Sophia: Person {
    override var name: String {
        return "Sophia"
    }
    // Error : Cannot override static property
    override class var species: String {
        return "\(sophia.name)은 여자입니다"
    }
}

let person = Person()
let sophia = Sophia()

Person.species    // Daniel은 남자입니다
Sophia.species    // Sophia은 여자입니다

 

 

속성 감시자(Property Observer)

속성 감시자의 내용은 이글에서 확인하세요

 

 

부족한 설명이지만, 조금은 이해 가셨나요?

틀린 내용이 있다면 언제든지 지적해 주시면 감사히 받겠습니다. 🫠
읽어주셔서 감사합니다 😃

 

반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!