10. 프로퍼티와 메서드

JinStory77·2022년 5월 15일
0

Swift 공부_기초

목록 보기
10/11

프로퍼티와 메서드

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


1. 프로퍼티

  • 저장 프로퍼티, 연산 프로퍼티, 타입 프로퍼티로 나눌 수 있다.
    • 저장 프로퍼티(Stored Properties)
      • 인스턴스의 변수 또는 상수를 의미한다. 구조체와 클래스에서만 사용할 수 있다.
    • 연산 프로퍼티(Computed Properties)
      • 값을 저장하는 것이 아니라 특정 연산을 실행한 결괏값을 의미한다. 구조체, 클래스, 열거형에 쓰일 수 있다.
    • 타입 프로퍼티(Type Properties)
      • 특정 인스턴스가 아닌 특정 타입에 사용되는 프로퍼티를 말한다.
  • 프로퍼티 감시자(Property Observers)라는 프로퍼티의 값이 변하는 것을 감시하는 것도 있다. 저장 프로퍼티에 적용할 수 있으며 부모클래스로부터 상속받을 수 있다.

1.1. 저장 프로퍼티(stored property)

  • 클래스 또는 구조체의 인스턴스에 연관된 값을 저장하는 가장 단순한 개념의 프로퍼티이다.

  • 저장 프로퍼티를 정의할 때 프로퍼티 기본값과 초깃값을 지정해줄 수 있다.

  • ✏️ 구조체와 클래스의 저장 프로퍼티

    • 구조체의 저장 프로퍼티가 옵셔널이 아니더라도, 구조체는 저장 프로퍼티를 모두 포함하는 이니셜라이저를 자동으로 생성한다. 하지만 클래스의 저장 프로퍼티는 옵셔널이 아니라면 프로퍼티 기본값을 지정해주거나 사용자 정의 이니셜라이저를 통해 반드시 초기화해주어야한다. 또, 클래스 인스턴스의 상수 프로퍼티는 인스턴스가 최기화(이니셜라이즈)될 때 한 번만 값을 할당할 수 있으며, 자식클래스에서 이 초기화를 변경(재정의)할 수 없다.
  • 저장 프로퍼티의 선언 및 인스턴스 생성
// 좌표
struct CoordinatePoint {
    var x: Int      // 저장 프로퍼티
    var y: Int      // 저장 프로퍼티
}

// 구조체에는 기본적으로 저장 프로퍼티를 매개변수로 갖는 이니셜라이저가 있음
let JinPoint: CoordinatePoint = CoordinatePoint(x: 10, y: 5)

// 사람의 위치 정보
class Position {
    var point: CoordinatePoint
    // 저장 프로퍼티(변수) - 위치(point)는 변경될 수 있음을 뜻함
    let name: String        // 저장 프로퍼티(상수)
    
    // 프로퍼티 기본값을 지정해주지 않는다면 이니셜라이저를 따로 정의해주어야 함
    init(name: String, currentPoint: CoordinatePoint){
    self.name = name
    self.point = currentPoint
    }
}

// 사용자 정의 이니셜라이저를 호출해야만 함.
// 그렇지 않으면 프로퍼티 초깃값을 할당할 수 없기 때문에 인스턴스 생성이 불가능함
let JinPosition: Position = Position(name: "Jin", currentPoint: JinPoint)
  • 저장 프로퍼티의 최깃값 지정
// 좌표
struct CoordinatePoint {
    var x: Int = 0     // 저장 프로퍼티
    var y: Int = 0     // 저장 프로퍼티
}

// 프로퍼티의 초깃값을 할당했다면 굳이 전달인자로 초깃값을 넘길 필요 없음
let JinPoint: CoordinatePoint = CoordinatePoint()

// 물론 기존에 초깃값을 할당 할 수 있는 이니셜라이저도 사용 가능함
let wizplanPoint: CoordinatePoint = CoordinatePoint(x: 10, y: 5)

print("Jin's point : \(JinPoint.x), \(JinPoint.y)")
// Jin's point : 0, 0

print("wizplan's point : \(wizplanPoint.x), \(wizplanPoint.y)")
// wizplan's point : 10, 5
// 사람의 위치 정보
class Position {
    var point: CoordinatePoint = CoordinatePoint()  // 저장 프로퍼티
    var name: String = "Unknown"                    // 저장 프로퍼티
}

// 초깃값을 지정해줬다면 사용자 정의 이니셜라이저를 사용하지 않아도 됨
let JinPosition: Position = Position()

JinPosition.point = JinPoint
JinPosition.name = "Jin"
  • 옵셔널 저장 프로퍼티
// 좌표
struct CoordinatePoint {
    // 위치는 x, y 값이 모두 있어야 하므로 옵셔널이면 안 됨
    var x: Int
    var y: Int
}

// 사람의 위치 정보
class Position {
    // 현재 사람의 위치를 모를 수도 있음 - 옵셔널
    var point: CoordinatePoint?
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

// 이름은 필수지만 위치는 모를 수 있음
let JinPosition: Position = Position(name: "Jin")

// 위치를 알게되면 그 때 위치 값을 할당함
JinPosition.point = CoordinatePoint(x: 20, y: 10)

1.2. 지연 저장 프로퍼티

  • lazy라는 키워드를 사용한다.
  • 지연 저장 프로퍼티는 호출이 있어야 값을 초기화하며, 이때 lazy 키워드를 사용한다.
  • 주로 복잡한 클래스나 구조체를 구현할 때 사용한다.
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 JinPosition: Position = Position(name: "Jin")

// 이 코드를 통해 point 프로퍼티로 처음 접근할 때
// point 프로퍼티의 CoordinatePoint가 생성됨
print(JinPosition.point)     // CoordinatePoint(x: 0, y: 0)

1.3. 연산 프로퍼티(calculated property)

  • 연산 프로퍼티는 실제 값을 저장하는 프로퍼티가 아니라, 특정 상태에 따른 값을 연산하는 프로퍼티이다.

  • 인스턴스 내/외부의 값을 연산하여 적절한 값을 돌려주는 접근자(getter)의 역할이나 은닉화된 내부의 프로퍼티 값을 간접적으로 설정하는 설정자(setter)의 역할을 할 수도 있다.

  • 다만 연산 프로퍼티는 접근자인 get 메서드만 구현해둔 것처럼 읽기 전용 상태로 구현하기 쉽지만, 쓰기 전용 상태로 구현할 수 없다는 단점이 있다. 메서드로는 설정자 메서드만 구현하여 쓰기 전용 상태로 구현할 수 있지만 연산 프로퍼티는 그것이 불가능한다.

  • 메서드로 구현된 접근자와 설정자

struct CoordinatePoint {
    var x: Int  // 저장 프로퍼티
    var y: Int  // 저장 프로퍼티

// 대칭점을 설정하는 메서드 - 설정자
// Self는 타입 자기 자신을 뜻함
// Self 대신 CoordinatePoint를 사용해도 됨.
    func oppositePoint() -> Self {
        return CoordinatePoint(x: -x, y: -y)
    }
    // 대칭점을 설정하는 메서드 - 설정자
    // mutating 키워드에 관한 내용은 10.2.1절에서 다룸
    mutating func setOppositePoint(_ opposite: CoordinatePoint) {
        x = -opposite.x
        y = -opposite.y
    }
}

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

// 현재 좌표
print(JinPosition)                   //  10, 20

// 대칭 좌표
print(JinPosition.oppositePoint())   // -10. -20

// 대칭 좌표를 (15, 10)으로 설정하면
JinPosition.setOppositePoint(CoordinatePoint(x: 15, y: 10))

// 현재 좌표를 -15, -10으로 설정됨
print(JinPosition)          // -15, -10
  • 연산 프로퍼티의 정의와 사용
struct CoordinatePoint {
    var x: Int  // 저장 프로퍼티
    var y: Int  // 저장 프로퍼티

    // 대칭 좌표
    var oppositePoint: CoordinatePoint {        // 연산 프로퍼티
        // 접근자
        get {
            return CoordinatePoint(x: -x, y: -y)
        }

        // 설정자
        set(opposite) {
            x = -opposite.x
            y = -opposite.y
        }
    }
}

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

// 현재 좌표
print(JinPosition)                   // 10, 20

// 대칭 좌표
print(JinPosition.oppositePoint)     // -10, -20

// 대칭 좌표를 (15, 10)으로 설정하면
JinPosition.oppositePoint = CoordinatePoint(x: 15, y: 10)

// 현재 좌표는 -15, -10으로 설정됨
print(JinPosition)          // -15, -10
  • 매개변수 이름을 생략한 설정자
struct CoordinatePoint {
    var x: Int      // 저장 프로퍼티
    var y: Int      // 저장 프로퍼티

// 대칭 좌표
    var oppositePoint: CoordinatePoint {       // 연산 프로퍼티

        get {
// getter(접근자)
// 이곳에서 return 키워드를 생략 할 수 있음
            return CoordinatePoint(x: -x, y: -y)
        }
        set {
            x = -newValue.x
            y = -newValue.y
        }
// setter(설정자)
    }
}
  • 읽기 전용 연산 프로퍼티
struct CoordinatePoint {
    var x: Int      // 저장 프로퍼티
    var y: Int      // 저장 프로퍼티
    
    // 대칭 좌표
    var oppositePoint: CoordinatePoint {     // 연산 프로퍼티
// getter
        get {
            return CoordinatePoint(x: -x, y: -y)
        }
    }
}

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

// 현자 좌표
print(JinPosition)          // 10, 20

// 대칭 좌표
print(JinPosition.oppositePoint)  // - 10, -20

// setter를 구현하지 않았으므로 오류
JinPosition.oppositePoint = CoordinatePoint(x: 15, y: 10)

1.4. 프로퍼티 감시자(Property Observers)

  • 프로퍼티 값이 변경될 때 적절한 작업을 취할 수 있다.
  • 프로퍼티의 값이 새로 할당될 때마다 호출한다. 이때 변경되는 값이 현재의 값과 같더라도 호출한다.
  • 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원으로 변경되었습니다.
  • 상속받은 연산 프로퍼티의 프로퍼티 감시자 구현
class Account {
    var credit: Int = 0 {
        willSet {
            print("잔액이 \(credit)원에서 \(newValue)원으로 변경될 예정입니다.")
        }
        didSet {
            print("잔액이 \(oldValue)원에서 \(credit)원으로 변경되었습니다.")
        }
    }
    var dollarValue: Double {
        get {
            return Double(credit) / 1000.0
        }
        set {
            credit = Int(newValue * 1000)
            print("잔액을 \(newValue)달러로 변경 중입니다.")
        }
    }
}

class ForeignAccount: Account {
    override var dollarValue: Double {
        willSet {
            print("잔액이 \(dollarValue)달러에서 \(newValue)달러로 변결될 예정입니다.")
        }
        didSet {
            print("잔액이 \(oldValue)달러에서 \(dollarValue)달러로 변경되었습니다.")
        }
    }
}


let myAccount: ForeignAccount = ForeignAccount()
// 잔액이 0원에서 1000원으로 변경될 예정입니다.
myAccount.credit = 1000
// 잔액이 0원에서 1000원으로 변경되었습니다.

// 잔액이 1.0달러에서 2.0달러로 변결될 예정입니다.
// 잔액이 1000원에서 2000원으로 변경될 예정입니다.
// 잔액이 1000원에서 2000원으로 변경되었습니다.

myAccount.dollarValue = 2
// 잔액을 2.0달러로 변경 중입니다.
// 잔액이 1.0달러에서 2.0달러로 변경되었습니다.

1.5. 전역변수와 지역변수

  • 연산 프로퍼티와 프로퍼티 감시자는 전역변수와 지역변수에 모두 사용 가능하다.
  • 함수, 메서드, 클로저, 클래스, 구조체, 열거형 등의 범위 안에 포함되지 않던 변수나 상수는 모두 전역변수 또는 전역상수에 해당한다.
  • 지금까지 다뤘던 변수, 전역변수 또는 지역변수는 저장변수라 할 수 있다.
var wonInPocket: Int = 2000 {
    willSet {
        print("주머니의 돈이 \(wonInPocket)원에서 \(newValue)원으로 변경될 예정입니다.")
    }
// 주머니의 돈이 2000원에서 3500원으로 변경될 예정입니다.
    didSet {
        print("주머니의 돈이 \(oldValue)원에서 \(wonInPocket)원으로 변경되었습니다.")
    }
// 주머니의 돈이 2000원에서 3500원으로 변경되었습니다.
}

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

dollarInPocket = 3.5
// 주머니의 달러를 3.5달러로 변경 중입니다.

1.6. 타입프로퍼티

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

  • 타입 자체에 영향을 미치는 프로퍼티이다.

  • 인스턴스 생성 여부와 상관없이 타입 프로퍼티의 값은 하나이다.

  • 타입 프로퍼티와 인스턴스 프로퍼티

class AClass {
    // 저장 타입 프로퍼티
    static var typeProperty: Int = 0
    
    // 저장 인스턴스 프로퍼티
    var instanceProperty: Int = 0 {
        didSet {
            // Self.typeProperty는
            // AClass.typeProperty와 같은 표현임
            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
  • 타입 프로퍼티의 사용
class Account {
    
    static let dollarExchangeRate: Double = 1000.0
    
    var credit: Int = 0
    
    var dollarValue: Double {
        get {
            return Double(credit)
        }
        
        set {
            credit = Int(newValue * Account.dollarExchangeRate)
            
            print("잔액을 \(newValue) 달러로 변경 중입니다.")
        }
    }
}

1.7. 키 경로

  • 스위프트의 함수가 일급시민으로서 상수나 변수에 참조를 할당 할 수 있다.

  • 그러나 키 경로(keyPath)를 지정해 어떤 프로퍼티의 위치만 참조하도록 할 수도 있다.

  • 키 경로 타입의 타입 확인

class Person {
    var 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>
  • 클로저를 대체할 수 있는 키 경로 표현
struct Person {
    let name: String
    let nickname: String?
    let age: Int
    
    var isAdult: Bool {
        return age > 18
    }
}

let jin: Person = Person(name: "jin", nickname: "bear", age: 100)
let hana: Person = Person(name: "hana", nickname: "na", age: 100)
let happy: Person = Person(name: "happy", nickname: nil, age: 3)

let family: [Person] = [jin, hana, happy]
let names: [String] = family.map(\.name)
// ["jin", "hana", "happy"]
let nickname: [String] = family.compactMap(\.nickname)
// ["bear", "na"]
let adults: [String] = family.filter(\.isAdult).map(\.name)
// ["jin", "hana"]


2. 메서드

  • 메서드는 특정 타입에 관련된 함수를 뜻한다.
  • 클래스, 구조체, 열거형 등은 실행하는 기능을 캡슐화한 인스턴스 메서드를 정의할 수 있다.
  • 스위프트에서는 프로그래머가 정의하는 타입(클래스, 구조체, 열거형 등)에 자유롭게 메서드를 정의 할 수 있다.

2.1.1 인스턴스 메서드

  • 특정 타입의 인스턴스에 속한 함수를 뜻한다.
  • 함수와 달리 특정 타입 내부에 구현한다.
  • 인스턴스가 존재할 때만 사용할 수 있다.

self 프로퍼티

  • 모든 인스턴스는 암시적으로 생성된 self 프로퍼티를 갖는다.

  • self 프로퍼티는 인스턴스를 더 명확히 지칭하고 싶을 때 사용한다.

  • 클래스의 인스턴스 메서드

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()   // Level Up!
// Level 1
levelClassInstance.levelDown() // Level Down
// Level 0
levelClassInstance.levelDown() // Level Down
// Level -1
// Reset!
// Level 0
levelClassInstance.jumpLevel(to: 3) // Jump to 3
// Level 3
  • mutating 키워드의 사용
struct LevelClass {
    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()   // Level Up!
// Level 1
levelStructInstance.levelDown() // Level Down
// Level 0
levelStructInstance.levelDown() // Level Down
// Level -1
// Reset!
// Level 0
levelStructInstance.jumpLevel(to: 3)  // Jump to 3
// Level 3

2.2. 타입 or 클래스 메서드

  • 타입 자체에 호출이 가능한 메서드를 타입 메서드라 부른다.
  • 'static', 'calss' 키워드를 사용한다.
    • static = 상속 후 메서드 재정의 불가능
    • class = 상속 후 메서드 재정의 가능
class AClass {
    static func staticTypeMethod() {
        print("AClass staticTypeMethod")
    }
    
    class func classTypeMethod() {
        print("AClass classTypeMethod")
    }
}

class BClass: AClass {
    override class func classTypeMethod() {
        print("BClass classTypeMethod")
    }
// override static func staticTypeMethod(){ }
// 재정의 불가한 static, 오류 발생!
}


AClass.staticTypeMethod()
// AClass staticTypeMethod
AClass.classTypeMethod()
// AClass classTypeMethod
BClass.classTypeMethod()
// BClass classTypeMethod
profile
Let's smile for future 🤩

0개의 댓글