프로퍼티는 스위프트에서 클래스와 구조체, 열거형에 값을 연결해 주는 존재이다
(다만 열거형은 계산 프로퍼티만 사용이 가능하다.)
종류는 세가지가 있는데 저장 프로퍼티와 계산 프로퍼티, 타입프로퍼티 세가지가 존재한다
그리고 프로퍼티 관찰자 (property observer)와 프로퍼티 래퍼(property wrapper)가 존재하는데
프로퍼티 관찰자와 프로퍼티 래퍼는 데이터의 변경을 관찰하고 해당 시점에 여러가지 데이터 처리를 가능하게 해준다.
클래스나 구조체에서 내부에 저장된 상수나 변수를 말한다.
물론 우리가 사용하는 int, double, String같은 기본 데이터타입 변수/상수들도 저장 프로퍼티에 속한다.
struct Box{
var width: Int
var height: Int
}
var someBox: Box = Box(width: 100, height: 100)
여기서 저장 프로퍼티는 width와 height변수이다.
( Box인스턴스 초기화에 왜 생성자가 필요없는지 궁금하다면 여기로 )
struct Box{
var width: Int
var height: Int
}
class Cube{
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
let someBox: Box = Box(width: 100, height: 100)
let someCube: Cube = Cube(width: 100, height: 100)
Box와 Cube의 인스턴스 someBox, someCube의 프로퍼티를 수정 할 수 있을까?
someCube.width = 200 //...1
someBox.width = 200 //...2
다음과 같이 코드를 작성하면 어떤게 오류가 날까? 둘다 오류가 날까?
정답은 2번코드만 오류가 난다.
이전에 구조체와 클래스 게시글에서 구조체는 값타입이고 클래스는 참조타입이라고 했었다.
인스턴스의 데이터 구조를 간단하게 하면 다음과 같은데
구조체의 인스턴스의 경우 메모리상의 데이터의 주소를 인스턴스가 직접 갖는다.
클래스의 인스턴스의 경우 메모리의 데이터의 주소를 저장하는곳의 주소를 갖는다.
그래서 상수로 인스턴스를 만들면 다음과 같이 변한다.
구조체의 경우 구조체 인스턴스가 가리키는 데이터의 수정이 불가능 하지만
클래스의 경우 클래스 인스턴스의 주소값을 수정하는게 불가능하게 되기 때문에
데이터 자체는 수정이 가능한 것이다.
var someCube2: Cube = Cube(width: 300, height: 300)
someCube1 = someCube2
하지만 위의 경우는 불가능하다
someCube1 = someCube2는
someCube1의 주소값을 someCube2의 주소값으로 변경하는 시도이기 때문에 주소값이 상수로 선언된 someCube1은 수정이 불가능하다.
lazy키워드를 사용한다.
보통은 인스턴스가 생성되면 그 즉시 저장프로퍼티는 연관된 값을 모두 계산해서 인스턴스에 집어 넣는데
lazy 프로퍼티의 경우에는 바로 값을 계산하는게 아니라 인스턴스가 생성되고 최초로 사용되는 시점에 계산을 수행한다.
저장프로퍼티의 계산이 내부 리소스를 많이 사용하는 무거운 경우 사용하면 좋다.
class DataImporter {
var filename = "data.txt"
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
}
let manager = DataManager()
manager.data.append("Some data") //이 시점에서 importer의 연산이 실행됨
manager.data.append("Some more data")
대용량의 파일을 로딩하거나 외부에서 데이터를 받아오는것을 제한하고 싶을때 사용하면 된다.
다른언어에서의 getter와 setter의 기능을 하는 부분이다.
저장 프로퍼티의 경우 클래스/구조체에서 내부 데이터를 저장하는 역할을 했다면
계산 프로퍼티는 내부 데이터를 접근할때의 데이터에 관여하는 기능을 수행한다.
import Foundation
struct Square {
private var line: Double = 0.0
var size: Double {
get {
//get에서는 무조건 값을 가져오기만 해야됨 내부 프로퍼티의 수정 불가
return line * line
}
set(newLine) {
//Foundation안에 있는 sqrt()함수, 실수의 제곱근을 반환한다.
line = sqrt(newLine)
}
}
}
var sRect: Square = Square()
print(sRect.size) // get
sRect.size = 30 // set
print(sRect.size) // get
//결과 0.0
//결과 30.0
위 코드는 정사각형을 만들어주는 코드로
사각형의 선을 저장하는 저장프로퍼티 line이 있지만 private라서 무조건 size 프로퍼티를 통해서만 데이터 접근이 가능하게 만들었다.
사용은 그냥 일반 저장프로퍼티나 변수처럼 사용하면된다. 하지만 사용하는 종류에 따라 다르게 동작한다.
그냥 인스턴스.size를 불러오기만 하면 자동으로 get의 코드가 실행되고
인스턴스.size에 값을 수정하려고 하면 자동으로 set의 코드가 실행된다.
var/let 이름: 타입{
get{
return //code...
}
set(newValue){
//code...
}
}
형태는 위 코드와 같다.
주의 해야 할 점은 get에서는 무조건 내부 프로퍼티를 가져오거나 return하는것만 가능하다.
무슨말이냐면 내부에 var로 선언된 저장프로퍼티를 수정하는게 불가능하다는 것이다.(컴파일러가 검사한다.)
set의 경우 위에서 ()안에 이름을 선언해서 넘어오는 데이터의 이름을 지정해 줬는데 이를 생략 할 수 있다.
struct Square {
private var line: Double = 0.0
var size: Double {
get {
return line * line
}
set {
line = sqrt(newValue)
}
}
}
이렇게 set뒤에 아무것도 없이 사용한다면 넘어오는 데이터는 newValue라는 키워드를 통해서 접근이 가능하다.
struct Square {
private var line: Double = 0.0
var size: Double {
get {
line * line
}
set {
line = sqrt(newValue)
}
}
}
이전에 클로저에서 했던것처럼 동작이 return 한줄이라면 return의 생략이 가능하다.
struct Square {
var line: Double = 0.0
var size: Double {
line * line
}
}
var square:Square = Square(line: 10.0)
print(square.size)
//결과...100.0
이처럼 계산프로퍼티 내부에 get/set키워드를 모두 없애고 대괄호 하나에 코드만 작성한다면 암시적으로 get만 사용하는게 된다.
읽기 전용으로만 만드는 경우에 사용된다.(클로저와 안헷갈리게 조심)
이번 시간에는 저장 프로퍼티와 계산 프로퍼티에 대해 알아 보았다
- 저장프로퍼티는 클래스나 구조체 내부에 저장하기 위한 변수나 상수들을 말한다
- 계산프로퍼티는 클래스나 구조체 내부에 접근을 처리하는 프로퍼티 getter/setter의 역할을 수행한다.
- 다른 문법들 처럼 생략형태가 존재한다.
다음 시간에는 프로퍼티 관찰자와 프로퍼티 래퍼, 타입프로퍼티 에 대해 알아볼 것이다.