프로퍼티 get, set, didSet, willSet in iOS

Swift의 get, set, didSet, willSet을 활용 하는법

Young Kim
스위프트 프로그래밍
8 min readApr 7, 2016

--

Swift에서는 프로퍼티에 get, set, didSet, willSet을 사용할 수 있습니다. 정확히는 프로퍼티에 get, set을 사용하거나 didSet, willSet을 사용 할 수 있습니다. 즉 get, set이 세트이고 didSet, willSet이 세트입니다. 하나의 프로퍼티에 get, set, didSet, willSet을 모두 함께 사용하지는 못합니다.

get, set in Swift

Swift에서 get, set은 getter와 setter와 비슷합니다. 다만 해당 프로퍼티에 직접 붙히는 방식이라는게 조금 특이합니다. get, set의 기본 syntax는 아래와 같습니다.

var myProperty: Int {
get {
return myProperty
}
set(newVal) {
myProperty = newVal
}
}
print(myProperty) // myProperty 출력
myProperty = 123 // newVal값은 123

그런데 위와 같이 작성하면 Xcode가 경고를 줍니다. get과 set은 해당 프로퍼티에 직접 붙어있기 때문에 위와 같이 get{}, set{}에서 myProperty에 접근하면 recursive하게 자기 자신의 get, set을 호출하게 되므로 이렇게 사용하면 안됩니다. 일반적으로 get, set은 아래처럼 실제 값을 저장 할 backing storage가 필요합니다.

var _myProperty: Int
var myProperty: Int {
get {
return _myProperty
}
set(newVal) {
_myProperty = newVal
}
}

_myProperty는 실제로 값이 저장되는 변수입니다. 외부에서 myProperty의 값에 접근하거나 새로운 값을 할당하면 실제로 값이 저장되는 곳은 myProperty가 아닌 _myProperty입니다. 즉 myProperty는 _myProperty의 interface역할을 합니다. 이쯤 되면 조금 궁금해집니다. 그럼 get, set을 어떻게 활용 할 수 있을까요? get, set을 사용하는 경우는 제가 아는것에 한하여 아래와 같습니다.

  1. 프로퍼티에 값이 할당 될 때 적절한 값인지 검증하기 위해
  2. 다른 프로퍼티값에 의존적인 프로퍼티를 관리 할 때
  3. 프로퍼티를 private하게 사용하기 위해

첫 번째 경우는 다음과 같은 경우 입니다. Company 클래스가 있고 그 안에 직원수를 나타내는 members 프로퍼티가 있습니다. 직원 수를 나타내는 members 변수는 Int 타입으로 선언 할 수 있습니다. 그런데 직원 수가 음수가 될 수는 없으니 members 값을 바꿀 때는 검증 할 필요가 있습니다.

class Company {
var _members:Int = 5
var members:Int {
get {
return _members
}
set (newVal) {
if (newVal < 1){
print(“직원수는 한명보다 작을 수 없습니다.”)
}else{
_members = newVal
}
}
}
}

set{}의 newVal 값을 사용하여 프로퍼티의 값이 바뀌기전에 validation 할 수 있습니다.
다음으로 두번째 경우는 다른 프로퍼티에 의존적인 프로퍼티에 get, set을 활용하는 것입니다. 이 경우에도 Company 클래스로 예를 들어볼 수 있습니다.

class Company {
var _members:Int = 5
var members:Int {
get {
return _members
}
set (newVal) {
if (newVal < 1){
print(“직원수는 한명보다 작을 수 없습니다.”)
}else{
_members = newVal
}
}
}
var teamDinnerCost:Int {
get {
return _members * 100000
}
}
}

회식비를 의미하는 teamDinnerCost 프로퍼티가 추가 됬습니다. 회식비는 직원 수에 비례하기 때문에 _members 프로퍼티에 의존적 입니다. teamDinnerCost 프로퍼티에 get을 사용하면 직원수에 비례하여 계산된 회식비를 반환하도록 할 수 있습니다. 이렇게 할 경우 teamDinnerCost의 값을 따로 신경쓰지 않아도 됩니다. 또한 get{}만을 구현했으므로 외부에서 teamDinnerCost의 값을 변경하려고 하면 에러가 발생합니다. 따라서 회식비는 직원수에 비례하여 정해진 값 만을 반환합니다.
마지막으로 프로퍼티를 private하게 사용하기 위해 get, set을 쓸 수 있습니다. 이 때는 직접적으로 get, set을 쓰지는 않습니다. 설명에 앞서 Swift는 3가지 접근 타입을 제공합니다.

private : 해당 프로퍼티가 선언된 파일내에서만 접근 할 수 있습니다.
internal : 해당 프로퍼티가 선언된 파일과 같은 모듈내의 다른 파일에서도 접근 할 수 있습니다.
public : 해당 프로퍼티가 선언된 모듈을 import한다면 어디서든 접근 할 수 있습니다.

다시 프로퍼티를 private하게 만드는 방법을 보면 아래와 같이 할 수 있습니다.

private(set) var myProperty: Int = 10

이 경우 myProperty는 internal getter와 private setter를 얻게됩니다. 즉 myProperty가 선언된 파일내에서만 값을 수정 할 수 있고 외부에서는 값을 수정 할 수 없습니다. 반면 myProperty의 값을 얻는것은 모듈내에서는 어디서든 할 수 있습니다. internal getter가 아닌 public getter로 만드는 방법도 있습니다.

public private(set) var myProperty: Int = 10

위와 같이 선언 할 경우 myProperty의 값은 어디서든 접근 할 수 있습니다. 다만 이렇게 선언할 경우 class를 public 클래스로 선언 해주어야 합니다.

didSet, willSet in Swift

스위프트는 프로퍼티 옵저버로 didSet, willSet을 제공합니다. 얘네들의 역할은 프로퍼티의 값이 변경되기 직전, 직후를 감지하는 것입니다. 따라서 이때 원하는 작업을 수행 할 수 있습니다. 기본적인 syntax는 다음과 같습니다.

var myProperty: Int = 10{
didSet(oldVal){
//myProperty의 값이 변경된 직후에 호출, oldVal은 변경 전 myProperty의 값
}
willSet(newVal){
//myProperty의 값이 변경되기 직전에 호출, newVal은 변경 될 새로운 값
}
}

참고로 프로퍼티 옵저버를 사용하기 위해서는 프로퍼티의 값이 반드시 초기화 되어 있어야합니다. 내부적으로 초기화된 프로퍼티의 값을 감시하기 때문이지 않을까 싶습니다. 또한 클래스의 init()안에서 값을 할당 할 때는 didSet, willSet은 호출 되지않습니다. 초기화 이후부터 프로퍼티를 감시합니다. 위와 같은 이유일 것 같습니다.
이제 didSet과 willSet의 활용을 알아보겠습니다. 프로퍼티 옵저버의 가장 빈번한 사용은 Model에서 갱신된 값을 View에 보여줄 때 입니다. (MVC 패턴의 이해)
예를 들면 View에 점수를 표시하는 Label이 있다고 가정하고, 점수가 바뀔때마다 View의 Label을 업데이트하고 싶습니다. 이 경우에 점수를 저장하고 있는 변수인 score의 값을 바꾸어주고 화면을 갱신하는 작업을 아래처럼 할 수 있습니다.

score = 85
scoreLabel.text = "Score: \(score)"

이렇게 해도 문제는 없습니다. View의 Label은 정상적으로 바뀔 것입니다. 그러나 만약 여러곳에서 score의 값을 바꾼다면 어떻게 해야 할까요? score의 값이 바뀌는 곳마다 scoreLabel.text = “Score: \(score)” 를 적어주어야 할 것입니다. 만약 실수로 빼먹은 곳이 생긴다면 해당 점수는 View에 반영되지 않을것입니다. 좋지 않은 방법입니다. 이럴 때 프로퍼티 옵저버를 사용 할 수 있습니다. 프로퍼티 옵저버를 사용하면 아래와 같이 할 수 있습니다.

var score: Int = 0 {
didSet {
scoreLabel.text = "Score: \(score)"
}
}

이렇게 하면 score값이 바뀔 때마다 View의 값을 갱신하는 작업을 따로 해줄 필요가 없습니다.
이 외에도 프로퍼티 옵저버를 사용하여 현재 값과 바뀔 값을 비교하는 작업을 할 수도 있습니다.

var score: Int = 0 {
didSet(oldVal) {
print("현재 점수는: \(self.score), 이전 점수는: \(oldVal)")
}
}

설명하다 보니까 didSet만 사용했는데 willSet도 비슷하게 활용할 수 있습니다.
Swift에서 제공하는 get, set, didSet, willSet을 잘 활용하면 좀더 편하게 코딩을 할 수 있습니다. 이상으로 get, set, didSet, willSet에 대한 포스팅을 마치겠습니다.

--

--

Young Kim
스위프트 프로그래밍

Startup, 운동, 영화 그리고 프로그래밍에 관심이 많은 학생입니다.