프로퍼티는 클래스, 구조체, 또는 열거형 등에 관련된 값을 뜻합니다.
메서드는 특정 타입에 관련된 함수를 뜻합니다.
12.1.1 저장프로퍼티
클래스의 경우 옵셔널을 준다면, init을 통해 초기값을 주지않아도 괜찮습니다.
나중에 값을 알게되었을 때 class에 값을 넣어줄 수 도 있습니다.
// 좌표
struct CoordinatePoint {
var x: Int = 0 // 저장 프로퍼티
var y: Int = 0 // 저장 프로퍼티
}
// 구조체에는 기본적으로 저장 프로퍼티를 매개변수로 갖는 이니셜라이저가 있다.
let kemiPoint = CoordinatePoint(x: 3, y: 5)
//
class Position {
var point: CoordinatePoint
// 저장 프로퍼티(변수) - 위치(point)는 변경될 수 있음을 뜻합니다.
let name: String
init(name: String, currentPoint: CoordinatePoint) {
self.name = name
self.point = currentPoint
}
}
let kemiPosition: Position = Position(name: "kemi", currentPoint: kemiPoint)
// 좌표
struct CoordinatePoint {
var x: Int = 0 // 저장 프로퍼티
var y: Int = 0 // 저장 프로퍼티
}
// 구조체에는 기본적으로 저장 프로퍼티를 매개변수로 갖는 이니셜라이저가 있다.
//
class Position {
var point: CoordinatePoint?
// 저장 프로퍼티(변수) - 위치(point)는 변경될 수 있음을 뜻합니다.
// 옵셔널을 설정해 주면, init을 하지않아도 된다.
let name: String
init(name: String) {
self.name = name
}
}
let kemiPosition: Position = Position(name: "kemi")
// 위치를 알게되어 나중에 넣어줘도 된다.
kemiPosition.point = CoordinatePoint(x: 3, y: 5)
12.1.2 지연 저장 프로퍼티
지연 저장 프로퍼티는 호출이 있어야 값을 초기화하며, 이때 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
}
}
let kemiPosition: Position = Position(name: "kemi")
// 이 코드를 통해 point 프로퍼티로 처음 접근 할 때
// point 프로터피의 CoordinatePoint가 생성됩니다.
print(kemiPosition.point)
연산 프로퍼티
연산프로퍼티를 사용하면 코드가 좀더 심플해집니다.
// 연산프로퍼티를 사용하지 않았을 때,
struct CoordinatePoint {
var x: Int // 저장 프로퍼티
var y: Int // 저장 프로퍼티
// 대칭정을 구하는 메서드 - 접근자
func oppositePoint() -> Self {
return CoordinatePoint(x: -x, y: -y)
}
// 대칭점을 설정하는 메서드 - 설정자
mutating func setOppositePoint(_ opposite: CoordinatePoint) {
x = -opposite.x
y = -opposite.y
}
}
var kemiPosition: CoordinatePoint = CoordinatePoint(x: 10, y: 20)
// 현재좌표
print(kemiPosition)
// 대칭좌표
print(kemiPosition.oppositePoint())
kemiPosition.setOppositePoint(CoordinatePoint(x: 15, y: 10))
// 현재 좌표는 -15, -10으로 설정됩니다.
print(kemiPosition)
// 연산 프로퍼티를 사용한 예시
struct CoordinatePoint {
var x: Int // 저장 프로퍼티
var y: Int // 저장 프로퍼티
var oppsitePoint: CoordinatePoint { // 연산 프로퍼티
// 접근자
get {
return CoordinatePoint(x: -x, y: -y)
}
// 생성자
set(opposite) {
x = -opposite.x
y = -opposite.y
}
}
}
var kemiPosition: CoordinatePoint = CoordinatePoint(x: 10, y: 10)
// 현재 좌표
print(kemiPosition) // CoordinatePoint(x: 10, y: 10)
// 대칭 좌표
print(kemiPosition.oppsitePoint) // CoordinatePoint(x: -10, y: -10)
// 설정
kemiPosition.oppsitePoint = CoordinatePoint(x: 15, y: 10)
print(kemiPosition) // CoordinatePoint(x: -15, y: -10)
12.1.4 프로퍼티 감시자(Property Observers)
프로퍼티 감시자를 사용하면 프로퍼티의 값이 변경됨에 따라 적절한 작업을 취할 수 있습니다.
프로퍼티 값이 새로 할당될 때마다 호출합니다. 오로지 저장프로퍼티, 상속받은 연산프로퍼티에 사용 할 수 있습니다.
프로퍼티 감시자는 프로퍼티의 값이 변경되기 직전에 willSet 메서드와 프로퍼티의 값이 변경된 직후에 호출되는 didSet 메서드가 있습니다.
willset 메서드의 전달인자는 프로퍼티가 변경될 값이고, didSet 메서드의 전달인자는 변경되기 전 값입니다.
class Account {
var credit: Int = 0 {
willSet {
print("credit 잔액이 \(credit)원에서 \(newValue)원으로 변경될 예정입니다.")
}
didSet {
print("credit 잔액이 \(oldValue)원에서 \(credit)원으로 변경되었습니다.")
}
}
var dollorValue: Double { // 연산 프로퍼티
get {
return Double(credit) //1000.0
}
set {
credit = Int(newValue * 1000)
print("잔액을 \(newValue)달러로 변경 중입니다. ")
}
}
}
class ForeignAccount: Account {
// 상속받은 dollorValue
override var dollorValue: Double {
willSet {
print("dollorValue 잔액이 \(dollorValue)원에서 \(newValue)원으로 변경될 예정입니다.")
}
didSet {
print("dollorValue 잔액이 \(oldValue)원에서 \(dollorValue)원으로 변경되었습니다.")
}
}
}
let myAccount: ForeignAccount = ForeignAccount()
// 잔액이 0원에서 1000원으로 변경될 예정입니다.
myAccount.credit = 1000
// 잔액이 0원에서 1000원으로 변경되었습니다.
//dollorValue 잔액이 1000.0원에서 2.0원으로 변경될 예정입니다.
//credit 잔액이 1000원에서 2000원으로 변경될 예정입니다.
//credit 잔액이 1000원에서 2000원으로 변경되었습니다.
myAccount.dollorValue = 2 // 잔액을 2.0달러로 변경되었습니다. dollorValue.set
// dollorValue 잔액이 1000.0원에서 2000.0원으로 변경되었습니다.
전연변수에도 감시자와 연산변수를 설정 할 수 있습니다.
var wonInPocket: Int = 2000 {
willSet {
print("주머니의 돈이 \(wonInPocket)원에서 \(newValue)원으로 변경될 예정입니다.")
}
didSet {
print("주머니의 돈이 \(oldValue)원에서 \(wonInPocket)원으로 변경되었습니다.")
}
}
var dollarInPocket: Double {
get {
return Double(wonInPocket) // 1000.0
}
set {
wonInPocket = Int(newValue * 1000)
print("주머니 달러를 \(newValue)달라로 변경 중입니다.")
}
}
//주머니의 돈이 2000원에서 3500원으로 변경될 예정입니다.
//주머니의 돈이 2000원에서 3500원으로 변경되었습니다.
dollarInPocket = 3.5 //주머니 달러를 3.5달라로 변경 중입니다.
12.1.6 타입 프로퍼티
각각의 인스턴스가 아닌 타입 자체에 속하는 프로퍼티를 타입 프로퍼티라고 합니다.
static으로 선언된 프로퍼티.
인스턴스의 생성 여부와 상관없이 타입프로퍼티는 값은 하나며, 그 타입의 인스턴스가 공통으로 사용하는 값입니다.
C언어의 static constant와 유사합니다.
class AClass {
//저장 타입 프로퍼티
static var typeProperty: Int = 0
// 저장 인스턴스 프로퍼티
var instanceProperty: Int = 0 {
didSet {
Self.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
12.1.7 키 경로
어떤 프로퍼티의 위치만 참조하도록 할 수 있습니다. 이 위치를 통해 값을 넣거나 가져올 수 있습니다.
키경로 타입은 WritableKeyPath<Root, Value> 와 ReferenceWritableKeyPath<Root, Value> 타입입니다.
class Person {
let name: String
init(name: String) {
self.name = name
}
}
struct Stuff {
var name: String
var owner: Person
}
print(type(of: \Person.name)) // ReferenceWritableKeyPath<Person, String>
print(type(of: \Stuff.name)) // WritableKeyPath<Stuff, String>
let kemi = Person(name: "kemi")
let hana = Person(name: "hana")
let macbook = Stuff(name: "MacBook Pro", owner: kemi)
var iMac = Stuff(name: "iMac", owner: kemi)
let iPhone = Stuff(name: "iPhone", owner: hana)
let stuffNameKeyPath = \Stuff.name
let ownerKeyPath = \Stuff.owner
// \Stuff.owner.name과 같은 표현이 됩니다.
let ownerNameKeyPath = ownerKeyPath.appending(path: \.name)
// 키 경로와 서브스크립트를 이용해 프로퍼티에 접근하여 값을 가져옵니다.
print(macbook[keyPath: stuffNameKeyPath])
print(iMac[keyPath: stuffNameKeyPath])
print(iPhone[keyPath: stuffNameKeyPath])
print(macbook[keyPath: ownerNameKeyPath])
print(iMac[keyPath: ownerNameKeyPath])
print(iPhone[keyPath: ownerNameKeyPath])
iMac[keyPath: stuffNameKeyPath] = "iMac Pro"
iMac[keyPath: ownerKeyPath] = hana
키 경로를 활용하면 프로토콜과 마찬가지로 타입 간의 의존성을 낮추는 데 많은 도움을 줍니다.
애플의 프레임워크는 키-값 코딩 등 많은 곳에서 키 경로를 활용함으로 잘 알아둬야합니다.
12.2 메서드
func와 기본적으로 문법은 같으나 struct, class, 열거형 등 특정 타입에 관련된 함수를 뜻합니다.
class LevelClass {
// 현재 레벨을 저장하는 저장 프로퍼티
var level: Int = 0 {
didSet {
print("Level \(level)")
}
}
// 레벨이 올랐을 때 호출할 메서드
func levelUp() {
print("Level Up!")
level += 1
}
// 레벨이 감소했을 때 호출할 메서드
func levelDown() {
print("Level Down")
level -= 1
if level < 0 {
reset()
}
}
// 특정 레벨로 이동할 때 호출할 메서드
func jumpLevel(to: Int) {
print("Jump to \(to)")
level = to
}
// 레벨을 초기화 할 때 호출할 메서드
func reset() {
print("Reset!")
level = 0
}
}
var levelClassInstance: LevelClass = LevelClass()
levelClassInstance.levelUp()
levelClassInstance.levelDown()
levelClassInstance.levelDown()
levelClassInstance.jumpLevel(to: 3)
struct에서는 자신의 프로퍼티 값을 수정 할 때 메서드 앞에 mutating 키워드를 붙여서 해당 메서드가 인스턴스 내부의 값을 변경한다는 것을 명시해야합니다.
struct LevelStruct {
// 현재 레벨을 저장하는 저장 프로퍼티
var level: Int = 0 {
didSet {
print("Level \(level)")
}
}
// 레벨이 올랐을 때 호출할 메서드
mutating func levelUp() {
print("Level Up!")
level += 1
}
// 레벨이 감소했을 때 호출할 메서드
mutating func levelDown() {
print("Level Down")
level -= 1
if level < 0 {
reset()
}
}
// 특정 레벨로 이동할 때 호출할 메서드
mutating func jumpLevel(to: Int) {
print("Jump to \(to)")
level = to
}
// 레벨을 초기화 할 때 호출할 메서드
mutating func reset() {
print("Reset!")
level = 0
}
}
var levelStructInstance: LevelStruct = LevelStruct()
levelStructInstance.levelUp()
levelStructInstance.levelDown()
levelStructInstance.levelDown()
levelStructInstance.jumpLevel(to: 3)
타입 메서드
타입 프로퍼티가 있는 것 처럼 메서드에도 타입 메서드가 존재합니다.
메서드 앞에 class를 붙여주거나 static을 붙여주면 됩니다.
둘의 차이점은 static은 overriding이 불가능하고 class는 오버라이딩(재정의)가 가능하다는 점입니다.
코드로 보면 다음과 같습니다.
class AClass {
static func staticTypeMethod() {
print("AClass staticTypeMethod")
}
class func classTypeMethod() {
print("AClass classtypeMethod")
}
}
class BClass: AClass {
/*
// 오류 발생 재정의 불가!!
override static func staticTypeMethod() {
}
*/
override class func classTypeMethod() {
print("BClass classTypeMethod")
}
}
AClass.staticTypeMethod()
AClass.classTypeMethod()
BClass.classTypeMethod()
타입 프로퍼티를 제어하기위해 타입 메서드를 같이 사용하곤 합니다.
타입 프로퍼티는 언제나 유일한 값을 가질 수 있습니다. 그렇기 때문에 class를 메모리에 올려 인스턴스로 생성하지 않아도 접근 할 수 있게됩니다.
// 시스템 음량은 한 기기에서 유일한 값이어야합니다.
struct SystemVolume {
// 타입 프로퍼티를 사용하면 언제나 유일한 값이 됩니다.
static var volume: Int = 5
// 타입 프로퍼티를 제어하기 위해 타입 메서드를 사용합니다.
static func mute() {
self.volume = 0
}
}
// 내비게이션 역할은 여러 인스턴스가 수행 할 수 있습니다.
class Navigation {
// 내비게이션 인스턴스마다 음량을 따로 설정할 수 있습니다.
var volum: Int = 5
// 길 안내 음성 재생
func guideWay() {
// 내비게이션 외 다른 재생원 음소거
SystemVolume.mute()
}
// 길 안내 음성 종료
func finishGuideWay() {
// 기존 재생원 음량 복구
SystemVolume.volume = self.volum
}
}
SystemVolume.volume = 10
let myNavi: Navigation = Navigation()
myNavi.guideWay()
print(SystemVolume.volume)
myNavi.finishGuideWay()
print(SystemVolume.volume)
'Swift > Swift 기본문법' 카테고리의 다른 글
Swift 기본 문법 - 14. 접근제어 (0) | 2022.02.02 |
---|---|
Swift 기본 문법 - 13. 인스턴스의 생성과 소멸 (0) | 2022.02.01 |
Swift 기본 문법 - 11. 구조체와 클래스 (0) | 2022.01.30 |
Swift 기본 문법 - 10. 옵셔널 (0) | 2022.01.18 |
Swift 기본 문법 - 9. 함수 (0) | 2022.01.18 |