iOS

[Swift] 프로퍼티(Property)- 프로퍼티의 정의, 종류

D0HAN. 2022. 8. 9. 22:05

증말 기본적인 개념인데도 그냥 대애충 두루뭉술하게만 알고 넘어갔었더라고요 🥲

막상 구체적으로 문제가 나오니까 확실히 아는게 없는 것 같아서 

이번 기회에 한 번 정리해봅니다 ><

 

 

 

목차

     

     

    Properties associate values with a particular class&#44; structure&#44; or enumeration.
    프로퍼티란, 클래스나 구조체 또는 열거형에 값을 할당합니다.

    프로퍼티란 어떤 대상(클래스,구조체,열거형etc.)에 값을 할당해주는 존재입니다.

    프로퍼티는 크게 저장 프로퍼티,  연산  프로퍼티로 나눌 수 있습니다!

     

     

    저장 프로퍼티 (Stored Properties)

    Stored Properties

    ► 이름에서 알 수 있듯 가장 단순하고 기본적인 개념의 프로퍼티로, 클래스 또는 구조체의 인스턴스와 연관된 값을 저장하는 프로퍼티

    ► 클래스 / 구조체에서만 사용 가능!! (열거형x)

     

    - 변수 저장 프로퍼티 (variable stored properties) - var 키워드 사용

    - 상수 저장 프로퍼티 (constant stored properties) - let 키워드 사용

     

    struct Point {
      var x: Int  // 변수 저장 프로퍼티
      let y: Int  // 상수 저장 프로퍼티 
    }

    정의에서 봤듯이 어떤 대상 - 이경우에는 Point  라는 구조체에 x,y라는 값을 할당해주는 애들이 프로퍼티  입니다.

     

     

    Lazy Stored Properties

    ► lazy라는 키워드를 사용하는 지연 저장 프로퍼티는 호출이 있어야 값을 초기화 합니다.

         그 말은, 값이 처음으로 사용 되기 전에는 계산되지 않는다는 말이죠!

    ► 지연 저장 프로퍼티는 반드시! var로 선언해야 합니다.  let 선언은 초기화가 되기 전에 항상 값을 갖는 프로퍼티이기 때문!!!

    ► 어떨 때 유용할까? 프로퍼티가 특정 요소에 의존적이어서, 그 요소가 끝나기 전에 값을 알 수 없는 경우.

         혹은 복잡한 계산, 부하가 많이 걸리는 작업을 lazy로 선언하면 인스턴스 초기화 시점에 복잡한 계산을 피할 수 있읍니당.

    ► 지연 프로퍼티가 여러 스레드에서 사용되면? 한번만 실행된다는 보장이 x. Thread safe 하지 않음!!

    ► 단일 스레드에서 사용될 경우 초기화는 한 번!

     

    class DataImporter {
        /*
            겁 나 오 래 걸 리 는 초기화 작업
        */
         
        var filename = "data.txt"
    }
    
    class DataManager {
        lazy var importer = DataImporter()
        var data = [String]()
        /*
           데이터를 관리하는 기능
        */
    }
    
    let manager = DataManager()
    manager.data.append("Some data")
    manager.data.append("Some more data")
    // DataImporter 인스턴스는 이 시점에 생성돼 있지 x
    
    //이렇게 직접 importer 인스턴스에 접근할때야 생성됨
    print(manager.importer.filename)

     

     

     

    연산 프로퍼티 (Computed Properties)

    클래스, 구조체, 열거형은 저장 프로퍼티 뿐만 아니라 연산 프로퍼티도 선언할 수 있습니다.

     

    ► 저장 프로퍼티와 다르게 실제 값을 저장하고 있는 것이 아니라, 다른 프로퍼티와 간접적으로 값을 검색하고 세팅합니다

        그게 바로 getter  setter(optional)라는 연산 프로퍼티!

     

    ► 계산값에 따라 값이 변할 수 있는 프로퍼티이기 때문에 항상 var 으로 선언!!!

     

    ►set(newValue) { ... } 와 같은 구조에서 (newValue)라고 인자이름을 지정하지 않아도 기본 이름인 newValue를 사용할 수 있습니다!

     like this

    set {
                origin.x = newValue.x - (size.width / 2)
                origin.y = newValue.y - (size.height / 2)
            }

     

    ► 예제 1

    class P {
    	var x: Int {
        		get { return x }
            	set(newValue) { x = newValue * 2 }
        }
    }
    
    var p = Point()
    p.x = 12 		// error!

    이렇게 하면 에러가 납니다!!!!!

    x값이 자기 자신을 리턴하고 자기 자신을 두배로 만들고 있기 때문에 이렇게 사용하면 오류가 납니다.

    그래서 getter setter에는 필수적으로 변수가 더 있어야하는데, 이처럼 내부에서 사용하는 변수용으로 사용하는 stored Property는 언더바를 붙인 형태로 구분합니다. 

    class Test {
    
        private var _myProperty: Int
        
        var myProperty: Int {
            get {
                return _myProperty
            }
            set {
                return _myProperty = newValue
            }
        }
    
    }

    ► 예제2

    struct Point {
        var x = 0.0, y = 0.0
    }
    struct Size {
        var width = 0.0, height = 0.0
    }
    
    
    struct Rect {
        var origin = Point()
        var size = Size()
        var center: Point {
            get {
                let centerX = origin.x + (size.width / 2)
                let centerY = origin.y + (size.height / 2)
                return Point(x: centerX, y: centerY)
            }
            set {
                origin.x = newValue.x - (size.width / 2)
                origin.y = newValue.y - (size.height / 2)
            }
        }
    }

    center라는 Point형 변수에 접근할 때는 origin, size라는 다른 프로퍼티를 이용하여 계산하고, center를 직접 설정할 때는 size를 이용해서 origin을 변경하고 있습니다. 

    var square = Rect(origin: Point(x: 0.0, y: 0.0),
                      size: Size(width: 10.0, height: 10.0))
                      
    let initialSquareCenter = square.center	//getter로 square의 center값 가져옴
    square.center = Point(x: 15.0, y: 15.0) //setter로 center값 변경
    
    
    print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
    // "square.origin is now at (10.0, 10.0)" 출력

     

     

    프로퍼티 옵저버 (Property Observers)

    ► 프로퍼티에 새 값이 설정(set)될 때마다 이벤트를 감지할 수 있습니다. 

    ► 지연 저장 프로퍼티(lazy stored properties)에서는 사용할 수 없음!

    ► 연산 프로퍼티는 setter에서 값의 변화를 감지하므로 따로 옵저버를 정의할 필요가 없음!

    ► willSet : 값이 저장되기 바로 직전에 호출 (연산프로퍼티의 set에서와 마찬가지로 기본 값으로 newValue라는 파라미터명 사용)
         didSet : 값이 저장되고 난 직후에 호출 (oldValue라는 파라미터 사용)

     

    ► 예제

    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 = 200
    // About to set totalSteps to 200
    // Added 200 steps
    stepCounter.totalSteps = 360
    // About to set totalSteps to 360
    // Added 160 steps
    stepCounter.totalSteps = 896
    // About to set totalSteps to 896
    // Added 536 steps