[Swift] 지정생성자 & 편의생성자

임승섭·2023년 6월 23일
0

Swift

목록 보기
8/35

구조체 생성자

  • 오버로딩(같은 이름인데, parameter를 다르게 함)을 이용해서 여러 가지 생성자를 만들 수 있지만, 올바른 구현 방법은 아니다

지정 생성자

  • 값 타입 (value type) (구조체)의 경우, 자체 지정생성자를 작성할 때,
    생성자 내에서 self.init을 이용해서 다른 생성자 호출할 수 있다.
    • 구조체는 다른 생성자를 호출하는 방식이 가능하다!
  • 코드가 중복되지 않기 때문에 이 방법이 더 올바르다.
struct Color1 {
    let red, green, blue: Double
    
    // 코드가 중복되지 않고, 하나의 생성자만 호출한다.
    
    init() {      
        //red = 0.0
        //green = 0.0
        //blue = 0.0
        self.init(red: 0.0, green: 0.0, blue: 0.0)			// 다른 생성자를 호출
    }

    init(white: Double) {   
        //red   = white
        //green = white
        //blue  = white
        self.init(red: white, green: white, blue: white)	// 다른 생성자를 호출
    }
    
    init(red: Double, green: Double, blue: Double) {		// 하나의 생성자
        self.red   = red
        self.green = green
        self.blue  = blue
    }
}

클래스 생성자

지정(designated) 생성자 vs 편의(convenience) 생성자

  • 지정생성자 : 일반적인 initializer (init)
  • 편의생성자 : 다른 생성자를 호출하는 initializer
  • 구조체에서는 편의생성자라는 개념이 없다.
class Color2 {
    let red, green, blue: Double
    
    // 편의생성자
    convenience init() {	// 다른 생성자를 호출하는 생성자에는 convenience를 붙여줘야 한다.
        self.init(red: 0.0, green: 0.0, blue: 0.0)
        //self.init(white: 0.0)
    }

	// 편의생성자
    convenience init(white: Double) {
        //red   = white
        //green = white
        //blue  = white
        self.init(red: white, green: white, blue: white)
    }
    
    // 지정생성자
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
}

var c = Color2( 	// 이렇게 타이핑 하면, 어떤 생성자를 사용할 지 선택이 가능하다
  • 지정 생성자는 모든 속성을 초기화해야 한다 (당연)
  • 편의 생성자는 모든 속성을 초기화 할 필요가 없다 (지정 생성자에 의존)
  • 클래스는 상속을 지원하기 때문에
  • 여러 지정 생성자를 지원하면 상속 관계에서 개발자의 실수 가능성이 있다
  • 따라서, 초기화 과정을 조금 간편하게 하고, 실수 여지를 줄이기 위한 생성자이다
  • (이미 모든 속성을 초기화하는 지정생성자가 있으면)
    모든 속성을 초기화 하지 않으면 -> 편의생성자로 만드는 것이 낫다 (복잡도, 실수)
  • 결국, 생성자의 가능한 중복을 없애고, 다른 지정 생성자를 호출하는 패턴으로 구현해야 한다

상속 관계에서의 예시

class Aclass {
    var x: Int
    var y: Int
    
    // 지정생성자
    init(x: Int, y: Int) {    
        self.x = x
        self.y = y
    }
    
    // 편의생성자 (상속이 되지 않는다)
    convenience init() {     
        self.init(x: 0, y: 0)
    }
}

class Bclass: Aclass {
    
    var z: Int	// 저장 속성 추가 -> 반드시 초기화해야 함
    
    // 하위에서 지정생성자를 새로 정의 (지정생성자는 반드시 재정의해야 함)
    init(x: Int, y: Int, z: Int) {    
    	// (필수) 나의 저장 속성의 값을 초기화해야 한다
        self.z = z                
        
        //self.y = y                // 불가 (메모리 셋팅 전) (super init 호출 전에는 불가)
        // 아직 y라는 데이터 공간이 생기지 않았다
        
        // (필수) 상위 지정생성자를 호출해줘야 한다
        super.init(x: x, y: y)     
        //self.y = 7				// 여기는 가능 (메모리 셋팅 완료)
    }

// 즉, 메모리 세팅의 순서
// 1. self.z = z에서 z 메모리 세팅
// 2. super.init(x: x, y: y)에서 y 메모리 세팅
    
    // 편의생성자를 다시 정의 (왜냐하면 편의생성자는 상속이 되지 않기 때문)
    convenience init(z: Int) {
        //self.z = 7     //==========> self에 접근불가
        self.init(x: 0, y: 0, z: z)		// 메모리에 찍었어
        //self.z = 7	// 여기서는 접근 가능 - 값 바꾸는 것도 할 수 있다.
        // 저장속성의 값들을 다 초기화하고 난 후에는, 내 맘대로 커스텀(값 변경)할 수 있다
        // -> 선택 사항이고, 거의 이럴 일은 없다
    }
    
    convenience init() {
        self.init(z: 0)
    }
    
    func doSomething() {
        print("Do something")
    }
}

let a = Aclass(x: 1, y: 1)	// (1, 1)으로 초기화
let a1 = Aclass()			// (0, 0)으로 초기화

let b = Bclass(x: 1, y: 1, z: 1)	// (1, 1, 1)
let b1 = Bclass(z: 2)				// (0, 0, 2)
let b2 = Bclass()					// (0, 0, 0)
  • 지정생성자는 반드시 재정의
    • 반드시 새로운 저장속성 초기화 (self.z = z)
    • 반드시 상위 지정생성자 호출 (super.init(x: x, y: y)
  • 편의생성자는 상속이 되지 않는다

호출 규칙

  • Delegate up
    • 지정생성자가 상위의 지정생성자 호출하는거
    • sub-class의 지정생성자는 super-class의 지정생성자를 반드시 호출해야 한다
    • 하위 클래스의 저장속성을 초기화(self.z = z)하고,
      상위클래스의 저장속성을 초기화(super.init(x: x, y: y)
    • 하위의 메모리를 먼저 찍어내고, 상위의 메모리를 찍어낸다 (??)
  • Delegate across
    • 편의 생성자가 호출하는거
    • 편의생성자는 동일한 class에서 다른 initializer를 호출해야 하고,
      궁극적으로 지정생성자를 호출해야 한다
    • 결국, 지정생성자만이 해당 단계의 모든 저장값을 초기화

지정생성자 / 편의생성자 상속과 재정의 규칙

  • 생성자는 기본적으로 상속되지 않고 재정의 원칙
    • 재정의 : 동일한 이름을 가진 생성자를 구현하는 것
  • 상위 지정생성자현재 단계의 저장 속성을 고려하기
    1. 상위 지정생성자
      • (상위) 지정생성자 : 재정의 필수 고려 (1. 지정, 2. 편의, 3. 안한다(??))
      • (상위) 편의생성자 : 재정의 불가 (호출 불가)
    2. 현재 단계의 저장 속성
      • (지정 생성자 내에서) 모든 저장 속성 초기화 및 상위 지정 생성자 호출
      • (편의생성자 내에서) 현재 단계의 지정생성자 호출
// 기본(Base) 클래스

class Aclass {
    var x = 0
    
    // init() {}                // (없는 것처럼 보이지만) 기본 생성자가 자동으로 제공
}
let a = Aclass()

// 상위의 지정생성자가 뭐가 있을까 ([1단계]) ⭐️
// init()



class Bclass: Aclass {
    var y: Int	// 새로운 저장 속성

    
    // [1단계] 상위의 지정생성자 고려 ==============================
    // 상위에 동일한 이름이 있으므로 재정의 해야함 (이 형태는 안됨)
//    init() {
//
//    }
    
    // (선택 1) 지정생성자로 재정의
    override init() {       // 상위 클래스와 "이름이 동일한 생성자" 구현은 재정의만 가능함(올바른 재정의) (지정생성자로 구현)
        self.y = 0		// [2단계] 나의 저장속성 고려
        super.init()	// 하위의 지정생성자는 반드시 상위의 지정생성자를 호출해야 한다 (deligate up)
    }
    
    
    // (선택 2) 서브클래스에서 편의생성자로 구현해보기
    // 상위 클래스와 "이름이 동일한 생성자" 구현은 재정의만 가능함(올바른 재정의) (지정생성자 필요 -> 맨 아래에 있는 지정생성자)
//    override convenience init() {
//        self.init(y: 0)		// 밑에 있는 현재단계 생성자를 이용
//    }
    
    // (선택 3) 재정의 하지 않을 수도 있음 (상속안함)
    
    
    // [2단계] (현재단계의) 생성자 구현 ============================
    
    init(y: Int) {		// 동일한 생성자를 다른 애들이 상속받을 수도 있다.
        self.y = y
        super.init()
    }
    
    
    // 지정 생성자가 여러 개 있을 수 있다. (서로 같은 지정 생성자를 호출하기만 하면 아무 이상이 없다)

}
let b = Bclass()


// 상위의 지정생성자가 뭐가 있을까 ⭐️
// init()
// init(y: Int)


class Cclass: Bclass {
    var z: Int	// 저장속성 추가
    
    
    // [1단계] 상위 지정생성자 고려
    override init() {      // 상위 클래스와 "이름이 동일한 생성자" 구현(올바른 재정의)
        self.z = 0
        super.init()       // 2단계 값설정(커스텀)없고, 상위구현에 기본 init() 만 있는 경우 생략가능(암시적 요청)
    }
    
    
    override init(y: Int) {	// 해도 되고 안해도 되고
    	self.z = 0
        super.init(y: y)	// 상위의 지정생성자를 호출해주기만 하면 된다!
    }
   
    
    init(z: Int) {
        self.z = z
        super.init()       // 2단계 값설정(커스텀)없고, 상위구현에 기본 init() 만 있는 경우 생략가능(암시적 요청)
    }
    
    // 지정생성자가 3개가 되어버렸다 -> 아무 문제 없음
}
let c = Cclass()
let d = Cclass(z: 1)

Review (공식 문서 예제)

class Vehicle {
    
    var numberOfWheels = 0		// 저장 속성
    
    var description: String {	// 	계산 속성
        return "\(numberOfWheels) wheel(s)"
    }
    
    // init() { }				// 기본 생성자 자동 제공
}
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")    // Vehicle: 0 wheel(s)


// 상위의 지정생성자가 뭐가 있을까 ⭐️
// init()



class Bicycle: Vehicle {
    
    // 생성자가 상위클래스의 이름과 동일하기 때문에 재정의 키워드(override) 필요
    // 저장속성을 따로 만들지 않았기 때문에 메모리 세팅 필요 x
    // 상위 지정생성자 호출하여 메모리 초기화 후, 상위에서 구현한 속성에 접근하여 2로 셋팅(2단계 값설정)
    override init() {
        super.init()            // 수퍼클래스 호출 반드시 필요
        numberOfWheels = 2      // 메모리 세팅 후, 초기화의 2단계 값설정 가능
    }
    
}
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")    // Bicycle: 2 wheel(s)



class Hoverboard: Vehicle {
    
    var color: String
    
    // (읽기) 계산 속성 재정의
    override var description: String {
        return "\(super.description) in a beautiful \(color)"
    }
    
//    override convenience init() {
//        self.init(color: "빨간색")
//    }
    
    // (1) 재정의를 할 지
    override init() {
    	self.color = "빨간색"
        super.init()
    }
    
    // (2) 편의생성자로 구현할 지
    override convenience init() {
    	self.init(color: "빨간색")
    }
    
    // (3) 구현하지 않을 지
    
    
    
    
    init(color: String) {
        self.color = color      // (현재 클래스) 저장 속성 초기화
        super.init()          // =====> 여기서 암시적으로 호출됨 (지워도 된다)
    }
    
}
// 생성자에서 Hoverboard 클래스는 색상 속성만 설정
// 2단계 값설정(커스텀)없고, 상위구현에 기본 init() 만 있는 경우 생략가능(암시적 요청) ⭐️
let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")     // Hoverboard: 0 wheel(s) in a beautiful silver

예외사항 : 자동 상속

  • 지금까지 배운거
    • 생성자 : 기본적으로 상속되지 않고, 재정의가 원칙이다
      • 그 중 지정생성자만 재정의하고, 편의생성자는 재정의가 불가하다
  • 예외사항
    • 자동 상속
  • 지정생성자 자동 상속
    • 저장속성의 기본값 설정 및 어떤 재정의도 하지 않았을 때
      (새 저장속성이 아예 없거나,
      또는 새 저장속성이 있는데 이미 기본값 설정한 경우)
  • 편의생성자 자동 상속
    • 상위 지정생성자를 모두 상속
      • 지정생성자 모두 자동상속(위에 있는거) or 지정생성자 모두 재정의
class Food {
    var name: String
    
    init(name: String) {     // 지정생성자
        self.name = name
    }
    
    convenience init() {     // 편의생성자 ===> 지정생성자 호출
        self.init(name: "[Unnamed]")
    }
}
let namedMeat = Food(name: "Bacon")   	// Bacon
let mysteryMeat = Food()      			// [Unnamed]



// 상위의 지정생성자가 뭐가 있을까 ⭐️
// init(name: String)    지정생성자
// convenience init()    편의생성자

class RecipeIngredient: Food {
    var quantity: Int		// 새로운 저장 속성, 기본값x
    
    //
    init(name: String, quantity: Int) {  // 모든 속성 초기화
        self.quantity = quantity
        super.init(name: name)
    }
    
    
    // 지정생성자를 편의생성자로 재정의. 근데 지정생성자가 1개밖에 없으니까 "상위의 지정생성자를 모두 재정의"한 것이 된다 -> 예외사항
    // => 자동으로 상위의 편의생성자도 상속하게 된다
    override convenience init(name: String) {    // 상위 지정생성자를 편의생성자로 재정의 ===> 지정생성자 호출
        self.init(name: name, quantity: 1)
    }
    
    // convenience init() { }      // 자동 상속 (예외 규칙)
}



let oneMysteryItem = RecipeIngredient()    //  편의생성자
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)



// 상위의 지정생성자 ⭐️
// init(name: String, quantity: Int)          지정생성자
// override convenience init(name: String)    편의생성자
// convenience init()                         편의생성자

class ShoppingListItem: RecipeIngredient {
    
    var purchased = false       // 저장 속성 추가. 근데 기본값도 설정 -> 에러가 날 확률이 없고, 지정생성자를 구현할 필요 없다
    
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ✔" : " ✘"
        return output
    }
    
    // init(name: String, quantity: Int) {}    // 지정생성자 모두 자동 상속
    
    // 상위의 지정생성자를 모두 자동 상속했기 때문에 => 편의생성자도 자동 상속
    // convenience init(name: String) {}       // 따라서 ====> 편의상속자도 모두 자동 상속됨
    
    // convenience init() {}                   // 따라서 ====> 편의상속자도 모두 자동 상속됨
    
}

var breakfastList = [
    ShoppingListItem(),
    ShoppingListItem(name: "Bacon"),
    ShoppingListItem(name: "Eggs", quantity: 6),
]

0개의 댓글