[스위프트 프로그래밍-10장] 프로퍼티와 메서드

sanghee·2021년 11월 2일
0
post-thumbnail

이 글은 스위프트 프로그래밍(3판, 야곰 지음)을 읽고 간단하게 정리한 글입니다. 책에 친절한 설명과 관련 예제 코드들이 있으므로 직접 사서 읽기를 추천합니다.

10.0 소개

프로퍼티는 클래스, 구조체 또는 열거형 등에 관련된 값을 뜻한다. 메서드는 특정 타입에 관련된 함수를 뜻한다.

10.1 프로퍼티

프로퍼티는 크게 저장, 타입, 연산 프로퍼티가 있다. 저장 프로퍼티는 인스턴스의 변수 또는 상수이다. 연산 프로퍼티는 값은 저장한 것이 아닌, 특정 연산을 실행한 결괏값이다. 타입 프로퍼티는 특정 타입에 사용되는 프로퍼티이다.

10.1.1 저장 프로퍼티

키워드 let, var

키워드 let을 사용하면 상수 저장 프로퍼티, 키워드 var를 사용하면 변수 저장 프로퍼티가 된다. 저장 프로퍼티를 정의할 때 프로퍼티 기본값과 초깃값을 지정할 수 있다.

클래스의 저장 프로퍼티는 옵셔널이 아니라면 기본값을 지정하거나, 사용자 정의 이니셜라이저를 통해 반드시 초기화해주어야 한다. 클래스의 상수 프로퍼티는 값을 한번만 할당할 수 있으며 변경할 수 없다.

import Foundation

struct CoordinatePoint {
    var x: Int // 저장 프로퍼티(변수)
    var y: Int // 저장 프로퍼티(변수)
}

let point = CoordinatePoint(x: 0, y: 0)

class Position {
    let name: String // 저장 프로퍼티(상수)
    var point: CoordinatePoint // 저장 프로퍼티(변수)
    
    
    // 기본값이 없기에 이니셜라이저를 정의해야 한다.
    init(name: String, point: CoordinatePoint) {
        self.name = name
        self.point = point
    }
}

let position = Position(name: "Lee", point: point)

초기값을 지정한 경우

초기값을 미리 지정하면 의도와 맞지 않게 인스턴스가 사용될 수 있다. 또한 생성 후 원하는 값을 일일이 할당해야 해서 불편하며 이때 상수가 변수로 정의해야 한다는 점이 있다.

struct CoordinatePoint {
    var x: Int = 0
    var y: Int = 0
}

class Position {
    var name: String = "Unknwon"
    var point: CoordinatePoint = CoordinatePoint()
    
    // 기본값이 있기에 이니셜라이저를 정의하지 않아도 된다.
}

let position = Position()

position.name = "Lee"

print(position.name) // Lee
print(position.point) // CoordinatePoint(x: 0, y: 0)

저장 프로퍼티 옵셔널 타입

저장 프로퍼티 값이 옵셔널이라면 굳이 초기값을 넣지 않아도 된다.

class Position {
    let name: String
    var point: CoordinatePoint?
    
    init(name: String) {
        self.name = name
    }
}

let position = Position(name: "Lee")

// 옵셔널이기에, 생성 후 값을 할당해도 된다.
position.point = CoordinatePoint(x: 99, y: 99)

10.1.2 지연 저장 프로퍼티

키워드: lazy var

지연 저장 프로퍼티는 호출이 있어야 값을 초기화한다.

상수는 인스턴스가 완전히 생성되기 전에 초기화해야 한다. 반면 지연 저장 프로퍼티는 생성 후 호출이 있을 경우 var 키워드를 사용해야 한다.

주로 복잡한 클래스나 구조체를 구현할 때 많이 사용한다. 굳이 모든 저장 프로퍼티를 사용할 필요가 없는 경우 사용하며, 불필요한 성능저하나 공간 낭비를 줄일 수 있다.

class Position {
    let name: String
    lazy var point: CoordinatePoint = CoordinatePoint()
    
    init(name: String) {
        self.name = name
    }
}

let position = Position(name: "Lee")

print(position.point) // point 프로퍼티로 처음 접근할 때 point 프로퍼티의 CoordinatePoint가 생성된다.

다중 스레드 환경에서 지연 저장 프로퍼티에 동시다발적으로 접근할 때는 한번이 아닌 여러번 초기화될 수 있다.

10.1.3 연산 프로퍼티

연산 프로퍼티는 값을 연산하는 프로퍼티이다.

메서드

struct CoordinatePoint {
    var x: Int
    var y: Int
    
    // Self는 타입 자기 자신을 의미한다
    func oppositePoint() -> Self {
        CoordinatePoint(x: -x, y: -y)
    }
    
    // mutating 키워드는 잠시 후 나온다
    mutating func setOppositePoint(_ opposite: CoordinatePoint) {
        x = -opposite.x
        y = -opposite.y
    }
}

var point = CoordinatePoint(x: 10, y: 20)

print(point.oppositePoint()) // CoordinatePoint(x: -10, y: -20)

point.setOppositePoint(CoordinatePoint(x: 15, y: 10))

print(point) // CoordinatePoint(x: -15, y: -10)

연산 프로퍼티

메서드: get set

읽기 전용으로 구현하고 싶다면 get 메서드만 사용하면 된다.

struct CoordinatePoint {
    var x: Int
    var y: Int
    
    var oppositePoint: Self {
        get {
            CoordinatePoint(x: -x, y: -y) // return 키워드 생략 가능
        }
        set(opposite) {
            x = -opposite.x // opposite 명시 안하면 newValue로 접근
            y = -opposite.y
        }
    }
}

var point = CoordinatePoint(x: 10, y: 20)

// get
print(point.oppositePoint) // CoordinatePoint(x: -10, y: -20)

// set
point.oppositePoint = CoordinatePoint(x: 15, y: 10)

print(point) // CoordinatePoint(x: -15, y: -10)

10.1.4 프로퍼티 감시자

메서드 willSet didSet

프로퍼티 값이 변경될 때 호출하며 변경되는 값이 현재의 값과 같더라도 호출한다.

willSet은 값이 변경되기 직전에 호출하며 newValue로 변경될 값을 전달한다.

didSet은 값이 변경된 이후에 호출하며 oldValue로 변경전 값을 전달한다.

class Account {
    var credit: Int = 0 {
        willSet {
            print("\(credit)원에서 \(newValue)원으로 변경 예정")
        }
        didSet {
            print("\(oldValue)원에서 \(credit)원로 변경 됨")
        }
    }
}

let account = Account()

// willSet: 0원에서 1000원으로 변경 예정
account.credit = 1000
// didSet: 0원에서 1000원으로 변경 됨
class Account {
    var credit: Int = 0 {
        willSet {
            print("\(credit)원에서 \(newValue)원으로 변경 예정")
        }
        didSet {
            print("\(oldValue)원에서 \(credit)원로 변경 됨")
        }
    }
    
    var dollarValue: Double {
        get {
            print("달러로 변경 예정입니다.")
            return Double(credit)
        }
        set {
            credit = Int(newValue * 1000)
            print("\(newValue)달러로 변경 중입니다.")
        }
        
    }
}

class ForeignAccount: Account {
    override var dollarValue: Double {
        willSet {
            print("\(dollarValue)달러에서 \(newValue)달러로 변경 예정")
        }
        didSet {
            print("\(oldValue)달러에서 \(dollarValue)달러로 변경 됨")
        }
    }
}

let account = ForeignAccount()
// willSet(Account의 credit): 0원에서 1000원으로 변경 예정
account.credit = 1000
// didSet(Account의 credit ): 0원에서 1000원로 변경 됨
// 2. get(Account의 dollarValue): 달러로 변경 예정입니다.
// 2. get(Account의 dollarValue):달러로 변경 예정입니다.

// 3. willSet(ForeignAccount의 dollarValue): 1000.0달러에서 2.0달러로 변경 예정

// 1. willSet(Account의 credit): 1000원에서 2000원으로 변경 예정
// 1. didSet(Account의 credit): 1000원에서 2000원로 변경 됨

account.dollarValue = 2 // 2. set(Account의 dollarValue): 2.0달러로 변경 중입니다.

// 3. didSet(ForeignAccount의 dollarValue): 1000.0달러에서 2000.0달러로 변경 됨

10.1.5 전역변수와 지역변수

전역변수란 프로그램 내에서 어디서든 사용이 가능한 변수이며, 지역변수는 함수 등 일정 영역에서만 사용이 가능한 변수이다.

전역변수 또는 전역상수는 지연 저장 프로퍼티처럼 처음 접근할 때 최초로 연산이 이루어지기에 lazy 키워드를 사용할 필요가 없다.

지연변수 및 지역상수는 절대로 지연 연산이 되지 않는다.

var wonInPocket: Int = 2000 {
    willSet {
        print("\(wonInPocket)원에서 \(newValue)원으로 변경될 예정입니다.")
    }
    didSet {
        print("\(oldValue)원에서 (wonInPocket)원으로 변경되었습니다.")
    }
}

var dollarInPocket: Double {
    get {
        Double(wonInPocket)
    }
    set {
        wonInPocket = Int(newValue * 1000.0)
        print("주머니의 돈을 \(newValue)로 변경중입니다.")
    }
}

// willSet(wonInPocket) : 2000원에서 3500원으로 변경될 예정입니다.
// didSet(wonInPocket) : 2000원에서 (wonInPocket)원으로 변경되었습니다.
dollarInPocket = 3.5 // set(dollarInPocket) : 주머니의 돈을 3.5로 변경중입니다.

10.1.6 타입 프로퍼티

인스턴스가 아닌 타입 자체에 속하는 프로퍼티를 타입 프로퍼티라고 한다.

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()

classInstance.instanceProperty = 100

print(classInstance.instanceProperty) // 100

print(AClass.typeProperty) // 200. 타입으로 접근함
print(AClass.typeComputedProperty) // 200

10.2 메서드

메서드는 특정 타입에 관련된 함수를 의미한다. 이전에 언급했듯이, 함수와 메서드는 기본적으로 같으나, 구조체, 클래스, 열거형 등 특정 타입에 연관되어 사용하는 함수를 특별히 메서드라고 부른다.

10.2.1 인스턴스 메서드

인스턴스 메서드는 특정 타입의 인스턴스에 속한 함수를 뜻한다.

클래스 메서드

class LevelClass {
    var level: Int = 0 {
        didSet {
            print("레벨 \(level)")
        }
    }
    
    func levelUp() {
        level += 1
        print("레벨업")
    }
}

let levelClass = LevelClass()

levelClass.levelUp()
// 레벨 1
// 레벨업

구조체 메서드

키워드 == mutating

구조체는 값 타입이므로 클래스와는 다르게 메서드 앞에 mutating을 붙여서 해당 메서드가 인스턴스 내부의 값을 변경한다는 것을 명시해야 한다.

struct LevelStruct {
    var level: Int = 0 {
        didSet {
            print("레벨 \(level)")
        }
    }
    
    mutating func levelUp() {
        level += 1
        print("레벨업")
    }
}

var levelStruct = LevelStruct()

levelStruct.levelUp()
// 레벨 1
// 레벨업

self 프로퍼티

모든 인스턴스는 암시적으로 생성된 self 프로퍼티를 갖는다. 인스턴스 자기 자신을 가리키는 프로퍼티이다. self 프로퍼티는 인스턴스를 더 명확히 지칭하고 싶을 때 사용한다.

스위프트는 메서드 내부에서 선언된 지역 변수 → 매서드 매개변수 → 인스턴스 프로퍼티 순으로 level를 찾는다.

아래의 코드에서 level = level로 작성하면, 좌측의 level이 매개변수로 넘어온 level로 판단하기에 self를 붙여서 인스턴스 프로퍼티라는 것을 명확히 한다.

class LevelClass {
    var level: Int = 0 { // 인스턴스 프로퍼티 level
        didSet {
            print("레벨 \(level)")
        }
    }
    
    func levelJump(level: Int) { // 함수 매개변수 level
        self.level = level
        print("레벨 \(level)로 점프")
    }
}

let levelClass = LevelClass()

levelClass.levelJump(level: 99) // 레벨 99 // 레벨 99로 점프

클래스

클래스의 인스턴스는 참조 타입이어서 self프로퍼티에 다른 참조를 할당할 수 없다. 반면에 구조체는 self 프로퍼티를 통해 자기 자신 자체를 치환할 수 있다.

class LevelClass {
    var level: Int = 0
    
    func reset() {
        self = LevelClass() // 에러!
    }
}

구조체

struct LevelStruct {
    var level: Int = 0
    
    mutating func levelUp() {
        level += 1
    }
    
    mutating func reset() {
        self = LevelStruct()
    }
}

var levelStruct = LevelStruct()

levelStruct.levelUp()
print(levelStruct.level) // 1

levelStruct.reset()
print(levelStruct.level) // 0
profile
👩‍💻

0개의 댓글