애플 스위프트 언어 개발 가이드를 기반으로 초기화 관련된 내용을 정리했습니다. 초기화가 복잡합니다. 크게 복잡하지 않을 수도 있는 내용이지만 언어마다 조금씩 다른 부분을 잘 정리했습니다.
http://cafe.naver.com/architect1 에서 스터디 진행중입니다.
기본적인 뼈대는 http://swift.leantra.kr/ 를 기반으로 합니다.
5. 사용자 정의 초기화 - 초기화 파라메터
struct Celsius {
var temperatureInCelsius: Double = 0.0
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (faherenheit - 32.0)/1.8
}
}
6. 사용자 정의 초기화 - 지역 파라메터
struct Color {
let red = 0.0, green = 0.0, blue = 0.0
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
7. 사용자 정의 초기화 - 외부 파라메터
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let veryGreen = Color(0.0, 1.0, 0.0)
init 의 파라메터는 기본적으로
지역 파라메터 + 외부 파라메터 이다.
9. class SurveyQuestion {
var text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
println(text)
}
}
옵셔널 속성 타입
let cheeseQuestion =
SurveyQuestion(text: “Do you like
cheese?”)
cheeseQuestion.ask()
cheeseQuestion.response = “Yes, I do
like cheese.”
10. class SurveyQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
println(text)
}
}
초기화 과정중에 상수 속성을 변경하기
let cheeseQuestion =
SurveyQuestion(text: “How about
beets??”)
cheeseQuestion.ask()
cheeseQuestion.response = “I also
like beets.”
11. 기본 초기자
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
모든 속성은 기본 값을 가져야 합니다.
옵셔널은 자동 기본값으로 nil을 받습
니다.
12. 구조체 타입의 멤버 단위 초기화
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0,
height: 2.0)
모든 속성에 기본값이 있고,
사용자 정의 초기자가 없으면
=> 구조체 타입은 자동으로 멤버 단위
초기자를 가진다.
13. 값 타입 = 구조체, 열거자
값 타입의 초기자 대리자
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
14. 값 타입의 초기자 대리자
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size:Size) {
self.origin = origin
self.size = size
}
init(center: Point, size:Size) {
let originX = center.x - (size.width /2)
let originY = center.y - (size.height /2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
정사각형을 그리는 방법은 3가지가 있다.
1. (0, 0)에 위치하는 크기 0 짜리 정사각형
2. (x, y)에 위치하는 크기 n 짜리 정사각형
3. (x, y)를 중심으로하는 크기 n짜리 정사각
형
이 초기화 시나리오이다. 3번 째 초기자에
서, 2번째 초기자를 호출 또는 대리 한다.
16. 클래스 상속과 초기화
지정 초기자
주 초기자
모든 속성을 완전히 초기화
+
적절한 부모 클래스 초기자 호출
모든 클래스는 반드시 하나 이상의 지정 초기자를
가져야 한다.
편의 초기자
부 초기자
같은 클래스내의 지정 초기자를 호출하는 초기자
호출하는 지정 초기자의 몇몇 파라메터를 기본으
로 하는 초기자
특정 용도나 입력 값 타입에 대한 클래스 인스턴스
를 만드는 초기자
18. 1 Phase
해당 클래스가 가지는 저장속성에 초기값을 할당 한다.
2 Phase
클래스 인스턴스를 사용할 준비가 되기 전까지 속성 값을 변경한다.
두 단계 초기화
19. 안전 점검 1
부모 클래스의 초기자를 위임하기 전에, 내 클래
스의 속성이 초기화 되었는지 확인한다.
안전 점검 2
지정 초기자는 상속받은 속성에 값을 할당하기 전
에 부모 클래스의 초기자를 수행해야 합니다. (값
이 부모 클래스의 초기화 중에 덮어씌워진다.)
안전 점검 3
편의 초기자는 속성에 값을 할당하기전에 다른 초
기자를 대리 수행 해야 합니다. 그렇지 않으면 편
의 초기자가 할당한 값은 지정 초기자에 의해 덮어
씌워집니다.
안전 점검 4
초기자는 인스턴스 메소드를 호출 할 수 없습니다.
초기화 첫 단계가 끝나기 전에는 self도 참조 할 수
없습니다.
두 단계 초기화
22. swift의 자식 클래스는 기본적으로 부모 클래스의 초기자를 상속 받지 않습니다
.
(=! object-C)
초기자 상속과 오버라이딩
23. 초기화 중간에 뭔가 수정 하기 위해서, 부모 클래스와 같은 초기자를 가진 자식
클래스가 필요 할 때, 자식 클래스에 같은 초기자를 오버라이딩 해서 구현 할 수
있습니다.
오버라이딩 하는 초기자가 지정 초기자라면, 오자식 클래스에서 구현체를 오버
라이드 하고, 오버라이딩 하는 버전에서 부모 버전의 초기자를 호출 하도록 할
수 있습니다.
초기자 상속과 오버라이딩
24. Rule 1
자식 클래스가 지정 초기자를 정의하지 않는다.
Rule 2
자식 클래스가 부모 클래스의 모든 지정 초기자를 구현한다.
초기자 자동 상속
25. 편의 초기자는 convenience를 사용합
니다.
convenience init(parameters) {
statements
}
클래스의 지정 초기자는 값 타입을 위
한 단순 초기자와 같은 방식으로 작성
한다.
init(parameters) {
statements
}
지정 초기자, 편의 초기자의 문법
26. class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: “[Unnamed]”)
}
}
지정 초기자와 편의 초기자 실습
27. class RecipeIngredient: Food {
var quantity: Int
init(name:String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
지정 초기자와 편의 초기자 실습
32. class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = “(quantity) x
(name.lowercaseString)”
output += purchased ? “V” : “X”
return output
}
}
지정 초기자와 편의 초기자 실습
33. class SomeClass {
let someProperty: SomeType = {
return someValue}()
}
클로저나 함수로 기본 속성 값을 설정하
기
지역 파라메터는 init 안에서만 쓸 수 있는 파라메터이다.
외부 파라메터는 외부에서 init을 호출 할 때 반드시 이름을 magenta 를 초기화 하는 것 처럼 명시해야 한다. 그래서 veryGreen은 오류이다.
옵셔널은 그냥 int 가 아니라 int? / optional int 타입이다. toInt() 했을때 possibleNumber가 nil일 수 있다. 이 때 !를 통해서 강제 언래핑 하면 fatal exception이 발생 한다.
클래스 인스턴스는 상수 속성의 값을 오직 초기화 과정중에 해당 클래스에 의해서만 바꿀 수 있습니다. 상수 속성은 자식(sub) 클래스에 의해 변경될 수 없습니다
반드시 클래스의 모든 속성이 기본 값을 가지고 있어야 합니다. 그래야 자동으로 기본 초기자를 구현할 수 있습니다.
단, 옵셔널은 자동으로 기본값으로 nil을 받습니다.
구조체 타입은 자동으로 멤버 단위 초기자를 가진다.
단, 모든 저장 속성에 기본 값이 제공되고, 사용자 정의 초기자가 없어야 한다.
값 타입은 상속을 허용하지 않기 때문에 초기자를 대리하는게 상대적으로 쉽다. 왜냐하면 자기 자기가 가지고 있는 다른 초기자만 대리 할 수 있기 때문이다.
초기자 연쇄
지정초기자는 반드시 바로 위 부모 클래스의 지정 초기자를 호출한다.
편의 초기자는 반드시 같은 클래스 내의 초기자를 호출한다.
편의 초기자는 반드시 지정 초기자를 호출하는 것으로 끝내야 한다.
이렇게 두 단계 초기화를 함으로써, 초기화를 안전하게 하고, 상속 계층 상에서 유연성을 가진다. 속성 값이 초기화 되기 전에 접근하는 것을 반지, 다른 초기자 값을 설정하는 것을 방지 한다.
스위프트의 이 단계 초기화 과정은 오브젝티브 C의 초기화와 비슷합니다. 주요한 차이점은 첫번째 단계에 있습니다. 오브젝티브 C는 0이나 널(null) 값(0 또는 nil)을 모든 속성에 할당합니다. 스위프트의 초기화 흐름은 좀 더 유연하려 사용자 정의 초기값을 설정할 수 있게 해줍니다. 그리고 0이나nil이 기본값으로 유효하지 않은 타입에 대처할 수 있게 합니다.
- 클래스의 지정 초기자 또는 편의 초기자 호출
- 클래스 인스턴스를 위한 메모리 할당
- 클래스 지정 초기자가 해당 클래스에있는 모든 저장속성이 값이 있는지 확인. 해당 저장 속성을 위한 메모리 초기화 완료.
- 지정 초기자는 부모 클래스 지정 초기자로 작업을 넘겨줍니다.
- 상속 계층의 맨 꼭대기까지 계속 됩니다.
- 연쇄의 꼭대기에서 마지막 클래스는 모든 저장속성이 값을 가졌는지 확인하고, 인스턴스의 메모리는 완전히 초기화 되었습니다. 첫 단계를 종료합니다.
연쇄의 꼭대기에서 거꾸로 내려오면서 각각의 지정 초기자는 해당 클래스 인스턴스를 수정 할 수 있습니다. 초기자들은 이제 self에 접근하고, 프로퍼티를 수정 할 수 있습니다. 인스턴스 메소드도 호출 할 수 있습니다.
- 끝으로, 모든 편의 초기자들은 해당 클래스 인스턴스를 수정 할 수 있고, self를 사용 할 수 있습니다.
이런 접근 방식은 더 복잡한 자식 클래스가 부모 클래스의 단순한 초기자를 자동으로 상속받아서 불완전하게 초기화되는 것을 막아줍니다.
아마도 초기화 도중에 클래스의 무엇인가를 수정 하고 싶을 때, 수정한 자식 클래스가 부모로 부터 초기자를 상속 받기를 원할 것 입니다. 이 때는 수정한 자식 클래스에 같은 초기자를 오버라이딩해서 구현 할 수 있습니다.
지정 초기자를 오버라이딩 한다면, 자식 클래스에서 구현체를 오버라이드 할 수 있고, 오버라이딩한 버전에서 부모 클래스 버전의 초기자를 호출 할 수 있습니다. 편의 초기자를 오버라이딩 하고싶으면, 오버라이드한 초기자는 반드시 자식클래스 안의 지정 초기자를 호출해야 합니다.
부모클래스의 모든 편의 초기자를 자동으로 상속한다.
(Rule 1에 따라 지정 초기자를 상속 받아서 구현 하든가, 자식 클래스 정의의 일부로서 구현 할 수 있다.)
조건을 만족하면 자식 클래스가 자동으로 부모 클래스의 초기자를 상속 받습니다. 여기 진짜 말이긴데, 결국 자식 클래스가 부모 클래스의 모든 초기자를 구현해야 한다는 말이다.
클래스는 기본 멤버 단위 초기자가 없습니다. 그래서 Food 클래스는 단일 인자 name을 받는 초기자를 제공합니다.
Food 클래스는 편의 초기자도 제공합니다.
RecipeIngredient 클래스에는 한 개의 지정 초기자 init(name:String, quantity: Int)가 있습니다.
이 초기자는 RecipeIngredient 인스턴스의 모든 프로퍼티를 채우는데 쓰입니다.
이 초기자는 RecipeIngredient에서 새롭게 추가된 quantity에 값을 전달하면서 시작 합니다. 그리고나서, 이 초기자는 Food 클래스의 init(name:String)를 초기자를 대신 실행 합니다.
1단계는 해당 클래스가 가지는 저장속성에 초기값을 할당 한다. 입니다.
이 프로세스는 두 단계 초기화에서 1단계를 만족합니다.
RecipeIngredient는 init(name:String)도 정의 하고 있다. 이 편리 초기자는 별다른 입력이 없는 모든 RecipeIngredient의 quantity를 1로 가정 했다.
이 편의 초기자는 단순히 클래스의 지정 초기자를 대리한다.
RecipeIngredient의 init(name:String) 편의 초기자가 Food의 초기자 init(name:String)과 같은 파라메터를 받습니다.
RecipeIngredient가 이 초기자를 편의 초기자로 제공하지만, RecipeIngredient는 모든 부모 클래스의 지정 초기자들을 구현한것이 된다.
그러므로, RecipeIngredient는 자동으로 모든 부모 클래스의 편의 초기자들을 상속받는다.
이 예제에서 RecipeIngredient의 부모클래스 Food는 하나의 편의 초기자 init()을 제공 합니다.
따라서 이 초기자는 RecipeIngredient에 상속 됩니다. init()의 상속된 버전은 Food 버전과 똑같이 기능 합니다. 대리 수행하는 초기자 init(name:String)을 Food 버전이 아니라 ReceipeIngredient 버전을 사용 한다는 것만 빼면요.
이 클래스가 도입한 모든 속성의 초기값을 제공하고, 어떤 이니셜라이저도 스스로 정의하지 않기 때문에 ShoppingListItem은 자동적으로 모든 지정 이니셜라이저와 편의 이니셜라이저를 부모 클래스에서 상속받습니다.
중괄호 바로 옆에 빈 괄호 한쌍이 있는 것은 클로저를 즉시 실행 하도록 한다. 이 괄호가 없으면 값이 아니라 클로저 자체를 속성에 할당 하려고 시도 하는 것이 된다.
알아야 할 점은 클로저가 호출되는 시점은 인스턴스의 나머지는 아직 초기화 되지 않은 상태이다. 따라서 self를 쓰거나 다른 메소드를 호출 할 수 없다.
중괄호 바로 옆에 빈 괄호 한쌍이 있는 것은 클로저를 즉시 실행 하도록 한다. 이 괄호가 없으면 값이 아니라 클로저 자체를 속성에 할당 하려고 시도 하는 것이 된다.
알아야 할 점은 클로저가 호출되는 시점은 인스턴스의 나머지는 아직 초기화 되지 않은 상태이다. 따라서 self를 쓰거나 다른 메소드를 호출 할 수 없다.