본문 바로가기

문법

[Swift]프로퍼티와 메서드 알아보기 2부(Feat. 고차함수)

주의 :  초보이기에 정확하지않는 정보가 있을 수 있습니다.

 

 

프로퍼티와 메서드 알아보기 2부

 

- 타입 프로퍼티(Type Propertie)
- 키 경로(KeyAndPath)
- 메서드(Method)
- 인스턴스 메서드(Instance Method)
- self프로퍼티
- 타입 메서드(Type Method)
- 포인트 정리
- 궁금증

 

타입 프로퍼티

 

이제까지 알아본 프로퍼티 개념은 모두 타입을 정의하고 해당 타입의 인스턴스가 생성되었을때 사용할 수 있는 인스턴스 프로퍼티이다. 인스턴스 프로퍼티인스턴스를 새로 생성할 때마다 초깃값에 해당하는 값이 프로퍼티의 값이 되고, 인스턴스마다 다른 값을 지닐 수 있다.

=> 인스턴스 프로퍼티는 생성시 독립적인 형태이며 여러개를 생성하여 각각  다른 값으로 활용할 수있다.

예시)

let A = Person()

A.name = "강호동"

let B = Person()

B.name = "이수근"

let C = Person()

C.name = "유재석"

 

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

미치는 프로퍼티이다. 인스턴스의 생성여부와 상관없이 타입프로퍼티의 값은 하나이며, 그 타입의 모든 인스턴스가

공통으로 사용하는 값(C언어의 static constant와유사), 모든 인스턴스에서 공용으로 접근하고 값을 변경할

수 있는 변수(C언어의 static변수와 유사)등을 정의할 때 유용하다.

 

타입프로퍼티는 두 가지인데 저장 타입프로퍼티는 변수 또는 상수로 선언할 수 있으며, 연산 타입프로퍼티변수로만

선언할 수 있다. 저장 타입프로퍼티반드시 초깃값을 설정해야 하며 지연 연산됩니다. 지연 저장 프로퍼티와는

조금 다르게 다중 스레드 환경이라고 하더라도 단 한번만 초기화 된다는 보장을 받는다. 지연 연산된다고 해서

lazy키워드로 표시해주지는 않는다.

 

타입 프로퍼티와 인스턴스 프로퍼티의 차이를 확인해보자

 

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

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

class AClass {
    // 저장 타입 프로퍼티
    static var typeProperty: Int = 0
     
    // 저장 인스턴스 프로퍼티
    var instanceProperty: Int = 0 {
        didSet {
            // Self.typeProperty는
            // AClass.typeProperty와 같은 표현이며 타입을 지칭할때 사용함, (self는 타입의 프로퍼티를 지칭할때 사용함)
            Self.typeProperty = instanceProperty + 100
        }
    }
    
    // 연산 타입 프로퍼티
    static var typeComputedProperty: Int {
        get {
            Self.typeProperty
        }
        
        set {
            Self.typeProperty = newValue
        }
    }
}

AClass.typeProperty = 123

let classInstance = AClass()
classInstance.instanceProperty = 100
print(classInstance.instanceProperty) // 100
print(AClass.typeProperty) // 200
AClass.typeComputedProperty = 2000
print(AClass.typeComputedProperty) // 200

위에서 보듯이 타입 프로퍼티는 인스턴스 생성하지 않고도 사용할 수 있으며 타입에 해당하는 값이다. 그래서 인스턴스에 접근할 필요 없이 타입 이름만으로도 프로퍼티를 사용할 수 있다.

 

타입 프로퍼티의 사용

// 타입 프로퍼티의 사용
class Account {
    static let dollarExchangeRate: Double = 1000.0 // 타입 상수
    
    var credit: Int = 0 // 저장 인스턴스 프로퍼티
    
    var dollarValue: Double { // 연산 인스턴스 프로퍼티
        get {
            // Self.dollarExchangeRate는 Account.dollarExchangeRate와 같은표현이다.
            Double(credit) / Self.dollarExchangeRate
        }
        
        set {
            // Self.dollarExchageRate는 Account.dollarExchangeRate와 같은표현이다.
            credit = Int(newValue * Account.dollarExchangeRate)
            print("잔액을 \(newValue)달러로 변경중입니다.")
        }
        
    }
}

 

키 경로 (이해도낮음)

 

함수가 일급시민으로서 상수나 변수에 참조를 할당할 수 있었던 것을 기억하시나요?

func someFunction(parmA: Any, parmB: Any) {
    print("someFunction called...")
}

또 이렇게 함수를. 참조해두고 나중에 원할 때 호출할 수 있고, 다른 함수를 참조하도록 할 수도 있다.

var functionreference = someFunction(parmA:parmB:)
print(functionreference("A", "B"))

 

프로퍼티도 이처럼 값을 바로 꺼내오는 것이 아니라 어떤 프로퍼티의 위치만 참조하도록 할 수 있다. 바로 키 경로(keyPath)를 활용하는 방법이다. 키 경로를 사용하여 간접적으로 특정타입의 어떤 프로퍼티 값을 가리켜야 할지미리 지정해두고 사용할 수 있습니다.

 

키 경로 타입은 AnyKeyPath라는 클래스로부터 파생됩니다. 제일 많이 확장된 키 경로 타입은 WritableKeyPath<Root, Value>*와 ReferenceWritableKeyPath<Root, Value>타입이다. WritableKeyPath<Root, Value>타입은 값 타입에 키 경로 타입으로 읽고 쓸 수 있는 경우에 적용되며, ReferenceWritableKeyPAth<Root, Value>타입은 참조 타입, 즉 클래스 타입에 키 경로 타입으로 읽고 쓸 수 있는 경우에 적용된다.

 

키 경로는 역슬래시와 타입, 마침표(.) 경로로 구성된다.

 

* 제네릭 타입이다.

 

키 경로 타입의 타입 확인

// 키 경로 타입의 타입 확인
class Person {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

struct Stuff {
    var name: String
    var owner: Person
}
//print(type(of: Person)) // 오류발생 - Expected member name or constructor call after type name
print(type(of: \Person.name)) // ReferenceWritableKeyPath<Person, String>
print(type(of: \Stuff.owner)) // WritableKeyPath<Stuff, String>

키 경로는 기존의 키 경로에 하위 경로를 덧붙여 줄 수도 있다.

 

키 경로 타입의 경로 연결

// 키 경로 타입의 경로 연결
let keyPath = \Stuff.owner
let nameKeyPath = keyPath.appending(path: \.name)

print(keyPath) // Swift.WritableKeyPath<PropertieAndMethod2.Stuff, PropertieAndMethod2.Person>
print(nameKeyPath) // Swift.ReferenceWritableKeyPath<PropertieAndMethod2.Stuff, Swift.String>

 

키 경로 타입의 경로 연결

// keyPath 서브스크립트 키 경로 활용
class Person2 {
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

struct Stuff2 {
    var name: String
    var owner: Person
}

let quokka = Person(name: "quokka")
let kangaru = Person(name: "kangaru")
var iPad = Stuff2(name: "아이패드", owner: quokka)
let macBook = Stuff2(name: "맥북", owner: kangaru)
let iPhone = Stuff2(name: "아이폰", owner: quokka)

let stuffNameKeyPath = \Stuff2.name
print(stuffNameKeyPath)
let ownerKeyPath = \Stuff2.owner
print(ownerKeyPath)

// \Stuff.owner.name과 같은 표현이 됩니다.
let ownerNameKeyPath = ownerKeyPath.appending(path: \.name)
print(ownerKeyPath)
//키 경로와 서브스크립트를 이용해 프로퍼티에 접근하여 값을 가져옵니다.
print(macBook[keyPath: stuffNameKeyPath]) // 맥북
print(iPad[keyPath: stuffNameKeyPath]) // 아이패드
print(iPhone[keyPath: stuffNameKeyPath]) // 아이폰

print(macBook[keyPath: ownerKeyPath].name) // kangaru
print(iPad[keyPath: ownerKeyPath].name) // quokka
print(iPhone[keyPath: ownerKeyPath].name) // quokka

// 키 경로와 서브스크립트를 이용해 프로퍼티에 접근하여 값을 변경한다.

iPad[keyPath: stuffNameKeyPath] = "iPad3세대"
iPad[keyPath: ownerKeyPath] = kangaru

print(iPad[keyPath: stuffNameKeyPath]) //iPad3세대
print(iPad[keyPath: ownerKeyPath].name) //kangaru

 

상수로 지정한 값 타입과 읽기 전용 프로퍼티는 키 경로 서브스크립트 값을 바꿀 수 없다.

 

키경로를 잘 활용하면 프로토콜과 마찬가지로 타입 간의 의존성을 낮추는데 많은 도움을 줍니다. 또, 애플의 프레임워크는

키-값 코딩 등 많은 곳에 키 경로를 활용하므로, 애플 프레임워크 기반의 애플리케이션을 만든다면 잘 알아두면 많은 도움이 될것이다!

 

NOTE

접근수준과 키 경로

키경로는 타입 외부로 공개된 인스턴스 프로퍼티 혹은 서브스크립트에 한하여 표현할 수 있다.

 

TIP

(이해도 잘안됨)자신을 나타내는 키 경로는 \.self를 사용하면 인스턴스 그 자체를 표현하는 키 경로가 된다. 또 컴파일러가 타입을

유추할 수 있는 경우에는 키 경로에서 타입이름을 생략할 수도 있다.

 

 

스위프트 5.2버전부터 (SomeType) -> Value타입의 클로저를 키 경로 표현으로 대체할 수 있다.

map, filter, compactMap은 맵, 필터, 리듀스 알아보자

 

클로저를 대체할 수 있는 표현 (복습필요)

// 클로저를 대체할 수 있는 키표현

struct Person3 {
    let name: String
    let nickname: String?
    let age: Int
    
    var isAdult: Bool {
        return age > 18
    }
}

let quokka2 = Person3(name: "quokka", nickname: "쿼카", age: 25)
let kanghodong = Person3(name: "강호동", nickname: "돼지", age: 50)
let leesugeun = Person3(name: "이수근", nickname: "오락부장", age: 45)

let family: [Person3] = [quokka2, kanghodong, leesugeun]
print(family)
//[PropertieAndMethod2.Person3(name: "quokka", nickname: Optional("쿼카"), age: 25), PropertieAndMethod2.Person3(name: "강호동", nickname: Optional("돼지"), age: 50), PropertieAndMethod2.Person3(name: "이수근", nickname: Optional("오락부장"), age: 45)]

let names: [String] = family.map(\.name)
let namesClosure: [String] = family.map({ $0.name }) //names와 같은표현
let namesClosure2: [String] = family.map{ $0.name } //names와 같은표현
print(names)// ["quokka", "강호동", "이수근"]
print(namesClosure) // ["quokka", "강호동", "이수근"]
print(namesClosure2) // ["quokka", "강호동", "이수근"]

let nicknames: [String] = family.compactMap(\.nickname)
let nicknamesClosure: [String] = family.compactMap({ $0.nickname })
let nicknamesClosure2: [String] = family.compactMap{ $0.nickname }
print(nicknames)// ["쿼카", "돼지", "오락부장"]

let adults: [String] = family.filter(\.isAdult).map(\.name)
let adultsClosure: [String] = family.filter({ $0.isAdult}).map({$0.name })
let adultsClosure2: [String] = family.filter{ $0.isAdult}.map{$0.name }
print(adults)// ["quokka", "강호동", "이수근"]

let ages: [Int] = family.map(\.age)
let agesClosure: [Int] = family.map({ $0.age })
let agesClosure2: [Int] = family.map{ $0.age }
print(ages)// [25, 50, 45]

 

family배열은 [Person3]타입이며, 이 타입의 map은 (Person3) -> T를, compactMap은 (Person3) -> T?를, filter는 (Person3) -> Bool 타입의 클로저를 매개변수를 통해 전달 받은것이다.

 

이에따라 \.name 표현은 (Person) -> T타입의 클로저를 대체하여 표현하였고, 이는 클로저 표현인 {$0.name}의 표현과 같은 역할을 수행한다. \.nickname과 \.isAdult 표현도 같은 방식으로 동작하는 것을 알 수 있따.

 

 

메서드

메서드는 특정 타입에 관련된 함수를 뜻한다.

클래스, 구조체, 열거형 등은 실행하는 기능을 캡슐화한 인스턴스 메서드를 정의할 수 있다. 또한 타입 자체와 관련된 기능을 실행하는 타입 메서드를 정의할 수도 있다. 타입 메서드는 기존의 프로그래밍 언어에서의 클래스 메서드와 유사한 개념이다.

 

구조체와 열거형이 메서드를 가질 수 있다는 것은 기존 프로그래밍 언어와 스위프트간의 큰 차이점이다. 스위프트에서는 프로그래머가 정의하는 타입(클래스, 구조체, 열거형 등)에 자유롭게 메서드를 정의 할 수 있다.

 

 

인스턴스 메서드

인스턴스 메서드는 특정 타입의 인스턴스에 속한 함수를 뜻한다. 인스턴스 내부의 프로퍼티 값을 변경하거나 특정 연산 결과를 반환하는 등 인스턴스와 관련된 기능을 실행한다. 인스턴스 메서드는 함수와 문법이 같다

 

인스턴스 메서드는 함수와 달리 특정 타입 내부에 구현한다. 따라서 인스턴스가 존재할 때만 사용할 수 있다.

이 점이 함수와 유일한 차이점이다. 클래스의 인스터스 메서드를 정의하고 사용하는 방법을 살펴보자

 

클래스의 인스턴스 메서드

// 클래스의 인스턴스 메서드
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 1
levelClassInstance.levelDown()
// Level 0
levelClassInstance.levelDown()
//Level -1
//Reset!
//Level 0
levelClassInstance.jumpLevel(to: 3) //jump to 3
//Level 3

구현한 LevelClass의 인스턴스 메서드는 level 인스턴스 프로퍼티의 값을 수정하는 코드가 있다. 자신의 프로퍼티 값을 수정할 때 클래스의 인스턴스 메서드는 크게 신경 쓸 필요가 없지만, 구조체 열거형 등은 값 타입이므로 메서드 앞에 mutating 키워드를 붙여서 해당 메서드가 인스턴스 내부의 값을 변경한다는 것을 명시해한다.

 

구조체의 인스턴스메서드 (mutating 사용)

// 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
    }
}

 

 

self프로퍼티

 

모든 인스턴스 암시적으로 생성된 self프로퍼티를 갖는다. *자바의 this의 비슷하게 인스턴스 자기 자신을 가리키는 프로퍼티이다.

self 프로퍼티는 인스턴스를 더 명확하게 지칭하고 싶을 때 사용한다.* **level변수를 사용할때, 스위프트는 자동으로 메서드 내부에

선언된 지역변수를 먼저 사용하고, 그다음 메서드 매개변수, 그 다음 인스턴스의 프로퍼티를 찾아서 level이 무엇을 지칭하는지

유추한다. (위 코드에서 인스턴스 메서드 기준으로 컴파일러가 읽는 순서: 메서드내 지역변수 -> 매개변수 -> 인스턴스의 프프로퍼티)

*기억하기 쉽게 '메지,매매,인프'순*

그런데 메서들 매개변수의 이름이 level인데, 이 level매개변수가 아닌 인스턴스 프로퍼티인 level을 지칭하고 싶다면 self프로퍼티를 사용한다.

 

self프로퍼티의 다른용도는 값 타입 인스턴스 자체의 값을 치환할 수 있다. 클래스의 인스턴스는 참조 타입이라서 self 프로퍼티에

다른 참조를 할당할 수 없는데, 구조체나 열거형 등은 self프로퍼티를 사용하여 자신 자체를 치환할 수도 있다.

아래 코드로 확인해보자

 

self프로퍼티와 mutating 키워드

// self프로퍼티와 mutating 키워드
class LevelClass2 {
    var level: Int = 0
    
    func reset() {
        //오류 발생! self프로퍼티 참조 변경불가!
//        self = LevelClass2()
    }
}

struct LevelStruct2 {
    var level: Int = 0
    
    mutating func levelUp() {
        print("Level Up!")
        level += 1
    }
    
    mutating func reset() {
        print("Reset!")
        self = LevelStruct2()
    }
}

var levelStrictInstance2 = LevelStruct2()
levelStrictInstance2.levelUp() //Level Up!
print(levelStrictInstance2.level) // 1

levelStrictInstance2.reset() //Reset!
print(levelStrictInstance2.level) //0

enum OnOffSwitch {
    case on, off
    mutating func nextState() {
        self = self == .on ? .off : .on
    }
}

var toggle = OnOffSwitch.off
toggle.nextState()
print(toggle) // on

 

인스턴스를 함수처럼 호출하도록 하는 메서드

사용자 정의 명목 타입의 호출가능한 값(Callable values of user-unfined nominal types)을 구현하기 위해 인스턴스를 함수처럼 호출할 수 있도록 하는 메서드(Call-as-function method)가 있습니다. 말이 좀 어려운데 특정 타입의 인스턴스를 문법적으로 함수를 사용하는 것처럼 보이게 할 수 있다는 뜻이다. 인스턴스를 함수처럼 호출할 수 있도록 하려면 callAsFunction이라는 이름의 메서드를 구현하면된다. 이 메서드는 매개변수와 반환 타입만 다르다면 개수에 제한 없이 원하는 만큼 만들 수 있다. mutating 키워드도 사용할 수 있고, throws와 rethrows도 함께 사용 할 수 있다.

 

구조체에  callAsFunction 메서드 구현

// Puppy 구조체에 callAsFunction메서드 구현

struct Puppy {
    var name: String = "멍멍이"
    
    func callAsFunction() {
        print("멍멍")
    }
    
    func callAsFunction(desination: String) {
        print("\(desination)(으)로 달려갑니다.")
    }
    
    func callAsFunction(something: String, times: Int) {
        print("\(something)(을)를 \(times)번 반복합니다.")
    }
    
    func callAsFunction(color: String) -> String {
        "\(color) 응가"
    }
    
    mutating func callAsFunction(name: String) {
        self.name = name
    }
    
    func callAs(message: String) {
        print("\(message)")
    }
}

var doggy = Puppy()
//만일 callAsFunction에서 이름이 달라진다면? doggy()를 했을때 callAsFunction라고 적혀있는 메서드를 순서대로 읽어 찾아간다.
//doggy.callAsFunctionA() // 멍멍
//doggy(desination: "멍멍") // 멍멍(으)로 달려갑니다.
doggy.callAsFunction()// 멍멍
doggy()//멍멍
doggy.callAsFunction(desination: "집")// 집(으)로 달려갑니다.
doggy(desination: "뒷마당")// 뒷마당(으)로 달려갑니다.
doggy(something: "뒷구르기", times: 4)// 뒷구르기(을)를 4번 반복합니다.
print(doggy(color: "베이지색")) // 베이지색 응가
doggy(name: "몽몽이")
print(doggy.name) // 몽몽이

//doggy(message: "붐")//callAs은 인식을 못하며 오류가 발생한다.
// No exact matches in call to instance method 'callAsFunction'
// 오류 해석 - 인스턴스 메서드 'callAsFunction'에 대한 호출에서 정확히 일치하는 항목이 없다.

puppy 구조체에 다양한 형태의 callAsFunction메서드를 구현했다. 매개변수타입, 전달인자레이블, 반환타입이 겹치지 않는다면 원하는 만큼 구현할 수 있다. 매개변수의 개수도 원하는 대로 구현할 수 있다. doggy()표현과 doggy.callAsFunction() 표현은 완전히 똑같은 표현이다. 이와 마찬가지로 doggy(destination: "집") 과 doggy.callAsFunction(destination: "집")도 같은 표현이라는 것이다.

 

하지만 메서드를 호출하는 것 외에 함수 표현으로는 사용할 수 없다.

즉, let function: (String) -> Void = doggy.callAsFunction(destination:)로 표현해야 한다.

 

 

타입 메서드

인스턴스 프로퍼티와 타입 프로퍼티가 있듯 메서드에서 인스턴스 메서드와 타입 메서드가 있다. 타입 자체에 호출이 가능한 메서드를 타입 메서드(흔히 객체지향 프로그래밍에서 지칭하는 클래스 메서드와 유사)라고 부른다. 메서드 앞에 static 키워드를 사용하여 타입 메서드임을 나타내준다. 클래스의 타입 메서드는 static 키워드와 class키워드를 사용할 수 있는데 static으로 정의하면 상속 후 메서드 재정의가 불가능하고 class로 정의하면 상속 후 메서드 재정의가 가능하다.

 

전에 포스팅했던 링크[클릭가능]에서 확인해볼 수 있지만 복습차원에서 다시 코드로 확인해보자

 

클래스의 타입 메서드

// 클래스의 타입 메서드
class ClassA {
    static func staticTypeMethod() {
        print("AClass staticTypeMethod")
    }
    
    class func classTypeMethod() {
        print("AClass classTypeMethod")
    }
}

class ClassB: ClassA {
//    override static func staticTypeMethid () {
        //오류 발생 -  Method does not override any method from its superclass
//    }
    override class func classTypeMethod() {
        print("BClass classTypeMethod")
    }
}

ClassA.staticTypeMethod() //AClass staticTypeMethod
ClassA.classTypeMethod() //AClass classTypeMethod
ClassB.classTypeMethod() //BClass classTypeMethod

타입 메서드는 인스턴스 메서드와 달리 self프로퍼티가 타입 그 자체를 가리킨다는 점이 다르다. 인스턴스 메서드에서는 self가 인스턴스를 가리킨다면 타입 메서드의 self는 타입을 가리킨다. 그래서 타입 메서드 내부에서 타입 이름과 self는 같은 뜻이라고 볼 수 있다. 그래서 타입 메서드에서 self 프로퍼티를 사용하면 타입 프로퍼티 및 타입메서드를 호출할 수 있다.

 

 

타입 프로퍼티와 타입 메서드의 사용

// 타입 프로퍼티와 타입메서드의 사용

//시스템 음량은 한 기기에서 유일한 값이어야한다.
struct SystemVolume {
    // 타입 프로퍼티를 사용라면 언제나 유일한 값이 된다.
    static var volume: Int = 5
    
    // 타입 프로퍼티를 제어하기 위해 타입 메서드를 사용한다.
    static func mute() {
        self.volume = 0 //SystemVolume.volume = 0 과 같은 표현이다.
                        //Self.volume = 0 과도 같은 표현이다.
    }
}

//네비게이션 역할은 여러 인스턴스가 수행할 수 있다.
class Navigation {
    
    //네비게이션 인스턴스마다 음량을 따로 설정할 수 있다.
    var volume: Int = 5
    
    //길 안내 음성 재생
    func guideWay() {
        // 네비게이션 외 다른 재생원 음소거
        SystemVolume.mute()
    }
    
    //길 안내 음성 종료
    func finishGuideWay() {
        // 기존 재생원 음량 복구
        SystemVolume.volume = self.volume
    }
}

SystemVolume.volume = 10

let myNavi = Navigation()
myNavi.guideWay() //SystemVolume.volume을 0으로 변경
print(SystemVolume.volume) // 0

 

포인트정리

- 인스턴스 프로퍼티는 인스턴스를 새로 생성할 때마다 초깃값에 해당하는 값이 프로퍼티의 값이 되고, 인스턴스마다 다른 값을 지닐 수 있다.

- 각각의 인스턴스가 아닌 타입자체에 속하는 프로퍼티를 타입프로퍼티 라고 하며 인스턴스의 생성여부와 상관없이 타입프로퍼티의 값은 하나로 이 타입의 모든 인스턴스가 곹옹으로 사용하는 값이 된다.

 

- 저장 타입프로퍼티는 변수 또는 상수로 선언할 수 있으며, 연산 타입프로퍼티는 변수로만 선언할 수 있다.

- 저장 타입프로퍼티는 반드시 초깃값을 설정해야 하며 다중 스레드 환경이라고 하더라도 단 한번만 초기화 된다는 보장을 받는다.

- 타입 프로퍼티는 인스턴스를 생성하여 접근할 필요 없이 타입.타입인스턴스 이렇게 바로 접근할수있다.

- 키경로는 타입 외부로 공개된 인스턴스 프로퍼티 혹은 서브스크립트에 한하여 표현할 수 있다.

- 인스턴스 메서드와 함수의 유일한 차이점이 인스턴스 메서드는 함수와 달리 특정 타입 내부에 구현한다. 따라서 인스턴스가 존재할 때만 사용할 수 있다.

 

- 자신의 프로퍼티 값을 수정할 때 클래스의 인스턴스 메서드는 크게 신경 쓸 필요가 없지만 값 타입의 구조체나 열거형은 메서드 앞에 mutating 키워드를 명시함으로써 해당 메서드가 인스턴스 내부의 값을 변경한다는 것을 컴파일러에게 알려야한다.

 

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

- 위 코드에서 인스턴스 메서드 기준으로 컴파일러가 읽는 순서는 - 메서드내 지역변수 -> 매개변수 -> 인스턴스의 프로퍼티)

  *기억하기 쉽게 '메지,매매,인프'순*

 

- 클래스의 인스턴스는 참조 타입이라서 self 프로퍼티에 다른 참조를 할당할 수 없는데, 구조체나 열거형 등은 self프로퍼티를 사용하여 자신 자체를 치환할 수도 있다.

 

- callAsFunction이라는 메서드명을 사용하여 특정 타입의 인스턴스를 문법적으로 함수를 사용하는 것처럼 보이게 할 수 있으며 매개변수와 반환 타입만 다르다면 개수에 제한 없이 원하는 만큼 만들 수 있다.

예시) doggy()표현과 doggy.callAsFunction() 완전히 똑같은 표현이다.

 

- 타입 메서드는 앞에 static키워드를 사용하여 나타내며 클래스 타입메서드에는 class를 사용할 수도 있다. static과 class타입메서드 이 둘의 차이는 상속후 재정의가 되느냐 안되느냐의 차이이다.

- 타입 메서드는 인스턴스 메서드와 달리 self프로퍼티가 타입 그 자체를 가리킨다는 점이 다르다.

궁금증

-  예시로 var name: String = "쿼카" 이 코드가 타입내에 존재하면 저장 프로퍼티라고 불렸는데 이 챕터에서는

   저장 인스턴스 프로퍼티라고 불려서 혼란스럽습니다. 타입 내에 타입 프로퍼티가 존재하게되면 일반적으로 프로퍼티라고

   칭했던게 인스턴스 프로퍼티로 불려지게 되는걸까요? 어떻게 받아들여야 할까요?

=> 프로퍼티란 특정 타입의 연관값이고 저장 프로퍼티는 특정 타입의 연관값을 저장하기 위한 변수나 상수이다. 그렇다면 저장 인스턴스 프로퍼티는 인스턴스의 값을 저장하기 위한 변수나 상수이다.

 

-  mutating은 값 타입에서 자신의 프로퍼티값을 수정할때 값을 변경한다는 것을 명시해야하는데 왜 값타입만 그런것일까?

 

Reference

- 야곰책 3판 Swift 프로그래밍