참조
객체지향, 함수형, 프로토콜 지향 패러다임까지 한 번에!
야곰 / 한빛미디어
프로퍼티(Property)에 대해 알아 보겠습니다.
1. 프로퍼티
프로퍼티는 크게 5가지가 존재합니다.
- 저장 프로퍼티(Stored Properties)
- 지연 저장 프로퍼티(Lazy Stroed Properties)
- 연산 프로퍼티(Computed Properties)
- 프로퍼티 감시자(Property Observers)
- 타입 프로퍼티(Type Properties)
1.1 저장 프로퍼티(Stored Properties)
가장 단순한 개념의 프로퍼티로써 클래스 또는 구조체의 인스턴스와 연관된 값을 저장하는 프로퍼티입니다.
구조체의 저장 프로퍼티가 옵셔널이 아니더라도, 구조체는 저장 프로퍼티를 모두 포함하는 이니셜라이저를 자동으로 생성합니다.
하지만 클래스의 저장 프로퍼티는 옵셔널이 아니라면 프로퍼티 기본값을 지정해주거나 사용자정의 이니셜라이저를 통해 반드시 초기화해주어야 합니다.
struct CoordinatePoint {
var x: Int // 저장 프로퍼티
var y: Int // 저장 프로퍼티
}
//구초제는 기본적으로 저장 프로퍼티를 매개변수로 가지는 이니셜라이져가 있습니다.
let point: CoordinatePoint = CoordinatePoint(x: 10, y: 5)
class Position {
var point: CoordinatePoint // 저장 프로퍼티(변수)
let name: String // 저장 프로퍼티(상수)
init(name: String, currentPoint: CoordinatePoint) {
self.name = name
self.point = currentPoint
}
}
//사용자정의 이니셜라이저를 호출해야만 한다.
//그렇지 않으면 프로퍼티 초깃값을 할당할 수 없기 때문에 인스턴스 생성이 불가능 하다.
let position: Position = Position(name: "김승진", currentPoint: point)
1.2 지연 저장 프로퍼티(Lazy Stroed Properties)
키워드로는 lazy를 사용하고 지연 저장 프로퍼티는 호출이 있어야 값을 초기화하게 됩니다.
상수는 인스턴스가 완전히 생성되기 전에 초기화 되어야하므로 필요할 때 값을 할당하는 지연 저장 프로퍼티에는 맞지 않습니다.
따라서 var 키워드를 사용하여 변수로 정의합니다.
지연 저장 프로퍼티는 주로 복잡한 클래스나 구조체를 구현할 때 많이 사용됩니다. 클래스 인스턴스의 저장 프로퍼티로 다른 클래스 인스턴스나 구조체 인스턴스가 할당 되어야 할 때가 있습니다.
예를 들어 인스턴스를 초기화하면서 저장 프로퍼티로 쓰이는 인스턴스들이 한 번에 생성되거나 모든 저장 프로퍼티를 사용할 필요가 없다면 그때 지연 저장 프로퍼티를 사용하면됩니다. 즉, 지연 저장 프로퍼티를 잘 사용하면 불필요한 성능 저하나 공간 낭비를 줄일 수 있습니다.
struct CoordinatePoint {
var x: Int = 0
var y: Int = 0
}
class Position {
lazy var point: CoordinatePoint = CoordinatePoint()
let name: String
init(name: String) {
self.name = name
}
}
//Position이라는 객체를 생성됬지만 아직 CoordinatePoint는 객체에 생성되지 않은 상태.
let position: Position = Position(name: "김승진")
//이 코드를 통해 point프로퍼티로 처음 접근할 때 point 프로퍼티의 CoordinatePoint가 생성됩니다.
print(position.point)
point라는 CoordinatePoint객체에 lazy가 없다면 Position이라는 객체가 생성됨과 동시에 CoordinatePoint도 생성 됩니다.
1.3 연산 프로퍼티(Computed Properties)
연산 프로퍼티는 실제 값을 저장하는 프로퍼티가 아니라, 특정 상태에 따른 값을 연산하는 프로퍼티입니다.
인스턴스 내/외부의 값을 연산하여 적절한 값을 돌려주는 getter의 역할이나 은닉화된 내부의 프로퍼티 값을 간접적으로 설정하는 setter의 역할을 수행할 수도 있습니다. 연산 프로퍼티는 클래스와 구조체 열거형에 모두 추가 가능합니다.
굳이 메서드를 두고 왜 연산프로퍼티를 쓸까요? 그 이유에 대해 생각을 해보자면 인스턴스 외부에서 메서드를 통해 인스턴스 내부 값에 접근 하려면 메서드를 두개(getter, setter)를 구현해야합니다.
(기존에 다른 언어에 대해 익숙하신 분들은 getter, setter에 대한 메서드를 따로 만드셨을 겁니다.)
이 부분에 대해서 가독성이 떨어진다고 생각이 들어서 프로퍼티가 메서드 형식보다 훨씬 더 간편하고 직관적이여서 생겨난 부분 같습니다.
다만, 주의해야될 점은 연산 프로퍼티는 접근자인 get메서드처럼 읽기 전용을 구현하기 쉽지만, 쓰기 전용은 구현할 수 없다는 단점이 있습니다.(메서드로는 가능하지만 프로퍼티로는 불가능)
struct CoordinatePoint {
var x: Int
var y: Int
// 대칭점을 구하는 메서드 - 접근자(getter)
func oppsitePoint() -> CoordinatePoint {
return CoordinatePoint(x: -x, y: -y)
}
mutating func setOppositePoint(_ opposite: CoordinatePoint) {
x = -opposite.x
y = -opposite.y
}
}
var position: CoordinatePoint = CoordinatePoint(x: 10, y: 20)
//현재 좌표
print(position)
//대칭 좌표
print(position.oppsitePoint())
//대칭 좌표를 (15, 10)으로 설정
position.setOppositePoint(CoordinatePoint(x: 15, y: 10))
print(position) // -15, -10
위의 코드는 oppsitePoint() 메서드로 대칭점을 구할 수 있으며 setOppsitePoint() 메서드로 대칭점을 설정 해줘야 하는 코드 입니다.
이 코드에서 접근자와 설정자 이름의 일관성을 유지하기도 힘들며 일관되지 않아 코드를 한 번에 읽기가 쉽지 않다.
하지만 연산 프로퍼티를 사용하면 이 두 메서드를 좀 더 간결하고 확실하게 표현할 수 있습니다.
Setter의 매개변수로 원하는 이름을 소괄호 안에 명시해주면 set메서드 내부에서 전달받은 전달 인자를 사용할 수 있습니다. 관용적인 표현으로 newValue로 매개변수 이름을 대신할 수 있습니다.
struct CoordinatePoint {
var x: Int
var y: Int
var oppsitionPoint: CoordinatePoint {
get {
return CoordinatePoint(x: 10, y: 20)
}
// set {
// x = -newValue.x
// y = -newValue.y
// }
set (someParam) {
x = -someParam.x
y = -someParam.y
}
}
}
읽기 전용으로 연산 프로퍼티를 사용 (get 생략 가능)
struct CoordinatePoint {
var x: Int
var y: Int
var oppositePoint: CoordinatePoint {
get{
return CoordinatePoint(x: 10, y: 20)
}
}
//또는 get을 생략하여 바로 사용할 수 있다.
var oppositePoint: CoordinatePoint {
return CoordinatePoint(x: 10, y: 20)
}
}
1.4 프로퍼티 감시자(Property Observers)
감시자 프로퍼티를 사용하면 프로퍼티의 값이 변경됨에 따라 적절한 액션을 취할 수 있습니다. 프로퍼티의 값이 새로 할당될 때마다 호출되고 변경되는 값이 현재의 값과 같더라도 호출됩니다.
프로퍼티 감시자는 지연 저장 프로퍼티에는 사용할 수 없으며, 오로지 저장 프로프티에서만 적용할 수 있습니다.
프로퍼티 감시자에는 프로퍼티의 값이 변경되지 직전에 호출되는 willSet메서드와 프로퍼티의 값이 변경된 직후에 호출되는 didSet메서드가 있습니다. willSet메서드에서 전달되는 전달인자는 프로퍼티가 변경될 값이고, didSet메서드에서 전달되는 프로퍼티의 값은 변경되기 전의 값입니다. willSet, didSet의 메서드는 매개변수가 하나씩 있는데 매개변수를 따로 지정하지 않으면 willSet메서드에는 newValue, didSet메서드에는 oldValue가 매개변수로 자동으로 저장됩니다.
class Account {
var credit: Int = 0 {
willSet {
print("잔액이 \(credit)에서 \(newValue)원으로 변경될 예정입니다.")
}
didSet {
print("잔액이 \(oldValue)에서 \(credit)으로 변경되었습니다.")
}
}
}
let myAccount: Account = Account()
myAccount.credit = 1000
//잔액이 0에서 1000원으로 변경될 예정입니다.
//잔액이 0에서 1000으로 변경되었습니다.
1.5 타입 프로퍼티(Type Properties)
각각의 인스턴스가 아닌 타입 자체에 속하게 되는 프로퍼티를 타입 프로퍼티라고 합니다
이제까지 알아본 프로퍼티 개념은 모두 타입을 정의하고 그 타입의 인스턴스가 생성되었을때 사용될 수 있는 인스턴스 프로퍼티 입니다. 인스턴스 프로퍼티는 인스턴스를 새로 생성할 때 마다 초깃값에 해당하는 값이 프로퍼티의 값이 되고, 각 인스턴스마다 다른 값을 지닐 수 있습니다.
타입 프로퍼티는 타입 자체에 영향을 미치는 프로퍼티이고 인스턴스의 생성 여부와 상관없이 타입 프로퍼티의 값은 하나입니다.
타입프로퍼티는 그 타입의 모든 인스턴스가 공통으로 사용되는 값(C 언어의 static constant와 유사)이라는지, 모든 인스턴스에서 공용으로 접근하고 값을 변경할 수 있는 변수(C 언어의 static 변수와 유사)등을 정의할 때 유용합니다.
class Aclass {
static var typeProperty: Int = 0
var instanceProperty: Int = 0 {
didSet {
Aclass.typeProperty = instanceProperty + 100
}
}
// 연산 타입 프로퍼티
static var typeComputedProperty: Int {
get {
return typeProperty
}
set {
typeProperty = newValue
}
}
}
Aclass.typeProperty = 123
let classInstance: Aclass = Aclass()
classInstance.instanceProperty = 100
print(Aclass.typeProperty) // 200
print(Aclass.typeComputedProperty) // 200
'Swift' 카테고리의 다른 글
[Swift] 옵셔널(Optionals) (0) | 2019.07.22 |
---|---|
[Swift] 접근제어(Access Control) (0) | 2019.07.20 |
[Swift] 객체지향 프로그래밍(OOP, Object-Oriented Programming) in Swift (0) | 2019.07.12 |
[Swift] 구조체와 클래스 (0) | 2019.07.12 |
[Swift] Collection Types (0) | 2019.07.12 |