[목차]
1. 프로퍼티(Properties) 종류
2. 저장 프로퍼티(Stored Properties)
3. 지연 저장 프로퍼티(Lazy Stroed Properties)
4. 연산 프로퍼티(Computed Properties)
5. 프로퍼티 감시자(Property Observers)
6. 타입 프로퍼티(Type Properties)
Swift에서는 클래스 안에 있는 함수는 '메서드(method)'라 칭하고, 변수는 '프로퍼티(Property)'라고 부른다. Swift의 프로퍼티에는 다섯 종류가 있다.
1) 저장 프로퍼티(Stored Properties)
2) 지연 저장 프로퍼티(Lazy Stroed Properties)
3) 연산 프로퍼티(Computed Properties)
4) 프로퍼티 감시자(Property Observers)
5) 타입 프로퍼티(Type Properties)
💁🏻♂️ 이 중에 1)~4)는 모두 클래스의 인스턴스별로 개별적으로 생성되는 변수 및 속성(프로퍼티인)인 '인스턴스 프로퍼티'에 해당된다. 5)는 클래스의 인스턴스와 별개로 한번 생성되어 인스턴스들끼리 공유 및 공통으로 Type으로 사용하게 되는 '타입 프로퍼티'이다.
'6. 타입 프로퍼티(Type Properties)' 목차에서 인스턴스 프로퍼티에 대한 특징과 비교하여 타입 프로퍼티에 대해 자세히 설명하겠다.
- 저장 프로퍼티는 '클래스' 또는 '구조체' 내부에 선언되는 변수들로 가장 기본적인 프로퍼티이다.
- 특별한 키워드가 붙거나 연산 프로퍼티가 아니라면, 대부분 클래스나 구조체에 선언되는 변수들은 저장 프로퍼티(Stored Properties)이다.
구조체는 저장 프로퍼티가 옵셔널이 아니고 이니셜라이저를 사용자가 따로 정의하지 않아도, 기본적으로 저장 프로퍼티를 매개변수로 가지는 이니셜라이저가 자동으로 생성된다.
구조체와 달리 클래스의 저장 프로퍼티는 옵셔널이 아니라면, 저장 프로퍼티를 매개변수로 하는 이니셜라이가 자동으로 생성되지 않는다.
따라서 init 키워드를 통해 '사용자 정의 이니셜라이저'(기본 생성자가 아닌 사용자가 추가적으로 정의한 이니셜라이저)를 반드시 정의하고 인스턴스 생성시 호출하여 초기화 해줘야 한다.
물론 클래스 안에서 저장 프로퍼티를 선언과 동시에 초기화하 해준다면 굳이 사용자 정의 이니셜라이저를 정의하지 않아도 된다.
- '지연 저장 프로퍼티(Lazy Stored Properties)'는 객체 생성시에 바로 생성되는 프로퍼티가 아니다.
- 호출이 있어야 생성되고 값이 초기화되기 때문에 지연을 가지고 저장이 된다고 하여 '지연 저장 프로퍼티', 'Lazy Stored Properties'라고 한다.
let mike: Employee = Employee(name: "Peter")
Employee 클래스의 생성자를 통해 mike라는 객체가 생성됬지만, 아직 EmployeeInfo의 객체인 basicInfo 프로퍼티는 mike에 생성되지 않은 상태이다.
print(mike.basicInfo)
이 코드를 통해 basicInfo 프로퍼티에 접근하면, EmployeeInfo의 객체인 basicInfo 프로퍼티가 생성된다.
EmployeeInfo의 객체인 basicInfo 프로퍼티에 lazy가 없다면 Employee 클래스의 객체가 생성됨과 동시에 EmployeeInfo의 객체도 생성된다.
- '연산 프로퍼티(Computed Properties)'는 클래스, 구조체, 열거형에서
다른 '저장 프로퍼티'의 값을 읽거나(getter) 쓰는(setter) 연산을 실행하는 변수이다.- 따라서 항상 var로 선언되어야 한다.
- 저장 프로퍼티와 달리 저장 공간을 갖지 않는다는 특징이 있다.
'연산 프로퍼티'라는 말이 다소 생소하게 느껴지고 "변수가 연산을 어떻게 하는거지..?" 라는 생각이 든다. 간략하게 설명하자면 Java에 있는 getter와 setter 기능을 변수 하나에서 작동하게 하여 코드를 보기 쉽게 명료화한 것이다.
연산프로퍼티의 선언 방법은 변수이지만 메서드처럼 연산 프로퍼티 뒤에 { } 를 붙여야 한다. 그리고 { } 안에 get 연산과 set 연산을 작성하면 된다.
아래 코드 예시에서는 basicInfo가 바로 "연산 프로퍼티"이다.
set의 파라미터는 원하는 이름으로 적으면 되지만, 생략시 'newValue'(이미 swift에 정의된 변수명)로 받으면 된다.
아래 코드에서 연산프로퍼티 basicInfo의 값을 읽어오는 시도를 함으로써 basicInfo의 get이 작동하게 된다.
print(newEmployee.basicInfo)
연산프로퍼티 basicInfo의 값을 초기화하는 세팅을 함으로써 basicInfo의 set이 호출된다.
newEmployee.basicInfo = "R&D"
//연산 프로퍼티
var basicInfo: String{
get{
return self.name
}
set{
self.dept = newValue
}
}
- '프로퍼티 감시자(Property Observers)'는 상태값이 모니터링될 수 있는 프로퍼티이다.
- 프로퍼티 감시자는 프로퍼티의 값이 변경됨에 따라 적절한 액션을 취하기 위해 사용한다.
- 프로퍼티 감시자에는 두 메서드가 존재한다.
1) willSet : 프로퍼티의 값이 변경되기 직전에 호출된다.
2) didSet : 프로퍼티의 값이 변경된 직후에 호출된다.
연산 프로퍼티처럼 변수를 선언 후 매소드처럼 뒤에 { }를 열고, willSet과 didSet 메서드를 선언하면 해당 감시자 프로퍼티의 상태값이 변경될 때마다 willSet 또는 didSet 메서드가 호출된다.
willSet, didSet의 메서드는 매개변수가 하나씩 있는데 매개변수를 따로 지정하지 않으면 willSet메서드에는 newValue, didSet메서드에는 oldValue가 매개변수로 전달된다. (연산프로퍼티의 set 메서드를 작성할 때도 매개변수를 따로 지정하지 않으면 newValue로 설정된다.)
willSet메서드에서 전달되는 전달인자(newValue)는 프로퍼티가 변경될 값이고, didSet메서드에서 전달되는 전달인자(oldValue)는 프로퍼티가 변경되기 전의 값이다.
타입 프로퍼티(Type Properties)는 '메모리를 사용하는 방식', '사용 목적', '사용 방식(문법)'이 인스턴스 프로퍼티와 다르다. 따라서 프로퍼티를 '인스턴스 프로퍼티'와 비교하여 알아보도록 하겠다.
(아래 그림은 타입프로퍼티 중 static을 에시로 든 코드이다.)
- 모든 인스턴스가 공통으로 사용하는 값이 필요할 경우
- 모든 인스턴스에서 값을 변경할 수 있는 변수가 필요할 경우
- 저장 타입 프로퍼티 : 저장 프로퍼티에 타입 프로퍼티(static 키워드)를 적용한 것이다.
- 연산 타입 프로퍼티 : 연산 프로퍼티에 타입 프로퍼티(static, class, final class 키워드)를 붙인 것이다.
- static : 저장 프로퍼티 사용 o, 연산 프로퍼티에 사용 o
- class : 저장 프로퍼티 사용 x, 연산 프로퍼티에 사용 o
- class final : 저장 프로퍼티 사용 x, 연산 프로퍼티에 사용 o
- 'static' 키워드를 연산프로퍼티에 붙이면 자식클래스에서 오버라이딩 불가능하다.
- '저장 프로퍼티'와 '연산 프로퍼티' 모두에 적용 가능하다.
- 기본적으로 지연 저장되지만, 상수와 변수에 모두 사용 가능하다.
연산 프로퍼티에 static 키워드를 붙여 '연산 타입 프로퍼티'인 basicInfo를 super class인 Employee에 선언하였다.
sub class인 Boss에서 basicInfo를 오버라이딩(재정의)하려고 하니 "Cannot override static property"라고 static은 오버라이딩 불가능하다는 에러 메시지가 발생한다.
- 이 포스팅은 프로퍼티에 대해서만 다루고 있기는 하지만, class 키워드는 프로퍼티(타입 프로퍼티라고 부름) 뿐만 아니라 메서드에도(타입 메서드라고 부름) 붙일 수 있다.
- class는 '저장 프로퍼티'에는 사용 불가능하고, '연산 프로퍼티'에만 적용 가능하다. 즉, 연산타입프로퍼티만 존재할 수 있다.
- 'class' 키워드를 연산프로퍼티와 메서드에 붙이면 자식 클래스에서 오버라이딩이 가능하다.
위에서 발생한 에러를 class 키워드를 사용하여 고쳐보았다.
super class의 연산 프로퍼티 basicInfo의 타입 프로퍼티 키워드를 static에서 class로 바꾸니 오버라이딩 불가능하다는 에러 메시지가 사라졌다.
슈퍼클래스 Employee의 저장 프로퍼티인 age에 class 키워드를 붙이고, 서브클래스 Boss에서 오버라이딩을 시도하였다.
그 결과 저장프로퍼티는 class를 지원하지 않으며, 오버라이딩 불가능하다는 두개의 에러 메시지가 떴다.
따라서 타입 프로퍼티 class는 저장 프로퍼티에는 사용 불가능하고, 연산 프로퍼티에만 사용 가능하다.
- 슈퍼 클래스(부모 클래스)에 class로 선언된 연산 프로퍼티 또는 메서드를 서브 클래스(자식 클래스)에서 'final class' 키워드를 선언하면 오버라이딩아 가능하다.
- 단, override final class를 한번 선언해버리면 그 이후 자식클래스(최초의 슈퍼 클래스를 상속한 서브클래스를 또다시 상속하는 서블클래스)애서는 오버라이드가 불가능하다.
- 그래서 '최종', '마지막'이라는 의미에서 class 앞에 final이 붙은 것 같다.
- class와 동일하게 '저장 프로퍼티'에는 사용 불가능하고, '연산 프로퍼티'에만 적용 가능하다.
아래 예시를 살펴보자.
슈퍼클래스인 Employee에 연산 타입 프로퍼티 basicInfo를 서브클래스에서 오버라이딩이 가능하도록 타입 프로퍼티로 선언하였다.
그리고 Employee를 직접 상속받는 James, Mark에서는 class 앞에 final을 선언하여 '마지막으로', '최종으로' 오버라이딩 시도하겠다고 선언하였다.
이후 Peter는 Employee를 한번 상속한 Mark를 상속하여 타입연산프로퍼티인 basicInfo를 오버라이딩 시도하였지만, "Class property overrides a 'final' class property" 라는 에러메시지가 발생하였다. 파이널 클래스의 프로퍼티를 오버라이딩 시도하여 에러가 발생한 것이다.
위의 코드의 상속 구조를 그림으로 나타나면 위와 같다.
Mark에서 한번 final class를 선언한 타입프로퍼티 또는 타입 메서드는 이후 상속받는 Peter에서 오버라이딩이 불가능하다.
🤔 처음에 final class 이해를 잘못하였다. "추가적으로 상속시 오버라이딩 불가능하다"고 하여 Employee를 상속받는 Mark는 앞서 James가 이미 상속받아 class 타입 프로퍼티인 Employee의 basicInfo를 오버라이딩하였기 때문에 더이상 오버라이딩이 불가능한 줄 알았다. 하지만 오버라이딩시 상속 한 단계까지는 서브 클래스들끼리 오버라이딩 가능하지만, 두 단계 상속 구조부터는 오버라이딩 불가능한 것으로 이해해야 한다.