이니셜라이저 내에서 저장된 속성의 초기 값을 설정하거나 속성 정의의 일부로 기본 속성 값을 할당하여 설정할 수 있습니다.
저장된 속성에 기본값을 할당하거나 이니셜라이저 내에서 초기 값을 설정하면 속성 관찰자를 호출하지 않고 해당 속성의 값이 직접 설정됩니다.
init() {
// perform some initialization here
}
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// Prints "The default temperature is 32.0° Fahrenheit"
속성이 항상 동일한 초기 값을 사용하는 경우 이니셜라이저 내에서 값을 설정하는 것보다 기본값을 제공하십시오.
최종 결과는 동일하지만 기본값은 속성의 초기화를 선언과 더 밀접하게 연결합니다.
더 짧고 명확한 이니셜라이저를 만들고 기본값에서 속성 유형을 유추할 수 있습니다.
또한 이 장의 뒷부분에서 설명하는 것처럼 기본값을 사용하면 기본 이니셜라이저와 이니셜라이저 상속을 더 쉽게 활용할 수 있습니다.
struct Fahrenheit {
var temperature = 32.0
}
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius is 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0
struct Color {
let red, green, blue: Double
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
init(white: Double) {
red = white
green = white
blue = white
}
}
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
let veryGreen = Color(0.0, 1.0, 0.0)
// this reports a compile-time error - argument labels are required
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
init(_ celsius: Double) {
temperatureInCelsius = celsius
}
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0
class SurveyQuestion {
var text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// Prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."
클래스 인스턴스의 경우 상수 속성은 초기화하는 동안 해당 속성을 도입한 클래스에서만 수정할 수 있습니다. 하위 클래스에서 수정할 수 없습니다.
class SurveyQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// Prints "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
구조체 유형은 자체 사용자 정의 이니셜라이저를 정의하지 않으면 자동으로 멤버별 이니셜라이저를 받습니다.
기본 이니셜라이저와 달리 구조체는 기본값이 없는 저장된 속성이 있더라도 멤버별 이니셜라이저를 받습니다.
멤버별 이니셜라이저는 새 구조체 인스턴스의 멤버 속성을 초기화하는 약식 방법입니다.
새 인스턴스의 속성에 대한 초기 값은 이름으로 멤버별 이니셜라이저에 전달할 수 있습니다.
아래 예제는 너비와 높이라는 두 가지 속성을 가진 Size라는 구조를 정의합니다.
두 속성 모두 기본값 0.0을 할당하여 Double 유형으로 유추됩니다.
Size 구조체는 새 Size 인스턴스를 초기화하는 데 사용할 수 있는 init(width:height:) 멤버별 이니셜라이저를 자동으로 받습니다.
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
멤버별 이니셜라이저를 호출할 때 기본값이 있는 모든 속성의 값을 생략할 수 있습니다.
위의 예에서 Size 구조는 height 및 width 속성 모두에 대한 기본값을 갖습니다.
속성 중 하나 또는 두 속성을 모두 생략할 수 있으며 이니셜라이저는 생략된 모든 항목에 대해 기본값을 사용합니다.
let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)
// Prints "0.0 2.0"
let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)
// Prints "0.0 0.0"
이니셜라이저는 다른 이니셜라이저를 호출하여 인스턴스 초기화의 일부를 수행할 수 있습니다.
여러 이니셜라이저에서 코드가 중복되는 것을 방지합니다.
값 유형의 경우 자체 사용자 정의 이니셜라이저를 작성할 때 self.init를 사용하여 동일한 값 유형의 다른 이니셜라이저를 참조합니다.
기본 이니셜라이저와 멤버별 이니셜라이저와 사용자 정의 이니셜라이저를 사용하여 사용자 정의 값 유형을 초기화할 수 있도록 하려면 값 유형의 원래 구현의 일부가 아닌 확장에 사용자 정의 이니셜라이저를 작성하십시오. 자세한 내용은 확장을 참조하십시오.
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
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)
}
}
let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
init() 및 init(origin:size:) 이니셜라이저를 직접 정의하지 않고 이 예제를 작성하는 다른 방법은 확장을 참조하세요.
편의 이니셜라이저는 클래스에 대한 이니셜라이저를 지원하는 보조 이니셜라이저입니다.
init(parameters) {
statements
}
이러한 규칙은 클래스의 사용자가 각 클래스의 인스턴스를 만드는 방법에 영향을 주지 않습니다.
위 다이어그램의 모든 이니셜라이저는 속한 클래스의 완전히 초기화된 인스턴스를 만드는 데 사용할 수 있습니다.
규칙은 클래스의 이니셜라이저 구현을 작성하는 방법에만 영향을 미칩니다.
Swift의 2단계 초기화 프로세스는 Objective-C의 초기화와 유사합니다.
주요 차이점은 1단계에서 Objective-C가 모든 속성에 0 또는 null 값(예: 0 또는 nil)을 할당한다는 것입니다.
Swift의 초기화 흐름은 사용자 정의 초기 값을 설정할 수 있고 0 또는 nil이 유효한 기본값이 아닌 유형에 대처할 수 있다는 점에서 더 유연합니다.
슈퍼클래스 이니셜라이저는 특정 상황에서 상속되지만 그렇게 하는 것이 안전하고 적절한 경우에만 상속됩니다. 자세한 내용은 아래의 자동 초기화 프로그램 상속을 참조하세요.
서브클래스의 이니셜라이저 구현이 편의 이니셜라이저인 경우에도 수퍼클래스 지정 이니셜라이저를 재정의할 때 항상 override 수정자를 작성합니다.
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)
class Hoverboard: Vehicle {
var color: String
init(color: String) {
self.color = color
// super.init() implicitly called here
}
override var description: String {
return "\(super.description) in a beautiful \(color)"
}
}
let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver
서브클래스는 초기화 중에 상속된 변수 속성을 수정할 수 있지만 상속된 상수 속성은 수정할 수 없습니다.
하위 클래스는 규칙 2를 충족하는 일부로 하위 클래스 편의 초기화로 상위 클래스 지정 초기화를 구현할 수 있습니다.
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"
let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}
ShoppingListItem은 구매에 대한 초기 값을 제공하기 위해 이니셜라이저를 정의하지 않습니다. 쇼핑 목록의 항목(여기에서 모델링됨)은 항상 구매하지 않은 상태로 시작하기 때문입니다.
var breakfastList = [
ShoppingListItem(),
ShoppingListItem(name: "Bacon"),
ShoppingListItem(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
print(item.description)
}
// 1 x Orange juice ✔
// 1 x Bacon ✘
// 6 x Eggs ✘
초기화가 실패할 수 있는 클래스, 구조 또는 열거형을 정의하는 것이 때때로 유용합니다.
이 실패는 잘못된 초기화 매개변수 값, 필요한 외부 리소스의 부재 또는 초기화 성공을 방해하는 기타 조건으로 인해 트리거될 수 있습니다.
실패할 수 있는 초기화 조건에 대처하려면 하나 이상의 실패 가능한 이니셜라이저를 클래스, 구조 또는 열거형 정의의 일부로 정의하십시오.
init 키워드(init?) 뒤에 물음표를 표시하여 실패할 수 있는 초기화 프로그램을 작성합니다.
동일한 매개변수 유형 및 이름으로 실패 가능 및 실패 불가능 초기화를 정의할 수 없습니다.
실패 가능한 이니셜라이저는 초기화하는 유형의 선택적 값을 만듭니다.
초기화 실패가 트리거될 수 있는 지점을 나타내기 위해 실패 가능한 이니셜라이저 내에 return nil을 작성합니다.
엄밀히 말하면 이니셜라이저는 값을 반환하지 않습니다.
오히려, 그들의 역할은 초기화가 끝날 때까지 self가 완전하고 올바르게 초기화되도록 하는 것입니다.
초기화 실패를 트리거하기 위해 return nil을 작성했지만 초기화 성공을 나타내기 위해 return 키워드를 사용하지 않습니다.
예를 들어, 숫자 유형 변환을 위해 실패 가능한 이니셜라이저가 구현됩니다.
숫자 유형 간의 변환이 값을 정확하게 유지하도록 하려면 init(exactly:) 이니셜라이저를 사용하십시오.
유형 변환이 값을 유지할 수 없으면 초기화가 실패합니다.
let wholeNumber: Double = 12345.0
let pi = 3.14159
if let valueMaintained = Int(exactly: wholeNumber) {
print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")
}
// Prints "12345.0 conversion to Int maintains value of 12345"
let valueChanged = Int(exactly: pi)
// valueChanged is of type Int?, not Int
if valueChanged == nil {
print("\(pi) conversion to Int doesn't maintain value")
}
// Prints "3.14159 conversion to Int doesn't maintain value"
아래 예제에서는 종이라는 상수 String 속성을 사용하여 Animal이라는 구조를 정의합니다.
Animal 구조는 또한 종이라는 단일 매개변수를 사용하여 실패할 수 있는 이니셜라이저를 정의합니다.
이 이니셜라이저는 이니셜라이저에 전달된 종 값이 빈 문자열인지 확인합니다.
빈 문자열이 발견되면 초기화 실패가 트리거됩니다. 그렇지 않으면 종 속성 값이 설정되고 초기화가 성공합니다.
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
이 실패할 수 있는 이니셜라이저를 사용하여 새 Animal 인스턴스를 초기화하고 초기화가 성공했는지 확인할 수 있습니다.
let someCreature = Animal(species: "Giraffe")
// someCreature is of type Animal?, not Animal
if let giraffe = someCreature {
print("An animal was initialized with a species of \(giraffe.species)")
}
// Prints "An animal was initialized with a species of Giraffe"
실패 가능한 이니셜라이저의 종 매개변수에 빈 문자열 값을 전달하면 이니셜라이저는 초기화 실패를 트리거합니다.
let anonymousCreature = Animal(species: "")
// anonymousCreature is of type Animal?, not Animal
if anonymousCreature == nil {
print("The anonymous creature couldn't be initialized")
}
// Prints "The anonymous creature couldn't be initialized"
빈 문자열 값(예: "Giraffe"가 아닌 "")을 확인하는 것은 선택적 String 값이 없음을 나타내기 위해 nil을 확인하는 것과 동일하지 않습니다.
위의 예에서 빈 문자열("")은 선택 사항이 아닌 유효한 문자열입니다.
그러나 동물이 종의 속성 값으로 빈 문자열을 갖는 것은 적절하지 않습니다.
이런 제한을 모델링하기 위해 실패 가능한 이니셜라이저는 빈 문자열이 발견되면 초기화 실패를 트리거합니다.
실패 가능한 이니셜라이저를 사용하여 하나 이상의 매개변수를 기반으로 적절한 열거 케이스를 선택할 수 있습니다.
그런 다음 제공된 매개변수가 적절한 열거 케이스와 일치하지 않으면 초기화가 실패할 수 있습니다.
아래 예에서는 세 가지 가능한 상태(켈빈, 섭씨 및 화씨)가 있는 TemperatureUnit이라는 열거를 정의합니다.
실패 가능한 이니셜라이저는 온도 기호를 나타내는 Character 값에 대한 적절한 열거 케이스를 찾는 데 사용됩니다.
enum TemperatureUnit {
case kelvin, celsius, fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .kelvin
case "C":
self = .celsius
case "F":
self = .fahrenheit
default:
return nil
}
}
}
이 실패 가능한 이니셜라이저를 사용하여 세 가지 가능한 상태에 대한 적절한 열거 케이스를 선택하고 매개변수가 다음 상태 중 하나와 일치하지 않으면 초기화가 실패하도록 할 수 있습니다.
let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "This is a defined temperature unit, so initialization succeeded."
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
print("This isn't a defined temperature unit, so initialization failed.")
}
// Prints "This isn't a defined temperature unit, so initialization failed."
원시 값이 있는 열거형은 자동으로 실패할 수 있는 초기화 프로그램 init?(rawValue:)를 수신합니다.
이 초기화 프로그램은 적절한 원시 값 유형의 rawValue라는 매개변수를 사용하고 발견된 경우 일치하는 열거형 케이스를 선택합니다 또는 일치하는 값이 없으면 초기화 실패를 트리거합니다.
위의 TemperatureUnit 예제를 다시 작성하여 Character 유형의 원시 값을 사용하고 init?(rawValue:) 이니셜라이저를 활용할 수 있습니다.
enum TemperatureUnit: Character {
case kelvin = "K", celsius = "C", fahrenheit = "F"
}
let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "This is a defined temperature unit, so initialization succeeded."
let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
print("This isn't a defined temperature unit, so initialization failed.")
}
// Prints "This isn't a defined temperature unit, so initialization failed."
클래스, 구조 또는 열거형의 실패 가능한 이니셜라이저는 동일한 클래스, 구조 또는 열거형의 다른 실패 가능한 이니셜라이저에 위임할 수 있습니다.
유사하게, 하위 클래스 실패 가능 이니셜라이저는 상위 클래스 실패 가능 이니셜라이저까지 위임할 수 있습니다.
두 경우 모두 초기화를 실패하게 하는 다른 이니셜라이저에 위임하면 전체 초기화 프로세스가 즉시 실패하고 더 이상의 초기화 코드가 실행되지 않습니다.
실패할 수 있는 이니셜라이저는 실패할 수 없는 이니셜라이저에 위임할 수도 있습니다. 다른 방법으로는 실패하지 않는 기존 초기화 프로세스에 잠재적인 실패 상태를 추가해야 하는 경우 이 접근 방식을 사용합니다.
아래 예제는 CartItem이라는 Product의 하위 클래스를 정의합니다.
CartItem 클래스는 온라인 쇼핑 카트의 항목을 모델링합니다.
CartItem은 수량이라는 저장된 상수 속성을 도입하고 이 속성이 항상 최소 1의 값을 갖도록 합니다.
class Product {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 { return nil }
self.quantity = quantity
super.init(name: name)
}
CartItem의 실패 가능한 이니셜라이저는 1 이상의 수량 값을 받았는지 확인하는 것으로 시작합니다.
수량이 유효하지 않으면 전체 초기화 프로세스가 즉시 실패하고 더 이상의 초기화 코드가 실행되지 않습니다.
마찬가지로 Product의 failable initializer는 이름 값을 확인하고 name이 빈 문자열이면 초기화 프로세스가 즉시 실패합니다.
비어 있지 않은 이름과 수량이 1 이상인 CartItem 인스턴스를 생성하면 초기화가 성공합니다.
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// Prints "Item: sock, quantity: 2"
수량 값이 0인 CartItem 인스턴스를 생성하려고 하면 CartItem 초기화 프로그램으로 인해 초기화가 실패합니다.
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
print("Unable to initialize zero shirts")
}
// Prints "Unable to initialize zero shirts"
마찬가지로 이름 값이 비어 있는 CartItem 인스턴스를 만들려고 하면 슈퍼클래스 Product 이니셜라이저로 인해 초기화가 실패합니다.
if let oneUnnamed = CartItem(name: "", quantity: 1) {
print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize one unnamed product"
실패할 수 있는 슈퍼클래스 이니셜라이저를 실패할 수 없는 서브클래스 이니셜라이저로 재정의하는 경우 슈퍼클래스 이니셜라이저까지 위임하는 유일한 방법은 실패할 수 있는 슈퍼클래스 이니셜라이저의 결과를 강제로 해제하는 것입니다.
실패할 수 없는 이니셜라이저를 실패할 수 없는 이니셜라이저로 재정의할 수 있지만 그 반대는 불가능합니다.
class Document {
var name: String?
// this initializer creates a document with a nil name value
init() {}
// this initializer creates a document with a nonempty name value
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class AutomaticallyNamedDocument: Document {
override init() {
super.init()
self.name = "[Untitled]"
}
override init(name: String) {
super.init()
if name.isEmpty {
self.name = "[Untitled]"
} else {
self.name = name
}
}
}
class UntitledDocument: Document {
override init() {
super.init(name: "[Untitled]")!
}
}
class SomeClass {
required init() {
// initializer implementation goes here
}
}
class SomeSubclass: SomeClass {
required init() {
// subclass implementation of the required initializer goes here
}
}
상속된 이니셜라이저로 요구 사항을 충족할 수 있다면 필수 이니셜라이저의 명시적 구현을 제공할 필요가 없습니다.
저장된 속성의 기본값에 일부 사용자 지정 또는 설정이 필요한 경우 클로저 또는 전역 함수를 사용하여 해당 속성에 대한 사용자 지정 기본값을 제공할 수 있습니다.
속성이 속한 유형의 새 인스턴스가 초기화될 때마다 클로저 또는 함수가 호출되고 반환 값이 속성의 기본값으로 할당됩니다.
class SomeClass {
let someProperty: SomeType = {
// create a default value for someProperty inside this closure
// someValue must be of the same type as SomeType
return someValue
}()
}
클로저를 사용하여 속성을 초기화하는 경우 클로저가 실행되는 시점에서 인스턴스의 나머지 부분이 아직 초기화되지 않았음을 기억하십시오. 즉, 해당 속성에 기본값이 있더라도 클로저 내에서 다른 속성 값에 액세스할 수 없습니다. 또한 암시적 self 속성을 사용하거나 인스턴스의 메서드를 호출할 수 없습니다.
struct Chessboard {
let boardColors: [Bool] = {
var temporaryBoard: [Bool] = []
var isBlack = false
for i in 1...8 {
for j in 1...8 {
temporaryBoard.append(isBlack)
isBlack = !isBlack
}
isBlack = !isBlack
}
return temporaryBoard
}()
func squareIsBlackAt(row: Int, column: Int) -> Bool {
return boardColors[(row * 8) + column]
}
}
let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false"