大器晩成

재정의(오버라이딩) - 메서드 본문

iOS/Swift 문법

재정의(오버라이딩) - 메서드

zerobugpark 2024. 11. 27. 01:20

 

메서드의 재정의

  • 상위 클래스 인스턴스 메서드 또는 타입 메서드 상관없이 기능을 추가하는 것도 가능합니다.
  • 오버라이딩 대상이 되는 메서드의 매개변수 개수나 타입, 그리고 반환타입은 변경할 수가 없습니다.
  • 상위 클래스에서 정의된 메서드의 반환 타입이 String이었다면 오버라이딩된 메서드 역시 반환 타입을 String으로 유지해야 합니다.
  • 클래스에서 메서드의 매개변수가 String, Int 두 개였다면 오버라이딩 된 메서드에서도 매개변수는 여전히 String, Int 두 개여야 하며, 순서도 변경해서는 안됩니다.
  • 메서드 오버라이딩을 통해 변경할 수 있는 것은 오로지 내부 구문들 뿐입니다. 매개변수 타입이나 반환타입은 반드시 그대로 유지해야 합니다.
class Warrior {
    var job: String =  "전사"
    var gender : String = "남"
    var items: [String] = ["낡은가죽바지", "두손도끼", "한손검"]
    
    func evolution() {
        print("전사로 전직했습니다.")
    }
    
    subscript(index: Int) -> String {
        get {
            if index > 2 {
                return "아이템이 없습니다."
            }
            return items[index]
        }
        set {
            items[index] = newValue
        }
    }

}

 

1.  상위 메서드를 하위 메서드에서 호출

class Hero: Warrior {
   
    override func evolution() {
        print("히어로로 전직했습니다.")
    }
}

let user = Hero()
user.evolution()

// result
전사로 전직했습니다.
히어로로 전직했습니다.
  • 하위  클래스에서 상위 클래스의 함수 호출 가능

2. 하위 메서드 실행 후 상위 메서드 호출

class Hero: Warrior {
   
    override func evolution() {
        print("히어로로 전직했습니다.")
        super.evolution()
    }
}

let user = Hero()
user.evolution()

// result
히어로로 전직했습니다.
전사로 전직했습니다.

 

3. 상위 구현 무시

class Hero: Warrior {
   
    override func evolution() {
        print("히어로로 전직했습니다.")
    }
}

let user = Hero()
user.evolution()

// 출력: 히어로로 전직했습니다.
  • 상위 기능을 무시하고 새롭게 구현하는 것도 가능합니다.
    (제약 없음 - 메서드 이름만 동일하고 완전히 새롭게 구현 가능하다고 생각하면 됩니다.)
  • 다만, 기능을 추가하는 구현을 선택할 시에 상위구현의 기능을 먼저 실행할지의 여부는 개발자의 선택입니다.

4. 서브스크립트 재정의

class Hero: Warrior {
   
    override func evolution() {
        print("히어로로 전직했습니다.")
    }
    
    override subscript(index: Int) -> String {
        get {
            if index > 2 {
                return "전설급아이템."
            }
            return super[index]
        }
        set {
            super[index] = newValue
        }
    }
}

let user = Hero()
user[0] = "바지"
user[3] // 낡은 가죽 바지

자식클래스에서 값을 바꿨다고 해서 user2의 Warrior에 영향을 미치는 것은 아니다. (서로 독립적인 공간)
let user2 = Warrior()
user2[0] // 낡은 가죽 바지


// 타입 캐스팅을 통한 변경된 값 확인
// (이때, 타입캐스팅한 Warrior랑 User2의 Warrior는 전혀 다른 객체이다)
(user as Warrior).items[0] // 바지
  • 상위의 서브스크립트를 구현했기 때문에, 대괄호가 사용이 가능합니다.
  • 그래서 하위에서 구현할 때는 super[Index] 사용하면 상위 클래스에 정의된 subscript 메서드를 호출한다.
  • 서브스크립트 구문에서 값을 읽거나 쓰는 items 속성은 상위 클래스에 저장되어 있습니다. 그렇기 때문에, 하위 클래스에서는 처리할 수 없습니다. (저장속성은 저장속성으로 재정의가 불가능)
  • 상위 서브스크립트를 호출함으로써, 값을 변경할 수 있습니다.

 

5. super[Index] vs super.dates[Index]

class A {
    var datas = ["1", "2", "3", "4", "5"]
    
    subscript(index: Int) -> String {
        get {
            if index > 4 {
                return "0"
            }
            return datas[index]
        }
        set {
            datas[index] = newValue
        }
    }
}

class B: A {
    override subscript(index: Int) -> String {
        get {
            if index > 4 {
                return "777"
            }
            return super[index]  // 부모 클래스의 서브스크립트를 호출
        }
        set {
            super[index] = newValue  // 부모 클래스의 서브스크립트에 값을 설정
        }
    }
    
    func subscriptTest() {
        // super[index]는 부모 클래스의 서브스크립트를 호출합니다.
        print(super[2])  // "3"이 출력됨 (datas[2]는 "3"이라서)
        
        super[2] = "99"   // 부모 클래스 서브스크립트를 통해 "99"로 값 설정
        print(datas)      // ["1", "2", "99", "4", "5"] (datas[2]가 "99"로 변경됨)
        
        // super.datas[index]는 부모 클래스의 datas 배열에 직접 접근
        print(super.datas[2])  // "99"가 출력됨 (datas[2]는 이제 "99")
    }
}

let sub = B()
sub.subscriptTest()
  • super[index]는 부모 클래스의 서브스크립트를 호출합니다.
    - 서브스크립트 문법을 사용하여 부모 클래스에서 정의한 서브스크립트 호출하는 것으로 부모 클래스에 정의된 subscript 메서드를 실행합니다
  • super.datas[index]는 부모 클래스의 datas 배열에 직접 접근합니다.
  • 배열에 직접 접근하는 것은 서브스크립트 문법을 사용하는 것이 아닙니다. 즉, dates 배열의 특정 인덱스에 접근하는 것입니다. 이는 서브스크립트와는 별도로 배열의 값에 직접 접근하는 방식입니다.

 


 

참고사항

- 메서드의 경우에는 오버라이딩 제약 조건으로 매개변수 타입이나 반환 타입을 그대로 유지해야 하는 이유는 스위프트가 메서드 오버로딩(Overloading)을 지원하기 때문입니다. 이때, 기준이 되는 것이 매개변수의 타입과 종류입니다. 즉 같은 이름의 메서드라도 정의된 매개변수의 타입이 다르면 서로 다른 메서드를 처리하는 것이 오버로딩입니다.

func makeNoise()
func makeNoise(param: Int)
func makeNoise(param: String)
func makeNoise(param: Double) -> String
func makeNoise(param: Double, append: String)
func makeNoise(param: Double, appendix: String)
  • makeNoise의 예를 살펴보면 이 메서드는 다음과 같이 하나의 이름으로 된 여러 개의 메서드로 정의될 수 있습니다. 
    물론 이들 메서드는 컴파일러에 의해 서로 다른 메서드로 처리됩니다..
  • 스위프트에서 메서드는 이름뿐만 아니라 매개변수의 개수와 타입을 기준으로 하여 유일성 여부를 구분합니다.
  • 따라서 이름이 같고 매개변수의 개수까지 일치하더라도  타입이 다르면 서로 다른 메서드로 간주합니다.
  • 매개변수명까지 메서드의 정의에 포함되므로 매개변수의 개수, 타입이 모두 일치하여도 매개변수명이 다르면 새로운 메서드로 정의됩니다.
  • 같은 메서드 이름이지만 매개변수의 변화만으로 새로운 메서드를 만들어 적재할 수 있도록 지원하는 문법이 오버로딩입니다.

오버라이딩과 오버로딩 구분

  • 오버라이딩을 덮어쓰기라는 개념으로 정의하는 것이 좀 더 직관적일 수 있습니다.
  • 오버라이딩된 메서드나 프로퍼티는 해당 클래스를 상속받는 모든 자식 클래스에 적용됩니다.
  • 적용된 자식 클래스를 다시 서브클래싱했을 때도 마찬가지입니다. 하지만 부모클래스는 오버라이딩 영향을 받지 않습니다.
  • 물론 부모 클래스를 상속받은 다른 형제뻘 클래스들도 오버라이딩 된 메서드나 프로퍼티는 적용되지 않습니다.
// 하나는 프로퍼티를 오버라이딩한 Car 클래스를 상속받고, 
// 또 다른 하나는 기본 클래스인 Vehicle을 상속받습니다.
// 두 가지 클래스를 각각 서브클래싱한 결과가 어떻게 다른지 확인해봅시다.


class Vehicle {
    var currentSpeed = 0.0
    
    var description: String {
        return "시간당 \(self.currentSpeed)의 속도로 이동하고 있습니다."
    }
    
    func makeNoise() {
        
    }
}
class Car: Vehicle {
    var gear = 0
    var engineLevel = 0
    
    override var currentSpeed: Double {
        get {
            return Double(self.engineLevel * 50)
        } set {
            super.currentSpeed = newValue
        }
    }
    
    override var description: String {
        get {
            //print("슈퍼: \(super.currentSpeed) ")
            return "Car : engineLevel = \(self.engineLevel), so currentSpeed = \(self.currentSpeed)"
        } set {
            print("New Value is \(newValue)") //descrpition. 프로퍼티에 값을 할당하면 set 구문이 실행
        }
    }
    
}
class HybridCar : Car {
    // 아무것도 추가로 선언하지 않음
}

class KickBoard : Vehicle {
    // 아무것도 추가로 선언하지 않음
}

let h = HybridCar()
//h.engineLevel = 40
//h.currentSpeed = 20
h.description //"Car : engineLevel = 0, so currentSpeed = 0.0

let k = KickBoard()
k.description //"시간당 0.0의 속도로 이동하고 있습니다."
  • Car를 상속받는 HybridCar는 Vehicle과 Car의 메서드 및 프로퍼티를 상속받고 있습니다.
  • Vehicle을 상속받는 KickBoard는 Vehicle의 메서드와 프로퍼티만 상속받고 있습니다.
  • 공통된 부모클래스를 사용한다고 해도, 어떤 클래스를 상속받냐에 따라 자식클래스가 가질 수 있는 프로퍼티와 메서드의 차이가 발생합니다.
728x90