[Swift] 왜 class 는 편의 이니셜라이저에 convenience 키워드를 붙여야 할까?

Mason Kim·2023년 7월 3일
0

swift

목록 보기
5/7

참고: https://forums.swift.org/t/why-does-swift-need-the-convenience-keyword/55428

  • ( = struct 도 이니셜라이저에서 다른 이니셜라이저를 호출 할 수 있는데 왜 convenience를 붙이지 않을까?)
  • Initializer의 상속이 있는 다른 언어들과, Swift의 차이가 존재
    • Swift 에서는 서브클래스에서 이니셜라이저를 직접 구현하더라도 상위클래스 이니셜라이저의 subset(하위집합)을 자동으로 상속해서 가져옴.
    • 그렇기에 하위 클래스에서 상위 클래스의 이니셜라이저를 호출함으로서 상위 클래스의 저장속성들을 초기화 해야 함
    • 그런데, 하위 클래스에서는 상위 클래스의 이니셜라이저가 다른 이니셜라이저를 호출하는지 아닌지를 알 수 없다.
    • 그렇기에 convenience 키워드를 붙여줌으로서 하위 클래스에서 다른 이니셜라이저를 호출하지 않는, 해당 단계의 저장 속성을 모두 초기화하는 지정 이니셜라이저를 호출하게 하는 것.
    • ❓ 그런데, 하위 클래스에서 상위 클래스의 편의 이니셜라이저를 호출하더라도..
      결국은 해당 상위 클래스의 편의 이니셜라이저가 지정 이니셜라이저를 호출하는 것 아닌가?
  • convenience 키워드가 없다면?
    • convenience 키워드는 현재 단계의 이니셜라이저만, 현재 단계에서만 호출 할 수 있게 함

    • 만약 하위 클래스에서 상위 클래스의 convenience 이니셜라이저를 호출할 수 있다면??

      class SuperClass {
      		var name: String
      		init() { self.init(name: "name") }  // convenience 키워드를 붙이지 않았음.
      		init(name: String) { self.name = name }
      }
      
      class SubClass: SuperClass {
      		override init(name: String) { super.init() } // 상위 클래스의 init을 호출
      }
      
      let subClass = SubClass(name: "name")
    • https://jercy.tistory.com/2 블로그에서는, “위와같이 호출할경우 무한루프에 빠질수 있습니다.”
      라고 하는데, 왜 무한루프에 빠지지??
      - SubClass 의 init(name:) 호출 → SuperClass의 init() 호출 → SuperClass의 init(name:) 호출??

  • ❓ final 키워드로 상속을 불가하게 한다면? 어떤 차이가 있을까?

이에 대한 개인적인 가설

class SuperClass {
    var name: String

    init(name: String) {
        self.name = name
        self.printText()
    }

    func printText() {
        print("SuperClass printText")
    }
}

class SubClass: SuperClass {
    override init(name: String){
        super.init(name: name)
    }

    override func printText() {
        print("SubClass printText")
    }
}

let subClass = SubClass(name: "name")
  • SubClassinit(name:)SuperClassinit(name:) 호출.
💡 `SuperClass` `init(name:)` 에서 `printText()` 가 호출되는데… 실제로 print 는 `"SubClass printText"` 가 호출되는 것을 알 수 있음!!
class A {
    init(x: Int) {
        print("A.init(x:)")
    }
    
    convenience init(y: Int) {
        print("A.init(y:)")
        self.init(x: y)
    }
}

class B: A {
    override init(x: Int) {
        print("B.init(x:)")
        super.init(y: x)
    }
}

B(y: 0) // A.init(y:) -> B.init(x:) -> A.init(x:)...
// Allowing `B.init(x:)` to call `A.init(y:)` will cause an infinite loop.

즉 정리하자면,

상속 구조에서는 메서드 디스패치 (테이블디스패치)가 적용되고,
하위 클래스에서 상위클래스의 편의생성자를 호출한다면
해당 상위클래스의 편의생성자에서 상위클래스의 지정생성자를 호출할 때 해당 생성자를 하위 클래스에서 오버라이드 한 생성자가 호출 할 수 있기 때문에 무한 루프에 빠질 수 있다"

그렇기 때문에 하위 클래스의 생성자에서 상위클래스의 지정생성자를 호출하도록 강제해야 하고,
지정생성자와 편의생성자를 구분짓기 위해 convenience 키워드를 붙여준다!

혹시 다른 어떤 의견이 있으신 분은 댓글 남겨주세요!

profile
iOS developer

0개의 댓글