프로퍼티(Property)
저장 프로퍼티(Stored Properties)
연산 프로퍼티(Computed Properties)
타입 프로퍼티(Type properties)
저장프로퍼티
저장프로퍼티는 클래스나 구조체에서 변수나 상수를 저장하는 가장 단순한 프로퍼티이다.
기본 형태
struct person1{
let name: String = "jake" //상수 저장 프로퍼티
var age: Int = 20 //변수 저장프로퍼티
}
class person2{
let name: String = "jake"
var age: Int = 20
//이니셜라이저 지정
// 초기값을 지정하지 않았거나, 지정했더라도 후에 값 변경이 있을 경우 이니셜라이저를 지정해주어야 한다.
init(age: Int){
self.age = age
}
}
//* 구조체는 기본적으로 저장 프로퍼티를 매개변수로 갖는 이니셜라이저가 있다.
let personjake: person1 = person1(age: 1)
//* 클래스는 사용자 정의 이니셜라이저를 호출하여 사용해야 한다.
//* 그렇지 않으면 프로퍼티 초기값을 할당할 수 없어 인스턴스 생성이 불가능하다
let personcake: person2 = person2(age: 1)
저장 프로퍼티는 열거형에서 사용 불가능하고 구조체와 클래스에서만 사용 가능하다.
구조체와 클래스의 차이점은 이니셜라이저의 존재 유무다
1. 구조체의 경우 저장 프로퍼티를 모두 포함하는 이니셜라이저를 자동 생성한다.
2. 클래스는 저장 프로퍼티가 옵셔널인 경우를 제외하면
프로퍼티의 가본 값을 지정해주거나, 사용자 정의 이니셜라이저를 지정해주어야 한다.
(기본값을 지정했더라도 나중에 값의 변경이 있을 예정이라면 이니셜라이저가 필요하다.)
인스턴스 생성시 초기값을 설정하면 구조체든, 클래스든 관련없이 이니셜라이저를 구현해줄 필요가 없어지기 때문에 편리해진다.
하지만 의도와 다르게 인스턴스가 사용될 수 있고, 값의 변경이 없도록 상수로 정의해주고 싶어도 인스턴스를 생성한 후에 값을 할당해 주어야 하므로 그럴 수 없다는 단점이 있다.
이런 단점을 개선하기 위해 옵셔널과 이니셜라이저를 적절히 사용하면 된다.
저장 프로퍼티의 효과적인 사용방법
struct Place{
var placeName: String
}
class Position{
var position: Place? //현재 위치를 모를수도 있으므로 옵셔널로 지정
let name: String = "jake"
}
// 필수 값만 먼저 할당하고, 값이 있어도 없어도 상관없는 프로퍼티는
// 옵셔널로 지정하여 위치를 지정하고 싶을 때 지정할 수 있다!
// 이 때엔 jake의 위치를 몰랐는데
let nowPosition: Position = Position()
//이때 jake의 위치를 알게되어서 위치를 지정
nowPosition.position = Place(placeName: "집")
지연 저장 프로퍼티
지금까지 본 저장 프로퍼티는 인스턴스를 생성할 경우 모든 프로퍼티를 초기화한다.
하지만 이 지연 저장 프로퍼티는 필요할 때 값이 할당된다.
인스턴스를 초기화 할 때는 선언만 되고, 해당 프로퍼티에 접근할 때 값을 할당함.
인스턴스를 초기화 할 때, 굳이 모든 프로퍼티를 초기화할 필요가 없을 때도 있다.
이럴 때 지연 저장 프로퍼티를 사용하면 불필요한 성능 저하나 공간 낭비를 줄일 수 있다.
지연 저장 프로퍼티는 lazy키워드 하나만 붙이면 끝이다. 하지만 주의할 점은, 지연 저장 프로퍼티는 필요할 때 값을 할당해야 하므로
상수로는 사용이 불가능하다. 무조건 var로 정의해야 한다.
지연저장 프로퍼티는 "선언 시에 기본값을 저장"해야함.
지연저장 프로퍼티를 사용하는 이유
class AView{
var a: Int
// 1) 메모리를 많이 차지할 때
lazy var view = UIImageView() //객체를 생성하는 형태
//2) 다른 속성을 이용해야 할때
lazy var b: Int = {
return a * 10
}()
init(num: Int){
self.a = num
}
}
1)
메모리 공간을 많이 차지하는 이미지 등의 속성에 저장할 때, (반드시 메모리에 다 올릴 필요가 없는 경우) 지연 저장 프로퍼티로 선언(메모리 낭비를 막기위해)
2)
다른 속성을 이용해야할떄
초기화 시점에 모든 속성들이 동시에 메모리 공간에 저장되므로 어떤 한가지 속성이 다른 속성에 접근할 수가 없다.
하지만 지연 저장 속성을 이용하는 경우 지연저장된 속성은 먼저 초기호된 속성에 접근 할 수 있게됨.
lazy의 특징
다중스레드 환경에서는 지연 저장 프로퍼티에 동시다발적으로 접근 할 때 한 번만 초기화한다는 보장을 할 수 없다.
생성되지 않은 지연 프로퍼티에 많은 스레드가 비슷한 시기에 접근하면, 여러번 초기화 될 수 있다.
연산프로퍼티(Computed Properties)
실제로 값을 저장하는 프로퍼티가 아닌 특정 상태의 값을 연산하는 프로퍼티이다( 값을 저장하는 공간이 없다).
따라서 다른 저장 프로퍼티를 가져와서 연산을 하고 적절한 값을 돌려주는 역할을 한다.
구조체, 클래스, 열거형 모두에서 사용 가능하고 접근자(getter), 설정자(setter)가 필요하다.
어떤 값을 가지고 연산, 반환한다는 부분에서 메서드와 비슷하다는 것을 알 수 있는데
메서드 대신 이 연산 프로퍼티를 사용하는 점을 알아보면,
연산 프로퍼티의 장점
- 관련이 있는 두가지 메서드(함수)를 한 번에 구현할 수 있다.
- 외부에서 보기에 속성이름으로 설정 가능하므로 보다 명확해보인다.
- 계산 속성은 메서드를 개발자들이 보다 읽기 쉽고, 명확하게 쓸 수 있는 형태인 속성으로 변환해 놓은것.
- 실제로는 속성 형태를 가진 메서드임
- 실제로 메모리 공간을 가지지 않는다. 속성에 접근했을 때 다른 속성에 접근해서 계산한 후 결과를 리턴하거나 세팅하는 메서드이다.
연산 프로퍼티의 주의점
- 항상 변하는 값이므로, var로 선언해야한다.
- 자료형을 반드시 선언해야한다(타입추론안됨)
- get은 반드시 선언해야한다.
연산 프로퍼티의 내부를 보면, get(getter), set(setter)가 존재한다.
1) getter는 말 그대로 값을 가져와서 연산하여 리턴하는 역할을 한다.
리턴을 하기때문에 return문 필수. 하지만 return이라는 키워드는 생략이 가능 할 수도 있다.
연산 프로퍼티는 타입을 꼭 명시해야 하는데 getter의 반환값이 프로퍼티 타입과 동일하다면
return키워드를 생략해도 그 결과값이 반환값이 된다.
2) setter 말 그대로 어떤 값을 설정하는 역할을 한다.
파라미터로 받은 값을 어떤 저장 프로퍼티에 연산하여 저장한다.
따라서 setter는 파라미터가 꼭 필요한데 , newValue키워드를 이용하면 파라미터를 생략하는 것도 가능하다.
class Person{
var birth: Int = 0
//계산 속성, 연산프로퍼티
var age: Int{
get{
return 2021 - birth
}
//set 뒤에 파라미터가 올 수 있다
set {
self.birth = 2021 - newValue
}
}
}
var p1 = Person()
p1.birth = 2000
//호출을 하면 컴파일러가 get인지 set인지 판단을 한다
//개발자 입장에서 값을 얻기위해 활동하는것은 get
p1.age // 21 //(get) get블럭 실행
//set
p1.age = 20 //(set) set의 파라미터로 들어가서 set구문을 실행
읽기 전용 연산 프로퍼티
//get키워드를 생략함.
class Person{
var birth: Int = 0
var age: Int{
return 2021 - birth
}
타입 프로퍼티(Type Properties)
타입 프로퍼티는 저장 프로퍼티, 연산 프로퍼티에 static만 붙이면 끝
*static: 고정적인, 고정된
타입프로퍼티는 각각의 인스턴스가 아닌 타입 자체에! 속하는 프로퍼티이다.
타입의 모든 인스턴스가 공통으로 사용하는 값이나, 모든 인스턴스에서 공용으로 사용할 수 있는 변수를 정의할 때 유용하다.
class Dog{
static var species: String: "Dog"
var name: String
var weight: Double
init(name: String, weight: Double){
self.name = name
self.weight = weight
}
}
let dog = Dog(name: "보라", weight: 15.0(
//일반적인 프로퍼티의 접근방법
dog.name //보라
dog.weight //15.0
//타입프로퍼티
//클래스자체에 접근한다
Dog.sepecies
name과 weight는 각각 인스턴스에 속한 타입이지만
sepecies는 Dog이라는 타입 자체에 속한 프로퍼티이다
저장 타입 프로퍼티
저장 타입 프로퍼티의 경우 중요한 특징이 하나 있다.
무조건 지연 연산 된다는 것
lazy 키워드를 사용하지 않음에도 불구하고 자동으로 지연 연산 처리된다.
하지만 지연 저장 프로퍼티와는 다른점
(1) 초기값을 설정해야 하며,
(2) 변수와 상수 모두 사용이 가능하고,
(3) 다중 스레드 환경에서도 무조건 한 번만 초기화 된다는 보장을 받기 떄문이다.
class Circle{
//저장타입프로퍼티 (항상 값이 있어야함)
static var let: Double = 3.14
static var count: Int = 0 //인스턴스(객체)의 수 가 몇개인지 확인
//저장속성
var radius: Double //반지름
//계산속성
var diameter: Double{ //지름
get{
return radius * 2
}
set{
radius = newValue / 2
}
}
//생성자
init(radius: Double){
self.radius = radius
Circle.count += 1 //인스턴스의 수 증가
}
}
(1) 초기값을 설정해야 한다
초기값을 설정하지 않고 생성을 하면 오류가 발생하는데, 그 내용은
static으로 선언할 거라면 초기값을 설정하거나 연산 타입 프로퍼티로 만들어라 라는 내용이다.
저장 타입 프로퍼티는 전역변수처럼 사용되므로 인스턴스르 생성할 때마다 초기화되는 다른 프로퍼티들과는 다르다.
한 번만 호출되어 메모리에 가면 그 이후로는 인스턴스가 새로 초기화 되든 말든, 상관없다.
프로퍼티가 새로 생성되지 않고 공유되어 사용되는 것이다.
따라서 이 저장 타입 프로퍼티는 인스턴스의 초기화와 관련이 없고,
인스턴스 생성시 호출되는 이니셜라이저 또한 관련이 없다..
때문에 처음 선언 시 초기값을 지정해두지 않으면
후에 초기값을 넣어 줄 방법이 없다. 따라서 초기값이 필수이다.
(2) 변수와 상수 모두로 정의 가능한 이유
지연 저장 프로퍼티에서는 무조건 변수만 사용 가능하다고 했는데,
여기서는 지연 저장임에도 불구하고 상수도 사용이 가능하다
타입 프로퍼티가 아닌 모든 프로퍼티 들의 경우
인스턴스가 생성될 때 이니셜라이저에 의해 모든 프로퍼티가 초기화 되는데,
이 떄 지연 저장 프로퍼티는 아직 호출이 되지 않았으므로 '값이 없음'으로 초기화 된다.
그렇게 되었을 때 이 지연 저장 프로퍼티가 상수라면 추후에 호출하여 원하는 값을 할당할 수 없게 된다.
위의 이유로 지연 저장 프로퍼티는 상수를 사용하지 못한다.
하지만 저장 타입 프로퍼티는 위에서 말했듯 인스턴스의 생성, 이니셜라이저와 아무 관계가 없다.
따라서 상수로 정의할지라도 해당 프로퍼티의 호출이 있기 전까지는
프로퍼티가 포함된 인스턴스가 초기화되든 말든.. 전혀 상관 없이 초기화 되지 않는다.
때문에 저장 타입 프로퍼티는 상수로 정의해도 내가 원할 때 호출하고 원하는 값을 할당할 수 있는 것.
(3) 다중 스레드 환경에서도 한 번만 초기화 된다는 보장을 받을 수 있는 이유
지연 저장 프로퍼티는 그냥 지연만 될 뿐, 지역 변수와 같은 역할을 한다는 점은 다르지 않다.
하지만 저장 타입 프로퍼티는 전역변수와 같은 역할을 하므로
딱 한 번 초기화 되고 메모리에 올라가면 더 이상 새로운 호출이 있을 수 없다.
따라서 다중 스레드 환경일지라도 두 번 이상 초기화 될 수 없다.
연산 타입 프로퍼티
연산 타입 프로퍼티는 전역 변수와 같은 역할을 한 다는 것만 제외하면
연산 프로퍼티와 큰 차이점이 없다
저장 타입 프로퍼티처럼 인스턴스에 접근하지 않고 타입 이름만으로 사용이 가능하다는 점,
그리고 연산 타입 프로퍼티와 마찬가지로 값을 저장하는게 아니므로
var로만 정의가 가능하다는 점만 인지하고 있으면 충분하다
class Circle{
//저장타입프로퍼티 (항상 값이 있어야함)
static var let: Double = 3.14
static var count: Int = 0 //인스턴스(객체)의 수 가 몇개인지 확인
//연산타입프로퍼티(read-only) get구문 생략
//타입프로퍼티끼리 이므로 Circle.생략 가능
static var multiPi: Double{
return Circle.pi * 2
}
//저장프로퍼티
var radius: Double //반지름
//생성자
init(radius: Double){
self.radius = radius
Circle.count += 1 //인스턴스의 수 증가
}
}
타입 속성의 메모리 구조 이해
저장 속성
- 일반적인 저장 속성은 인스턴스를 생성할 때, 생성자에서 모든 속성을 초기화한다. 그리고, 해당 저장 속성은 각 인스턴스가 가진 고유한 값이다.
저장 타입 속성
- 저장 타입(형식) 속성은 생성자가 따로 없기 때문에 항상 기본값이 필요하고 생략할 수 없다.
- 지연속성을 갖는다.
- 저장 타입 속성은 기본적으로 지연속성( 속성에 처음 접근하는 순간 초기화됨), 하지만 lazy로 선언할 필요는 없음
- 여러 스레드 환경에서 동시에 액세스하는 경우라도 한 번만 초기화 되도록 보장한다.
타입속성 - 클래스, 구조체, 열거형 모두 사용 가능
주의
인스턴스 내에서도 접근하려면 타입이름.속성으로 써야 접근 가능함.
타입메서드나 타입연산프로퍼티에선 타입이름을 적지 않아도 된다,
타입 속성을 선언하는 예
- 모든 인스턴스가 동일하게 가져야 하는 속성이거나, 모든 인스턴스가 공유해야하는 성격에 가까운 예
- 상속에서 재정의(overriding)
1) 저장 타입프로퍼티
상속에서, 하위클래스에서 재정의 불가능(class키워드 안됨)(인스턴스의 경우도 저장 속성은 고유의 틀이기 떄문에 건드릴 수 없음)
2) 연산 타입프로퍼티
상속에서, 상위클래스에서 class키워드를 붙인 경우, 재정의 가능
*class키워드(계산 타입 속성만)
상속이 있는 경우 계산 타입 속성에서는 static대신 class키워드를 사용 (---> ststic과 동일한 열할)
하면 재정의 가능한 속성이 됨.
즉) 상속에서 재정의가 가능할 때 class를 사용
상속에서 재정의가 불가능할 때 -> static사용