공부/SWIFT

[SWIFT] 이니셜라이저(생성자, initializer)

알로하리미 2021. 3. 21. 19:32
728x90

1.개요

초기화는 클래스, 구조체, 열거형 인스턴스를 사용하기 위해 준비 작업을 하는 단계 입니다. 이 단계에서 각 저장 프로퍼티의 초기 값을 설정합니다. 초기화 과정은 initializer를 정의 하는 것으로 구현할 수 있습니다. Swift의 initializer는 값을 반환하지 않습니다

 

 

저장 프로퍼티를 위한 초기값 설정

인스턴스의 저장 프로퍼티는 사용하기 전에 반드시 특정 값으로 초기화 돼야 합니다. 이 값으로 기본 값을 설정할 수 있고, 특정 값을 설정할 수도 있습니다.

초기화 후에 값이 확정되지 않은 저장프로퍼티는 존재 할 수 없습니다.

이니셜라이저를 통해 초깃값을 할당하거나, 프로퍼티 기본값을 통해 처음의 저장 프로퍼티가 초기화될 때는 프로퍼티 감시자 메서드가 호출 되지 않습니다.

항상 같은 초기 값을 갖는 다면 기본 프로퍼티를 사용하는 것이 좋습니다. 프로퍼티에 타입을 선언하지 않아도 컴파일러는 초기 값을 참조해서 타입을 추론할 수 있습니다. 이 기본값은 상속시 함께 상속 됩니다.

// 이니셜라이저 예시
struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}

var f = Fahrenheit()

print("The default temperature is \(f.temperature)° 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) 

 

 

전달인자 레이블이 없는 이니셜라이저 매개변수

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)

 

 

옵셔널 프로퍼티 타입

인스턴스가 사용되는 동안에 값을 꼭 갖지 않아도 되는 저장 프로퍼티가 있다면 해당 프로퍼티를 옵셔널로 서언할 수도 있다. 만약 초기화시 값을 할당해 주지 않으면 해당 프로퍼티는 nil이다.

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
}
//기본 init은 아래처럼 제공하지만
//멤버와이즈 이니셜라이저는 제공하지 않음
var item = ShoppingListItem()


//구조체
//기본이니셜라이저로 = 멤버와이즈이니셜라이저 제공
struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

 

 

값 타입을 위한 이니셜라이저 위임

이니셜라이저에서 다른 이니셜라이저를 호출할 수 있습니다. 이 과정을 이니셜라이저 위임이라 합니다.

사용자 정의 이니셜라이저를 정의하면 기본 이니셜라이저와 멤버 이니셜라이저를 사용할 수 없고. 직접 만들어야한다.

* 값타임 ( 구조체, 열거형 )

사용자정의 이니셜 라이저를 사용하면서 기본 이니셜라이저를 사용하고 싶다면 

사용자정의 이니셜 라이저를 extention에서 구현하면 된다

struct Size {

    var width = 0.0, height = 0.0
    
}

struct Point {

    var x = 0.0, y = 0.0
    
}


struct Rect {
    var origin = Point() //Point구조체 프로퍼티로 사용
    var size = 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)

* 위의 예에서 익스텐션으로 3번째 이니셜라이저를 확장하면 첫번째 이니셜라이저(기본 이니셜라이저와 동일)와, 두번째 이니셜라이저(멤버와이즈 이니셜라이저와 동일)는 자동으로 생성된다.

 

 

실패 가능한 이니셜라이저(Failable initailizer)

실패 가능한 이니셜라이저는 실패했을때 nil을 반환 해준다. 반환 타입이 옵셔널로 지정된다.

struct Animal {
    let species: String
    
    //init? : 실패가능한 이니셜 라이저
    init?(species: String) { 
        if species.isEmpty { return nil }
        self.species = species
    }
}


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"

* 빈것(Empty)과 nil은 다릅니다. 그래서 .isEmpty를 이용해 비교하지 않으면 인스턴스 초기화시 “”를 넣어도 초기화가 됩니다. 이것은 저희가 의도한 동작이 아니니 empty를 확인해서 nil을 반환하도록 구현해야 합니다.

 

열거형에서 사용하는 실패 가능한 이니셜라이저

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") // .fahrenheit

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") //nil

if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

 

 

 

이니셜라이저 실패의 위임.

실패가능한 초기자를 실패가 가능하지 않은 초기자에 위임할 수 있습니다. 이런 방식을 조합해 현재 존재하는 초기자를 특정 상황에만 실패하는 초기자로 만들 수 있습니다.

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


//초기화 성공
if let twoSocks = CartItem(name: "sock", quantity: 2) { 
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}

//초기화 실패 quanity 1 미만
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("Unable to initialize zero shirts")
}

//초기화 실패 name isEmpty
if let oneUnnamed = CartItem(name: "", quantity: 1) {
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    print("Unable to initialize one unnamed product")
}

 

 

실패 가능한 이니셜라이저의 오버라이딩

부모클래스의 실패가능한 이니셜라이저를 자식클래스에서 실패 불가능한 이니셜라이저로 오버라이딩 할 수 있습니다

( 반대로 실패불가능한 애를 실패 가능하게 만드는 것은 불가능 ) 

class Document {
    var name: String?
    
    // 실패 불가능한 이니셜라이저
    init() {}    
    
    
    // 실패 가능한 이니셜라이저
    init?(name: String) { 
        if name.isEmpty { return nil }
        self.name = name
    }
}

//Document 상속
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
        }
    }
}

//Document 상속
class UntitledDocument: Document {

    // 실패 불가능한 부모의 이니셜라이저 오버라이딩
    override init() {    
    
        // 부모의 실패가능한 이니셜라이저 호출
        // 옵셔널 값을 갖지 않도록 강제옵셔널 추출 사용
        // 실패 가능한 초기자 init?을 init!로 오버라이딩 할 수 있고 
        // 아니면 위임해서 사용할 수 있습니다.
        super.init(name: "[Untitled]")!
        
    }
}

 

필수 초기자(Required Initializers)

너 내꺼 상속 받았으면 무조건 구현해라 라는 의미

class SomeClass {
    required init() {
        // initializer implementation goes here
    }
}


class SomeSubclass: SomeClass {
    required init() {
        // subclass implementation of the required initializer goes here
    }
}

 

 

클로저나 함수를 이용해 기본 프로퍼티 값을 설정하기

클로저 실행 시점은 초기화시의 인스턴스의 다른프로퍼티 값이 설정되기(초기화되기)전이기 떄문에

초기화시 self나 다른 프로퍼티를 사용할 수 없다.

class SomeClass {
    let someProperty: SomeType = {
        // 인스턴스 생성시 연산 구현
        // 반환되는 값은 SomeType와 동일한 값
        return someValue
    }() 
    // ()는 {}해당 클로저를 실행한다는 말
    // 없으면 해당 프로퍼티는 클로저{} 그 자체가 됨.
}

 

 

2021.03.21 - [공부/SWIFT] - [SWIFT] 디이니셜라니저(초기화해지, Deinitialization)