class initailaizer를 이해하는데 좋은 예시 (swift docs 에서 가져온 내용)

original source https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID203

Designated and Convenience Initializers in Action

The following example shows designated initializers, convenience initializers, and automatic initializer inheritance in action. This example defines a hierarchy of three classes called Food, RecipeIngredient, and ShoppingListItem, and demonstrates how their initializers interact.

The base class in the hierarchy is called Food, which is a simple class to encapsulate the name of a foodstuff. The Food class introduces a single String property called name and provides two initializers for creating Food instances:

class Food {
   var name: String
   init(name: String) {
       self.name = name
   }
   convenience init() {
       self.init(name: “[Unnamed]”)
   }
}

The figure below shows the initializer chain for the Food class:

Classes do not have a default memberwise initializer, and so the Food class provides a designated initializer that takes a single argument called name. This initializer can be used to create a new Food instance with a specific name:

let namedMeat = Food(name: “Bacon”)
// namedMeat’s name is “Bacon”

The init(name: String) initializer from the Food class is provided as a designated initializer, because it ensures that all stored properties of a new Food instance are fully initialized. The Food class does not have a superclass, and so the init(name: String) initializer does not need to call super.init() to complete its initialization.

The Food class also provides a convenience initializer, init(), with no arguments. The init() initializer provides a default placeholder name for a new food by delegating across to the Food class’s init(name: String) with a name value of [Unnamed]:

let mysteryMeat = Food()
// mysteryMeat’s name is “[Unnamed]”

The second class in the hierarchy is a subclass of Food called RecipeIngredient. The RecipeIngredient class models an ingredient in a cooking recipe. It introduces an Int property called quantity (in addition to the name property it inherits from Food) and defines two initializers for creating RecipeIngredient instances:

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)
   }
}

The figure below shows the initializer chain for the RecipeIngredient class:

The RecipeIngredient class has a single designated initializer, init(name: String, quantity: Int), which can be used to populate all of the properties of a new RecipeIngredient instance. This initializer starts by assigning the passed quantity argument to the quantity property, which is the only new property introduced by RecipeIngredient. After doing so, the initializer delegates up to the init(name: String) initializer of the Food class. This process satisfies safety check 1 from Two-Phase Initialization above.

RecipeIngredient also defines a convenience initializer, init(name: String), which is used to create a RecipeIngredient instance by name alone. This convenience initializer assumes a quantity of 1 for any RecipeIngredient instance that is created without an explicit quantity. The definition of this convenience initializer makes RecipeIngredient instances quicker and more convenient to create, and avoids code duplication when creating several single-quantity RecipeIngredient instances. This convenience initializer simply delegates across to the class’s designated initializer, passing in a quantity value of 1.

The init(name: String) convenience initializer provided by RecipeIngredient takes the same parameters as the init(name: String) designated initializer from Food. Because this convenience initializer overrides a designated initializer from its superclass, it must be marked with the override modifier (as described in Initializer Inheritance and Overriding).

Even though RecipeIngredient provides the init(name: String) initializer as a convenience initializer, RecipeIngredient has nonetheless provided an implementation of all of its superclass’s designated initializers. Therefore, RecipeIngredient automatically inherits all of its superclass’s convenience initializers too.

In this example, the superclass for RecipeIngredient is Food, which has a single convenience initializer called init(). This initializer is therefore inherited by RecipeIngredient. The inherited version of init() functions in exactly the same way as the Food version, except that it delegates to the RecipeIngredient version of init(name: String) rather than the Food version.

All three of these initializers can be used to create new RecipeIngredient instances:

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: “Bacon”)
let sixEggs = RecipeIngredient(name: “Eggs”, quantity: 6)

The third and final class in the hierarchy is a subclass of RecipeIngredient called ShoppingListItem. The ShoppingListItem class models a recipe ingredient as it appears in a shopping list.

Every item in the shopping list starts out as “unpurchased”. To represent this fact, ShoppingListItemintroduces a Boolean property called purchased, with a default value of false. ShoppingListItem also adds a computed description property, which provides a textual description of a ShoppingListItem instance:

class ShoppingListItem: RecipeIngredient {
   var purchased = false
   var description: String {
       var output = “(quantity) x (name)”
       output += purchased ? “ ✔” : “ ✘”
       return output
   }
}

NOTE

ShoppingListItem does not define an initializer to provide an initial value for purchased, because items in a shopping list (as modeled here) always start out unpurchased.

Because it provides a default value for all of the properties it introduces and does not define any initializers itself, ShoppingListItem automatically inherits all of the designated and convenience initializers from its superclass.

The figure below shows the overall initializer chain for all three classes:

You can use all three of the inherited initializers to create a new ShoppingListItem instance:

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 ✘

Here, a new array called breakfastList is created from an array literal containing three new ShoppingListItem instances. The type of the array is inferred to be [ShoppingListItem]. After the array is created, the name of the ShoppingListItem at the start of the array is changed from "[Unnamed]" to "Orange juice" and it is marked as having been purchased. Printing the description of each item in the array shows that their default states have been set as expected.

Swift Tutorial: Initialization In Depth, Part 2/2

Designated Initializers and Convenience Initializers in Swift

Initialization


Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization that is required before the new instance is ready for use.

Unlike Objective-C initializers, Swift initializers do not return a value. 

Instances of class types can also implement a deinitializer, which performs any custom cleanup just before an instance of that class is deallocated. For more information about deinitializers, see Deinitialization.

Setting Initial Values for Stored Properties

Classes and structures must set all of their stored properties to an appropriate initial value by the time an instance of that class or structure is created. Stored properties cannot be left in an indeterminate state.

You can set an initial value for a stored property within an initializer, or by assigning a default property value as part of the property’s definition. These actions are described in the following sections.

NOTE

When you assign a default value to a stored property, or set its initial value within an initializer, the value of that property is set directly, without calling any property observers.(initialization작업에서는 property observer가 작동하지 않는다고 이해 하였다.)

Initializers

Initializers are called to create a new instance of a particular type.written using the init keyword:

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”

Default Property Values

If a property always takes the same initial value, provide a default value rather than setting a value within an initializer. The end result is the same, but the default value ties the property’s initialization more closely to its declaration. 

struct Fahrenheit {
   var temperature = 32.0
}

Customizing Initialization

Initialization Parameters

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

.

Parameter Names and Argument Labels

As with function and method parameters, initialization parameters can have both a parameter name for use within the initializer’s body and an argument label for use when calling the initializer.

Swift provides an automatic argument label for every parameter in an initializer if you don’t provide one.

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

Initializer Parameters Without Argument Labels

If you do not want to use an argument label for an initializer parameter, write an underscore (_) instead of an explicit argument label for that parameter to override the default behavior.

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

Optional Property Types

If your custom type has a stored property that is logically allowed to have “no value”—perhaps because its value cannot be set during initialization, or because it is allowed to have “no value” at some later point—declare the property with an optional type. Properties of optional type are automatically initialized with a value of nil, indicating that the property is deliberately intended to have “no value yet” during initialization.

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.”

Assigning Constant Properties During Initialization

You can assign a value to a constant property at any point during initialization, as long as it is set to a definite value by the time initialization finishes. Once a constant property is assigned a value, it can’t be further modified.  (constant의 경우 initialization마무리 전까지는 값이 할당되어야 한다는 이야기 )

NOTE

For class instances, a constant property can be modified during initialization only by the class that introduces it. It cannot be modified by a subclass.

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.)”

Default Initializers

Swift provides a default initializer for any structure or class that provides default values for all of its properties and does not provide at least one initializer itself. 

(모든 property들에 기본값이 주어지고 initializer가 하나도없는 경우 swift 자체에서 default initializer가 제공되어서 스스로 init작업이 이루어 진다는 이야기.)

class ShoppingListItem {
   var name: String?
   var quantity = 1
   var purchased = false
}
var item = ShoppingListItem()

Memberwise Initializers for Structure Types

Structure types automatically receive a memberwise initializer if they do not define any of their own custom initializers. (특별히 initializer를 작성하지 않더라도 기본적으로  property 이름을 parameter name으로 하는 기본 initializer가 제공된다는 이야기)

The memberwise initializer is a shorthand way to initialize the member properties of new structure instances. Initial values for the properties of the new instance can be passed to the memberwise initializer by name.

struct Size {
   var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

Initializer Delegation for Value Types

Initializers can call other initializers to perform part of an instance’s initialization. This process, known as initializer delegation, avoids duplicating code across multiple initializers. (initilizer가 initializer를 호출하는 방법 설명)

The rules for how initializer delegation works, and for what forms of delegation are allowed, are different for value types and class types. Value types (structures and enumerations) do not support inheritance, and so their initializer delegation process is relatively simple, because they can only delegate to another initializer that they provide themselves. Classes, however, can inherit from other classes, as described in Inheritance. This means that classes have additional responsibilities for ensuring that all stored properties they inherit are assigned a suitable value during initialization. These responsibilities are described in Class Inheritance and Initialization below.

For value types, you use self.init to refer to other initializers from the same value type when writing your own custom initializers. You can call self.init only from within an initializer.

Note that if you define a custom initializer for a value type, you will no longer have access to the default initializer (or the memberwise initializer, if it is a structure) for that type. This constraint prevents a situation in which additional essential setup provided in a more complex initializer is accidentally circumvented by someone using one of the automatic initializers.

NOTE

If you want your custom value type to be initializable with the default initializer and memberwise initializer, and also with your own custom initializers, write your custom initializers in an extension rather than as part of the value type’s original implementation. For more information, see Extensions.

struct Size {
   var width = 0.0, height = 0.0
}
struct Point {
   var x = 0.0, y = 0.0
}

You can initialize the Rect structure below in one of three ways

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)

Inheritance

Classes in Swift can call and access methods, properties, and subscripts belonging to their superclass and can provide their own overriding versions of those methods, properties, and subscripts to refine or modify their behavior. Swift helps to ensure your overrides are correct by checking that the override definition has a matching superclass definition.

Classes can also add property observers to inherited properties in order to be notified when the value of a property changes. Property observers can be added to any property, regardless of whether it was originally defined as a stored or computed property.

Defining a Base Class

Any class that does not inherit from another class is known as a base class.

NOTE

Swift classes do not inherit from a universal base class. Classes you define without specifying a superclass automatically become base classes for you to build upon.

class Vehicle {
   var currentSpeed = 0.0
   var description: String {
       return “traveling at (currentSpeed) miles per hour”
   }
   func makeNoise() {
       // do nothing – an arbitrary vehicle doesn’t necessarily make a noise
   }
}

let someVehicle = Vehicle()

print(“Vehicle: (someVehicle.description)”)
// Vehicle: traveling at 0.0 miles per hour

Subclassing

To indicate that a subclass has a superclass, write the subclass name before the superclass name, separated by a colon:

class SomeSubclass: SomeSuperclass {
   // subclass definition goes here
}

class Bicycle: Vehicle {
   var hasBasket = false
}

let bicycle = Bicycle()
bicycle.hasBasket = true

bicycle.currentSpeed = 15.0
print(“Bicycle: (bicycle.description)”)
// Bicycle: traveling at 15.0 miles per hour

class Tandem: Bicycle {
   var currentNumberOfPassengers = 0
}

let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print(“Tandem: (tandem.description)”)
// Tandem: traveling at 22.0 miles per hour

Overriding

A subclass can provide its own custom implementation of an instance method, type method, instance property, type property, or subscript that it would otherwise inherit from a superclass. This is known as overriding.

To override a characteristic that would otherwise be inherited, you prefix your overriding definition with the override keyword. Doing so clarifies that you intend to provide an override and have not provided a matching definition by mistake. Overriding by accident can cause unexpected behavior, and any overrides without the override keyword are diagnosed as an error when your code is compiled.

Accessing Superclass Methods, Properties, and Subscripts

Where this is appropriate, you access the super class version of a method, property, or subscript by using the super prefix:

  • An overridden method named someMethod() can call the superclass version of someMethod() by calling super.someMethod() within the overriding method implementation.
  • An overridden property called someProperty can access the superclass version of someProperty as super.someProperty within the overriding getter or setter implementation.
  • An overridden subscript for someIndex can access the superclass version of the same subscript as super[someIndex] from within the overriding subscript implementation.

Overriding Methods

class Train: Vehicle {
   override func makeNoise() {
       print(“Choo Choo”)
   }
}

let train = Train()
train.makeNoise()
// Prints “Choo Choo”

Overriding Properties

You can override an inherited instance or type property to provide your own custom getter and setter for that property, or to add property observers to enable the overriding property to observe when the underlying property value changes.

Overriding Property Getters and Setters

(참고: 

Setters and Getters apply to computed properties; such properties do not have storage in the instance – the value from the getter is meant to be computed from other instance properties.)

(ref: https://stackoverflow.com/a/24025548

You can provide a custom getter (and setter, if appropriate) to override any inherited property, regardless of whether the inherited property is implemented as a stored or computed property at source. The stored or computed nature of an inherited property is not known by a subclass—it only knows that the inherited property has a certain name and type. You must always state both the name and the type of the property you are overriding, to enable the compiler to check that your override matches a superclass property with the same name and type.

You can present an inherited read-only property as a read-write property by providing both a getter and a setter in your subclass property override. You cannot, however, present an inherited read-write property as a read-only property.

NOTE

If you provide a setter as part of a property override, you must also provide a getter for that override. If you don’t want to modify the inherited property’s value within the overriding getter, you can simply pass through the inherited value by returning super.someProperty from the getter, where someProperty is the name of the property you are overriding.

class Car: Vehicle {
   var gear = 1
   override var description: String {
       return super.description + “ in gear (gear)”
   }
}

let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print(“Car: (car.description)”)
// Car: traveling at 25.0 miles per hour in gear 3

Overriding Property Observers

You can use property overriding to add property observers to an inherited property. This enables you to be notified when the value of an inherited property changes, regardless of how that property was originally implemented. For more information on property observers, see Property Observers.

NOTE

You cannot add property observers to inherited constant stored properties or inherited read-only computed properties. The value of these properties cannot be set, and so it is not appropriate to provide a willSet or didSet implementation as part of an override.

Note also that you cannot provide both an overriding setter and an overriding property observer for the same property. If you want to observe changes to a property’s value, and you are already providing a custom setter for that property, you can simply observe any value changes from within the custom setter.

class AutomaticCar: Car {
   override var currentSpeed: Double {
       didSet {
           gear = Int(currentSpeed / 10.0) + 1
       }
   }
}

let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print(“AutomaticCar: (automatic.description)”)
// AutomaticCar: traveling at 35.0 miles per hour in gear 4

Preventing Overrides

You can prevent a method, property, or subscript from being overridden by marking it as final. Do this by writing the final modifier before the method, property, or subscript’s introducer keyword (such as final var, final func, final class func, and final subscript).

You can mark an entire class as final by writing the final modifier before the class keyword in its class definition (final class). 

Subscripts

Classes, structures, and enumerations can define subscripts, which are shortcuts for accessing the member elements of a collection, list, or sequence. You use subscripts to set and retrieve values by index without needing separate methods for setting and retrieval. For example, you access elements in an Array instance as someArray[index] and elements in a Dictionary instance as someDictionary[key]

You can define multiple subscripts for a single type, and the appropriate subscript overload to use is selected based on the type of index value you pass to the subscript. Subscripts are not limited to a single dimension, and you can define subscripts with multiple input parameters to suit your custom type’s needs. 

(array나 dictionary처럼 index,key를 통해 element에 접근가능하게 하는 방법 제공한다는 이야기 그러나 꼭 index,key (string) 형식이 아니어도 상관없음)

Subscript Syntax

Subscripts enable you to query instances of a type by writing one or more values in square brackets after the instance name. Their syntax is similar to both instance method syntax and computed property syntax. You write subscript definitions with the subscript keyword, and specify one or more input parameters and a return type, in the same way as instance methods. Unlike instance methods, subscripts can be read-write or read-only. This behavior is communicated by a getter and setter in the same way as for computed properties:

subscript(index: Int) -> Int {
   get {
       // return an appropriate subscript value here
   }
   set(newValue) {
       // perform a suitable setting action here
   }
}

The type of newValue is the same as the return value of the subscript. As with computed properties, you can choose not to specify the setter’s (newValue) parameter. A default parameter called newValue is provided to your setter if you do not provide one yourself.

As with read-only computed properties, you can simplify the declaration of a read-only subscript by removing the get keyword and its braces:

subscript(index: Int) -> Int {
   // return an appropriate subscript value here
}

example of a read-only subscript implementation

struct TimesTable {
   let multiplier: Int
   subscript(index: Int) -> Int {
       return multiplier * index
   }
}
let threeTimesTable = TimesTable(multiplier: 3)
print(“six times three is (threeTimesTable[6])”)
// Prints “six times three is 18”

Subscript Usage

Subscripts are typically used as a shortcut for accessing the member elements in a collection, list, or sequence. You are free to implement subscripts in the most appropriate way for your particular class or structure’s functionality.

For example, Swift’s Dictionary type 

var numberOfLegs = [“spider”: 8, “ant”: 6, “cat”: 4]
numberOfLegs[“bird”] = 2

Subscript Options

Subscripts can take any number of input parameters, and these input parameters can be of any type. Subscripts can also return any type. Subscripts can use variadic parameters(정해지지 않은 수의 변수 즉 여러개의 parameters), but they can’t use in-out parameters(값을 받고 계산된 값을 되돌리는 경우) or provide default parameter values.

A class or structure can provide as many subscript implementations as it needs, and the appropriate subscript to be used will be inferred based on the types of the value or values that are contained within the subscript brackets at the point that the subscript is used. This definition of multiple subscripts is known as subscript overloading.

While it is most common for a subscript to take a single parameter, you can also define a subscript with multiple parameters if it is appropriate for your type. The following example defines a Matrix structure, which represents a two-dimensional matrix of Double values. The Matrix structure’s subscript takes two integer parameters:

struct Matrix {
   let rows: Int, columns: Int
   var grid: [Double]
   init(rows: Int, columns: Int) {
       self.rows = rows
       self.columns = columns

       // 첫번째 파라미터는 값, 두번째 파라미터는 array 요소의 갯수

       // 참조 

Creating an Array with a Default Value.

       grid = Array(repeating: 0.0, count: rows * columns)
   }
   func indexIsValid(row: Int, column: Int) -> Bool {
       return row >= 0 && row < rows && column >= 0 && column < columns
   }
   subscript(row: Int, column: Int) -> Double {
       get {
           assert(indexIsValid(row: row, column: column), “Index out of range”)
           return grid[(row * columns) + column]
       }
       set {
           assert(indexIsValid(row: row, column: column), “Index out of range”)
           grid[(row * columns) + column] = newValue
       }
   }
}

var matrix = Matrix(rows: 2, columns: 2)

matrix[0, 1] = 1.5
matrix[1, 0] = 3.2

func indexIsValid(row: Int, column: Int) -> Bool {
   return row >= 0 && row < rows && column >= 0 && column < columns
}

let someValue = matrix[2, 2]
// this triggers an assert, because [2, 2] is outside of the matrix bounds

Swift type properties – Cosmin Mircea – Medium