Swift 문법 - 프로퍼티(Property)와 프로퍼티 감시자(willset, didst)

eelijus·2022년 12월 2일
0

Swift Syntax

목록 보기
7/11

💡저장 프로퍼티, 지연 저장 프로퍼티, 연산 프로퍼티, 타입 프로퍼티, 프로퍼티 감시자

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html

프로퍼티 (Property)

클래스, 구조체, 열거형과 내부에 구현. 타입과 관련된 값을 저장할 수도, 연산할 수도 있다. 열거형의 내부에는 연산 프로퍼티만 구현 가능하다.

저장 프로퍼티(Stored Property)

클래스 혹은 구조체의 인스턴스와 연관된 값을 저장하는 기본적인 프로퍼티

  • 인스턴스 저장 프로퍼티
  • 타입 저장 프로퍼티
  • 지연 저장 프로퍼티
  • var : 변수 저장 프로퍼티
  • let : 상수 저장 프로퍼티

기본값 및 초깃값 지정 가능

Struct : 저장 프로퍼티가 옵셔널이 아니어도 이니셜라이저를 자동 생성

Class : 저장 프로퍼티가 옵셔널이 아니면 기본값 지정 혹은 이니셜라이저를 통한 초기화 필수

  • 프로퍼티의 초기값이 없는 경우
struct Person {
  var name: String	//변수 저장 프로퍼티
  var age: Int			//변수 저장 프로퍼티
}

//구조체에는 저장프로퍼티를 매개변수로 하는 이니셜라이저가 내장되어있다
let sujilee: Person = Person(name: "suji", age: 29)
class Person {
  var name: String	//변수 저장 프로퍼티
  let age: Int			//상수 저장 프로퍼티
  
  //프로퍼티의 기본값(초기값)이 없으면 이니셜라이저를 정의해야함
  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
}

//사용자 정의 이니셜라이저를 사용해야 초기값을 할당할 수 있다
let sujilee: Person = Person(name: "suji", age: 29)
  • 프로퍼티의 초기값이 있는 경우
struct Coordinates {
  var x = 0	//타입 추론 저장 프로퍼티
  var y = 0	//타입 추론 저장 프로퍼티
}

//기본값이 할당돼있기 때문에 인스턴스 생성 시 전달인자로 초기값을 넘길 필요가 없다
let locationOfInit = Coordinates()

//물론 기존 이니셜라이저도 그대로 사용 가능하다
let locationOfTen: Coordinates = Coordinates(x: 10, y: 10)
struct Coordinates {
  var x = 0    //타입 추론 저장 프로퍼티
  var y = 0    //타입 추론 저장 프로퍼티
}

let locationOfInit = Coordinates()
let locationOfTen: Coordinates = Coordinates(x: 10, y: 10)

class Information {
    var location: Coordinates = Coordinates()    //변수 저장 프로퍼티
    var name: String = "UnKnown"                    //상수 저장 프로퍼티
}

//초기값을 설정된 상태기 때문에 사용자 정의 이니셜라이저를 사용할 필요 없음
let infoOfSujilee: Information = Information()
infoOfSujilee.name = "sujilee"
infoOfSujilee.location = locationOfInit

print(infoOfSujilee.name, infoOfSujilee.location)

초기값 설정이 필요없는 경우도 있다. 옵셔널 타입으로 생성된 프로퍼티의 경우.

struct Coordinates {
  var x: Int
  var y: Int
}

class Information {
  var location: Coordinates?
  let name: String
  
  init(name: String) {
    self.name = name
  }
}

//location은 옵셔널 타입이기 때문에 필수가 아님
//name은 상수이기 때문에 필수
let infoOfSujilee: Information = Information(name: "suji")

//위치를 알게 된다면 그 때 위치값 할당
infoOfSujilee.location = Coordinates(x: 7, y: 7)

지연 저장 프로퍼티 (Lazy Stored Propety)

"A lazy stored property is a property whose initial value is not calculated until the first time it is used"

지연 저장 프로퍼티를 이해하는데 좋은 예시가 있다. 바로 인스타 스토리임. 사용자가 인스타를 실행시킨 시점에서 모든 스토리를 서버로부터 가져온다면? 쓸데 없이 메모리만 차지하고 불필요한 연산이 일어날 것이다. 스토리는 사용자가 클릭했을때만 보여주면 그만이기 때문에, 앱을 실행시킨 시점이 아니라 클릭이 이루어진 순간에 해당 스토리를 서버로부터 로드하면 됨.

  • var과 함께 쓰여야함. lazy로 선언된 변수는 초기에는 값이 존재하지 않고 호출된 시점에 값이 생기는 것이기때문에 let으로는 선언될 수 없음. let 상수는 인스턴스가 완전히 생성되기 전에 초기화돼있어야하므로.
  • struct, class에서만 사용 가능
  • Computed Property에는 lazy 키워드를 사용할 수 없다. lazy var은 처음 사용될 때 메모리에 값을 올리고, 그 이후부터는 계속해서 메모리에 올라온 값만 사용함. 사용할때마다 값을 연산하여 사용하는 Computed Property에는 사용할 수 없음
  • lazy에 특별한 연산을 통해 값을 할당하기 위해서는 코드 실행 블록인 closure을 사용해야함. class나 struct의 다른 프로퍼티 값을 lazy 변수에서 사용하기 위해서는 closure내에서 self를 통해 접근해야한다. 기본적으로 일반 변수들은 클래스가 생성된 이후에 접근이 가능하기 때문에 클래스내의 다른 영역(메소드, 일반 프로퍼티)에서 self를 통해 접근하는게 불가능하지만, lazy키워드가 붙으면 생성 후 추후에 접근할 것이라는 의미이기 때문에 closure내에서 self로 접근하는게 가능하다.
import Foundation

class Person {
    var name: String
  	//lazy var에 클로저 실행의 결과를 할당한다.
    lazy var monologue: String = {
        //클로저 내부에서 self 키워드로 name에 접근
        //클래스 내부의 클로저에서 (클래스 객체를) self로 참조하면 메모리 누수 발생의 위험이 있음
        //but 뒤의 ()를 통해 그 즉시 실행 후 결과를 리턴하고 끝나버리기때문에 메모리 누수가 발생하지 않음
        return "My name is \(self.name)"
    }()
    
    init(name: String) {
        self.name = name
    }
}

var sujilee = Person(name: "Suji Lee")
//lazy var인 monologue 처음 사용
print(sujilee.monologue)


sujilee.name = "Lea Lee"
//프로퍼티가 "Lea Lee"로 변경되어도 처음 해당 프로퍼티를 사용하는 시점 메모리에 "Suji Lee"가 올라가기 때문에 "Suji Lee"로 출력된다.
print(sujilee.monologue)
My name is Suji Lee
My name is Suji Lee
import Foundation

class Person {
    var name: String
    //lazy var에 클로저 자체를 할당한다. geering: () -> String 라는 Closure.
    //이럴 경우 메모리 누수의 위험이 있기 때문에 [weak self]를 통해 누수를 방지.
    lazy var monologue: () -> String = { [weak self] in
        return "My name is \(((self?.name))!)"
    }
    
    init(name: String) {
        self.name = name
    }
}

var sujilee = Person(name: "Suji Lee")
print(sujilee.monologue())


sujilee.name = "Lea Lee"
//값이 아닌 클로저 자체가 메모리에 올라가 있고, self는 내부에서 계속 클래스를 참조하기 때문에 "Lea Lee"가 출력된다.
print(sujilee.monologue())
My name is Suji Lee
My name is Lea Lee

연산 프로퍼티

var 로만 선언 가능. 읽기 전용만 구현 가능.

  • 인스턴스 연산 프로퍼티
  • 타입 연산 프로퍼티
  • 정의와 사용
struct Student {
    //인스턴스 저장 프로퍼티
    var name: String = ""
    var `class`: String = "SWiftUI"
    var koreanAge: Int = 0
    
    //인스턴스 연산 프로퍼티
    var westernAge: Int {
        get {
            return koreanAge - 1
        }
        set(inputValue) {
            koreanAge = inputValue + 1
        }
    }
    
    //타입 저장 프로퍼티
    static var typeDescription: String = "Student"
    
    //인스턴스 메서드
    func selfIntroduceInstanceMethod() {
        print("I am \(self.name) of \(self.class)")
    }
    
    //읽기 전용 인스턴스 연산 프로퍼티
    //selfIntroduceInstanceMethod()를 대체할 수 있다.
    var selfIntroduceInstancePropety: String {
        get {
            return "I am \(self.name) of \(self.class)"
        }
    }
    
    //타입 메서드
    static func selfIntroduceTypeMethod() {
        print("I am \(self.name) of \(self.class)")
    }
    
    //읽기 전용 타입 연산 프로퍼티
    //selfIntroduceTypeMethod()를 대체할 수 있다.
    //읽기 전용에서는 `get` 생략 가능
    staic var selfIntroduceTypeProperty: String {
        return "I am \(self.name) of \(self.class)"
    }
    
    //타입 연산 프로퍼티 사용
    print(Student.selfIntroTypeProperty)
    
    var sujilee: Student = Student()
    sujilee.koreanAge = 29
    
    //인스턴스 연산 프로퍼티 사용
    print(sujilee.selfIntroduceInstanceProperty)
    
    print("my koreaAge is \(sujilee.koreanAge) while westernAge is \(sujilee.westernAge)")
    
}
  • 응용
struct Money {
    var currencyRate: Double= 1100
    var dollar: Double = 0
    var won: Double {
        get {
            return dollar * currencyRate
        }
        set {
          	//암시적 파라미터 'newValue' 사용 가능
            dollar = newValue / currencyRate
        }
    }
}

var moneyInMyPocket = Money()

moneyInMyPocket.won = 11000
print(moneyInMyPocket.won)
moneyInMyPocket.dollar = 10
print(moneyInMyPocket.won)

타입 프로퍼티

인스턴스 프로퍼티

: 특정한 구조체, 클래스에 속하는 저장 프로퍼티, 연산 프로퍼티. 해당 타입의 인스턴스가 생성되었을 때 사용할 수 있는 프로퍼티이다.

타입 프로퍼티

: 각각의 인스턴스가 아닌 타입 자체에 속하는 프로퍼티. 타입 프로퍼티의 값은 오직 하나이다.
: 인스턴스를 생성하지 않고도 사용 가능.

  • 사용

​ 타입 프로퍼티는 모든 타입이 사용할 수 있는 상수 프로퍼티 또는 글로벌 변수 프로퍼티처럼 특정 타입이 모든 인스턴스가 사용할 공통적인 값을 정의하는데 유용하다.

저장/연산 타입 프로퍼티

  • static 키워드를 사용해 정의
  • 저장 타입 프로퍼티는 열거형(enum)에서도 사용할 수 있다.
  • 저장 타입 프로퍼티는 반드시 초기값(기본값)을 설정해야 한다.
  • 자동 lazy(지연) 연산됨. 초기화 시 타입 자체에 저장 타입 프로퍼티에 값을 할당할 이니셜라이저가 없기 때문.
    • 지연 저장 프로퍼티와의 차이 : 다중 스레드 환경이어도 단 한번만 초기화되기 때문에 lazy 키워드를 사용할 필요가 없다.
  • satic 이 아닌 class 키워드를 사용해 정의한 연산 타입 프로퍼티는 해당 클래스를 상속받은 서브클래스에서 구현부를 재정의(override) 할 수 있다.
//구조체에서의 사용
struct someStructure {
    //저장 타입 프로퍼티
    static var storedTypeProperty: String = "type value"
    //연산 타입 프로퍼티
    static var computedTypeProperty: Int {
        return 1206
    }
}
//열거형에서의 사용
struct someEnumeration {
    //저장 타입 프로퍼티
    static var storedTypeProperty: String = "type value"
    //연산 타입 프로퍼티
    static var computedTypeProperty: Int {
        return 1206
    }
}
class SuperClass {
    static var storedTypeProperty: String = "type value"
    static var computedTypeProperty: Int {
        return 1206
    }
    //재정의 가능한 타입 연산 프로퍼티
    class var overridableComputedTypeProperty: Int {
        return 1206
    }
}

class SubClass: SuperClass {
    //override 키워드를 사용한 연산 타입 프로퍼티 재정의
    override static var overridableComputedTypeProperty: Int {
        return 941206
    }
}
  • 타입 프로퍼티 접근 예시
class SomeClass {
    static var storedTypeProperty: Int = 1994
    var storedInstanceProperty: Int = 0 {
        didSet {
            //Self.storedTypeProperty == SomeClass.storedTypeProperty
            Self.storedTypeProperty = storedInstanceProperty + 42
        }
    }
    
    static var computedTypeProperty: Int {
        get {
            return storedTypeProperty
        }
        set {
            storedTypeProperty = newValue
        }
    }
}

print(SomeClass.storedTypeProperty)
//OUTPUT : 1994
SomeClass.storedTypeProperty = 1206
print(SomeClass.storedTypeProperty)
//OUTPUT : 1206
print(SomeClass.computedTypeProperty)
//OUTPUT : 1206

var someInstance: SomeClass = SomeClass()
print(someInstance.storedInstanceProperty)
//OUTPUT : 0
someInstance.storedInstanceProperty = 1206

print(SomeClass.computedTypeProperty)
//OUTPUT : 1248

프로퍼티 옵저버

프로퍼티 감시자를 사용해 프로퍼티의 값이 변경될 때 원하는 동작을 수행할 수 있다. 기존 값과 동일하게 변경되어도 항상 동작함. 프로퍼티 감시자는 연산 프로퍼티에 사용할 수 없다.

  • willset 블럭 : 값이 변경되기 직전에 호출됨. 암시적 매개변수 newValue 사용 가능.
  • didset 블럭 : 값이 변경된 직후에 호출됨. 암시적 매개변수 oldValue 사용 가능.
struct Money {
  	//프로퍼티 옵저버 사용
    var currencyRate: Double = 1100 {
        willSet(newRate) {
            print("currency rate has changed from \(currencyRate) to \(newRate)")
        }
        didSet(oldRate) {
            print("current rate has changed from \(oldRate) to \(currenyRate)")
        }
    }
    
  	//프로퍼티 옵저버 사용
    var dollar: Double = 0 {
				//willSet의 암시적 파라미터(newValue) 사용
        willSet {
            print("earlier : \(dollar), later: \(newValue)")
        }
      	//didSet의 암시적 파라미터(oldValue) 사용
        didSet {
            print("earlier : \(oldValue), later: \(dollar)")
        }
    }
    
  	//연산 프로퍼티
    var won: Double {
        get {
            return dollar * currencyRate
        }
        set {
            dollar = newValue / currencyRate
        }
        
      	//연산 프로퍼티와 프로퍼티 옵저버 동시 구현 불가능
        //ERROR
        willSet {
            //code
        }
    }
}

var moneyInMyPocket: Money = Money()

//currency rate shift : 1100.0 to 1150.0
moneyInMyPocket.currencyRate = 1150

//dollar will shift : 0.0 to 10.0
moneyInMyPocket.dollar = 10

print(moneyInMyPocket.won)
//OUTPUT: 11500.0

전역 변수, 지역 변수

함수, 메서드, 클로저, 타입 등의 외부에 위치한 지역변수, 전역 변수도 저장 프로퍼티와 연산 프로퍼티의 기능을 사용할 수 있다.

var a: Int = 100
var b: Int = 200
var sum: Int {
	return a + b
}

print(sum)
struct Student {
    //인스턴스 저장 프로퍼티
    var name: String = ""
    var koreanAge: Int = 0
    var birthMonth: Int = 12
    
    //인스턴스 연산 프로퍼티
    var westernAge: Int {
        get {
            return koreanAge - 1
        }
        set(inputValue) {
            if self.birthMonth > 6 {
                koreanAge = inputValue + 1
            } else {
                koreanAge = inputValue + 2
            }
        }
    }
}


var sujilee: Student = Student()
sujilee.koreanAge = 29
//연산 프로퍼티 westernAge를 getter로 읽었을 때
print(sujilee.westernAge)
//연산 프로퍼티 westernAge를 setter로 변경했을 때
sujilee.westernAge = 27
print(sujilee.koreanAge)

var jji: Student = Student()
jji.koreanAge = 29
jji.birthMonth = 3
jji.westernAge = 27
print(jji.koreanAge)

참고 :
https://yagom.github.io/swift_basic/contents/13_property/]
https://seons-dev.tistory.com/entry/Swift-%EA%B8%B0%EC%B4%88-%EB%AC%B8%EB%B2%95-%EB%AA%A8%EC%9D%8C#%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0

profile
sujileelea

0개의 댓글