조앤의 기술블로그

[Swift] Initializers #2 본문

Study/Swift

[Swift] Initializers #2

쬬앤 2020. 2. 26. 14:42

Class Initializer

클래스의 이니셜라이저에는 두가지 종류가 있습니다.

Designated 이니셜라이저와, Convenience 이니셜라이저 입니다. 

 

- Designated Initializer

클래스에 최소 1개 이상 있어야 하고, 클래스가 가진 모든 속성을 초기화해야 합니다. 

init (parameters) {
	initialization
}

 

- Convenience Initializer

다양한 방법으로 초기화하기 위한 생성자이고, 필요한 속성을 초기화하고, 동일 클래스의 다른 생성자를 호출합니다. 

사용하는 이유는, 다른 생성자를 호출하는 패턴을 통해 가능한 코드의 중복을 없애기 위함입니다. 

convenience init (parameters) {
	initalization
}

 

[예제]

class Position {
	var x: Double
    var y: Double
    
    // Designated init - 모든 속성 초기화
    init(x: Double, y: Double) {
    	self.x = x
        self.y = y
    }
    
    // Convenience init - 동일 클래스 designated init 호출
    convenience init(x: Double) {
    	self.init(x: x, y: 0.0)
    }
}

convenience initializer을 통해 동일 클래스의 designated initializer을 호출함으로써, 코드의 중복을 막을 수 있게 되었습니다. 

이는 프로그램의 유지보수와 디버깅에 도움이 됩니다. 

 

[Initializer Inheritance]

이니셜라이저는 subclass로 상속될 수 있지만, 그 조건이 까다롭습니다. 

규칙 1.
   subclass의 모든 속성이 기본 값으로 초기화 되어 있고, designated 이니셜라이저를 직접 구현하지 않았다면, 
super class에 있는 모든 designated 이니셜라이저가 상속된다.

규칙 2.
    subclass가 모든 designated 이니셜라이저를 상속받았거나 오버라이딩했다면,
모든 convenience 이니셜라이저가 상속된다.

 

[예제1]

class Figure {
	var name: String
    init(name: String) {
    	self.name = name
    }
    func draw() {
    	print("draw \(name)")
    }
}

class Rectangle: Figure {
	var width: Double = 0.0
    var height: Double = 0.0
}

Rectangle 클래스에는 규칙1.에 따라 designated 이니셜라이저가 상속됩니다. 

 

[예제2]

class Figure {
	var name: String
    init(name: String) {
    	self.name = name
    }
    func draw() {
    	print("draw \(name)")
    }
    convenience init() {
    	self.init(name: "unknown")
    }
}

class Rectangle: Figure {
	var width: Double = 0.0
    var height: Double = 0.0
    
    init(name: String, width: Double, height: Double) {
    	// self.name = name  - 상속받은 속성이기 때문에 super.init(name:)을 통해 호출해야 합니다.
        self.width = width
        self.height = height
        super.init(name: name)
    }
    
    override init(name: String) {
    	width = 0
        height = 0
        super.init(name: name)
    }
}

Rectangle 클래스는 규칙2. 에 따라 Figure클래스의 모든 convenience 이니셜라이저를 상속받게 됩니다. 

 

Required Initializer

 

 

 

Initializer Delegation

Initialiaer Delegation은 초기화 코드에서 중복을 최대한 제거하여, 모든 속성을 효율적으로 초기화하기 위해서 사용합니다. 

value type과 class type이 서로 다른 방식으로 구현됩니다. 

 

[Value Type]

value type은 상속이 허용되지 않고, 이니셜라이저 종류가 1개이기 때문에 delegation이 단순합니다.

struct Size {
	var width: Double
    var height: Double
    
    init(w: Double, h: Double) {
    	width = w
        height = h
    }
    init(value: Double) {
    	self.init(w: value, h: value)
    }
}

밑의 이니셜라이저에서처럼 동일 클래스의 다른 이니셜라이저를 호출하는 방식으로 구현됩니다. 

이렇게 쓰는 이유는, 유지보수와 디버깅에 좋기 때문입니다. 

 

만약, 각 이니셜라이저에 개별적으로 초기화 구문을 선언했다면, 초기화 규칙이 바뀌었을 때, 모든 규칙을 변경해야 하므로, 유지보수에 좋지 않습니다. 하지만 이처럼 다른 이니셜라이저를 호출하는 패턴을 이용하면 초기화 규칙이 변경되어도 유지보수에 큰 문제가 없게 됩니다. 

 

즉, 모든 속성을 초기화하는 하나의 이니셜라이저를 구현하고, 다른 이니셜라이저가 이를 호출하도록 구현하도록 하는 것입니다. 주의할 점은 이니셜라이저에서 모든 속성이 초기화되어야 한다는 것입니다.

초기화가 완료되었을 때 모든 속성이 초기화되면 어떠한 이니셜라이저를 호출해도 상관없습니다. 

 

[Class Type]

클래스 타입은 상속이 허용되므로, 모든 이니셜라이저가 올바른 순서로 호출될 수 있도록 규칙이 적용됩니다. 

출처: swift 공식 홈페이지 - Initializer

Rule1.
    A designated initializer must call a designated initializer from its immediate superclass. (Delegate Up)
    (모든 designated 이니셜라이저는 슈퍼클래스의 designated 이니셜라이저를 호출해야 한다.)
Rule2.
    A convenience initializer must call another initializer from the same class. (Delegate Across)
    (모든 convenience 이니셜라이저는 동일 클래스의 다른 이니셜라이저를 호출해야 한다.)
Rule3.
    A convenience initializer must ultimately call a designated initializer.
    (모든 convenience 이니셜라이저는 최종적으로 (동일 클래스의) designated 이니셜라이저를 호출해야 한다.)

 

[예제]

class Figure {
	let name: String
    init(name: String) {
    	self.name = name
    }
    convenience init() {
    	self.init(name: "unknown")
    }
}

class Rectangle: Figure {
	var width = 0.0
    var height = 0.0
    
    init(n: String, w: Double, h: Double) {
    	width = w
        height = h
        super.init(name: n)
    }
    convenience init(value: Double) {
    	self.init(n: "Rect", w: value, h: value)
    }
}

class Square: Rectangle {
	convenience init(value: Double) {
    	self.init(n: "Square", w: value, h: value)
     } // 상속받은 Rectangle의 designated 이니셜라이저 호출하는 것.
     
     convenience init() {
     	self.init(value: 0.0)
    }
}