TIL13 ✨

YaR Lab·2023년 5월 10일
0

TIL✨

목록 보기
3/135
post-thumbnail

23.05.10

프로젝트 관련 (새로만드는 객체(서브스크랩트))
각 객체의 책임, 관계에 대해서 집중
책임 주도 설계를 이용해 전체적인 구조를 설계하고 들어감 (국소적 시각에서 벗어나게 됨)
코드에 이유가 생김(정답은 없지만 이유가 있어야함)
구조를 PR에 포함시키자!

git flow

revert
reset
amend

rebase

Initialization

클래스, 구조체, 또는 열거형의 인스턴스를 사용할 수 있도록 준비하는 과정으로, 이 과정은 해당 인스턴스의 각 저장 프로퍼티에 대해 초기값을 설정하고, 새로운 인스턴스가 사용할 준비가 된 전처리나 초기화를 수행하는 것을 포함합니다.

초기화 과정은 초기화 메서드를 정의함으로써 구현할 수 있다. 초기화 메서드는 특정 타입의 새 인스턴스를 생성하기 위해 호출할 수 있는 특별한 메서드와 같습니다. Objective-C의 초기화 메서드와는 달리, Swift의 초기화 메서드는 값을 반환하지 않습니다. 그들의 주된 역할은 타입의 새 인스턴스가 처음 사용되기 전에 올바르게 초기화되도록 보장한다.

클래스 타입의 인스턴스는 클래스가 할당 해제(deallocated)되기 직전에 사용자 정의 정리 작업을 수행하는 소멸자(deinitializer)를 구현할 수 있다.

클래스와 구조체는 해당 클래스 또는 구조체의 인스턴스가 생성될 때 모든 저장 프로퍼티를 적절한 초기값으로 설정해야 한다. 저장 프로퍼티를 불확정한 상태로 둘 수는 없다.

저장 프로퍼티의 초기값은 초기화 메서드 내에서 설정하거나 프로퍼티의 정의 일부로 기본 프로퍼티 값을 할당함으로써 설정할 수 있다.

참고:
저장 프로퍼티에 기본값을 할당하거나 초기화 메서드 내에서 초기값을 설정할 때, 해당 프로퍼티의 값은 %프로퍼티 관찰자(property observers)%를 호출하지 않고 직접 설정됩니다.

Initializers

Initializer(초기화 메서드)는 특정 타입의 새 인스턴스를 생성하기 위해 호출됩니다. init 키워드를 사용하여 작성된다.

저장 프로퍼티의 초기값은 초기화 메서드 내에서 설정할 수 있습니다. 또는 프로퍼티의 선언 일부로 기본 프로퍼티 값을 지정할 수도 있습니다. 프로퍼티가 정의될 때 초기값을 할당하여 기본 프로퍼티 값을 지정합니다.

만약 프로퍼티가 항상 같은 초기값을 가진다면, 초기화 메서드 내부에서 값을 설정하는 대신에 기본값을 제공하는 것이 좋습니다. 이렇게 하면 기본 초기화 메서드와 초기화 메서드 상속 기능을 더 쉽게 활용할 수 있게 됩니다.

Customizing Initialization

입력 매개변수와 옵셔널 프로퍼티 유형으로 초기화 프로세스를 커스터마이징하거나 초기화 중에 상수 프로퍼티를 할당하여 초기화 프로세스를 커스터마이징할 수 있다.

초기화 매개변수는 초기화 프로세스를 커스터마이즈하기 위해 값의 유형과 이름을 정의하기 위해 초기화자의 정의 일부로 제공된다.

두 개의 사용자 정의 초기화자를 구현할 수도 있습니다.

초기화 파라미터는 이니셜라이저 내부에서 사용할 파라미터 이름과, 이니셜라이저를 호출할 때 사용할 인자 레이블을 모두 가질 수 있습니다. 또한, 여러개의 파라미터를 가질 수 있습니다. 그래서 이니셜라이저 파라미터의 이름과 타입은 호출될 이니셜라이저를 식별하는데 특히 중요한 역할을 합니다. 이러한 이유로, 인자 레이블을 명시하지 않으면 Swift는 모든 이니셜라이저 파라미터에 자동으로 인자 레이블을 제공합니다.

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

초기화 매개변수에 인수 레이블을 사용하지 않으려면 해당 매개변수에 명시적인 인수 레이블 대신 밑줄(_) 을 작성하여 기본 동작을 재정의하면 됩니다.

만약 당신의 사용자 정의 타입이 "값이 없을 수 있다"는 논리적 가능성을 가진 저장 프로퍼티를 가지고 있다면, 해당 프로퍼티를 optional 타입으로 선언하세요. Optional 타입의 프로퍼티는 자동으로 nil 값으로 초기화됩니다(기본 초기화자에 의해서).

초기화 과정 중 언제든지 상수 프로퍼티에 값을 할당할 수 있습니다. 다만, 초기화가 완료될 때 명확한 값을 가지도록 설정해야 합니다. 상수 속성이 한 번 값이 할당되면, 이후로 수정할 수 없습니다. 클래스 인스턴스의 경우, 상수 프로퍼티는 도입한 클래스만이 초기화 중에 수정할 수 있습니다. 하위 클래스에서는 수정할 수 없습니다.

Default Initializers

Swift는 모든 속성에 기본값을 제공하고 자체 초기화기(initializer)를 제공하지 않는 구조체(structure) 또는 클래스(class)에 대해 기본 초기화기(default initializer)를 제공합니다. default initializer는 모든 속성을 기본값으로 설정한 새 인스턴스를 만듭니다.

super Class

수퍼 클래스는 자식 클래스가 상속할 수 있는 기본 형태를 정의하는 클래스입니다.

Memberwise Initializers for Structure Types

구조체 타입은 커스텀 이니셜라이저를 정의하지 않는다면 멤버와이즈 이니셜라이저를 자동으로 받게 된다. 디폴트 이니셜라이저와 달리, 구조체에 기본값이 없는 저장 프로퍼티가 있더라도 멤버와이즈 이니셜라이저를 받을 수 있다. 멤버와이즈 이니셜라이저를 호출할 때, 디폴트 값을 가진 프로퍼티에 대한 값을 생략할 수 있습니다.

Initializer Delegation for Value Types

이니셜라이저는 인스턴스 초기화의 일부를 수행하기 위해 다른 이니셜라이저를 호출할 수 있습니다. 이 과정을 이니셜라이저 위임(initializer delegation)이라고하며, 여러 이니셜라이저에서 중복 코드를 피하는 데 도움이 됩니다.

이니셜라이저 위임 작동 방식 및 허용되는 위임 형식에 대한 규칙은 값 타입과 클래스 타입에 따라 다릅니다. 값 타입(구조체 및 열거형)은 상속을 지원하지 않으므로, 이니셜라이저를 제공하는 다른 이니셜라이저로만 위임할 수 있습니다. 반면 클래스는 다른 클래스를 상속할 수 있으므로, 상속받은 모든 저장 프로퍼티가 초기화될 적절한 값을 할당하는 책임을 갖습니다.

값 타입의 경우, 자신의 사용자 정의 이니셜라이저를 작성할 때 self.init을 사용하여 같은 값 타입에서 다른 이니셜라이저를 참조합니다. self.init은 이니셜라이저 내부에서만 호출할 수 있습니다.

참고로, 값 타입에 대해 사용자 정의 이니셜라이저를 정의하면 해당 타입의 기본 이니셜라이저(또는 구조체의 멤버와이즈 이니셜라이저)에 더 이상 액세스할 수 없게됩니다.이 제한은 복잡한 이니셜라이저에서 제공하는 추가적인 필수 설정이 자동 이니셜라이저 중 하나를 사용하는 사람에 의해 우연히 우회되지 않도록하는 데 도움이 됩니다.

참고:

만약 당신이 당신의 커스텀 값 타입을 기본 이니셜라이저와 멤버와이즈 이니셜라이저, 그리고 당신 자신의 커스텀 이니셜라이저로 초기화하고 싶다면, 그것을 값 타입의 원래 구현의 일부로 작성하는 대신, 익스텐션에서 당신의 커스텀 이니셜라이저를 작성해라.

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

Rect 초기화 함수인 init(center:size:)는 약간 복잡합니다. 이 초기화 함수는 중심점과 크기 값을 기반으로 적절한 원점을 계산한 다음, 새로운 원점과 크기 값을 해당 속성에 저장하기 위해 init(origin:size:) 초기화 함수를 호출(또는 delegate)합니다.

'Extensions'를 사용하여 init() 및 init(origin:size:) 초기화 메서드를 직접 정의하지 않고도 이 예제를 작성하는 대체 방법이 있습니다.

Class Inheritance and Initialization

클래스가 상속하는 슈퍼클래스의 프로퍼티를 포함한 모든 저장 프로퍼티는 초기화 중 초기 값을 할당받아야 합니다.

모든 저장 프로퍼티에 초기 값을 할당하기 위해, Swift는 클래스 유형을 위해 두 종류의 이니셜라이저를 정의합니다. 이것들은 지정 이니셜라이저와 편의 이니셜라이저로 알려져 있습니다.

Designated Initializers

Designated Initializers는 클래스의 주요 이니셜라이저입니다. Designated Initializers는 클래스에서 도입된 모든 프로퍼티를 완전히 초기화하고, 초기화 프로세스를 상위 클래스 체인까지 이어나가기 위해 적절한 슈퍼클래스 이니셜라이저를 호출합니다.

클래스는 대개 Designated Initializers가 매우 적습니다. 클래스가 하나만 있어도 충분한 경우가 많습니다. Designated Initializers는 초기화가 이루어지는 "관문"입니다. 초기화 프로세스는 이 관문을 통해 상위 클래스 체인까지 이어집니다.


모든 클래스는 하나 이상의 Designated Initializers가 있어야 합니다. 일부 경우에는 하나 이상의 Designated Initializers를 슈퍼클래스에서 상속하여 이 요구 사항을 충족시킵니다.

Designated initializers는 값 타입의 일반적인 이니셜라이저와 같은 방식으로 작성됩니다.

Convenience Initializers

Convenience Initializers는 클래스의 보조 이니셜라이저입니다. Convenience Initializers를 정의하여 동일한 클래스 내의 Designated Initializer를 호출하고, Designated Initializer의 일부 매개변수를 기본 값으로 설정할 수 있습니다. 또한 Convenience Initializers를 정의하여 특정 사용 사례나 입력 값 유형에 대한 클래스 인스턴스를 생성할 수 있습니다.

클래스가 Convenience Initializers를 필요로하지 않는 경우에는 제공하지 않아도 됩니다. 클래스의 일반적인 초기화 패턴에 대한 바로 가기가 시간을 절약하거나 클래스의 초기화를 명확하게 할 때 Convenience Initializers를 만들 수 있습니다.

Convenience initializers는 'convenience' 키워드를 사용하여 선언됩니다. 이니셜라이저는 init 키워드 다음에 convenience 키워드를 붙여서 작성합니다.

Initializer Delegation for Class Types

지정 생성자와 편의 생성자 간의 관계를 단순화하기 위해, Swift는 이니셜라이저 간의 위임 호출에 대해 다음 세 가지 규칙을 적용합니다.

규칙 1
지정 생성자는 즉시 상위 클래스의 지정 생성자를 호출해야합니다.

규칙 2
편의 생성자는 동일한 클래스의 다른 이니셜라이저를 호출해야합니다.

규칙 3
편의 생성자는 궁극적으로 지정 생성자를 호출해야합니다.

이를 이해하기 쉽게 기억하는 간단한 방법

Designated initializer는 항상 위로 위임(delegation up)해야 합니다.
Convenience initializer는 항상 옆으로 위임(delegation across)해야 합니다.

이 규칙은 클래스의 이니셜라이저 구현 방식에만 영향을 미칩니다.

Two-Phase Initialization

Swift에서 클래스 초기화는 두 단계 과정으로 이루어집니다. 첫 번째 단계에서는 각 저장 프로퍼티가 도입한 클래스에서 초기 값을 할당 받습니다. 모든 저장 프로퍼티에 대한 초기 상태가 결정되면 두 번째 단계가 시작되고 각 클래스는 새 인스턴스가 사용 가능해지기 전에 저장 프로퍼티를 추가로 커스터마이징할 수 있는 기회를 갖게 됩니다. 두 단계 초기화 프로세스의 사용은 초기화를 안전하게 만들면서 프로퍼티 값이 초기화되기 전에 접근되는 것을 방지하고 다른 이니셜라이저에 의해 프로퍼티 값이 예기치 않게 변경되는 것을 방지합니다.

Swift의 초기화 흐름은 사용자 정의 초기값을 설정할 수 있으며, 0 또는 nil이 유효한 기본값이 아닌 타입도 처리할 수 있어 더 유연합니다.

Swift 컴파일러는 두 단계 초기화가 오류 없이 완료되도록 다음 네 가지 안전 검사를 수행합니다

안전 검사 1
지정 이니셜라이저는 상위 클래스 이니셜라이저로 위임하기 전에 해당 클래스에서 도입한 모든 속성이 초기화되었는지 확인해야 합니다.

class Vehicle {
    var numberOfWheels: Int

    init() {
        numberOfWheels = 0
    }
}

class Bicycle: Vehicle {
    var hasBasket: Bool

    override init() {
        hasBasket = false
        super.init()
        numberOfWheels = 2
    }
}

객체의 메모리는 저장된 모든 속성의 초기 상태를 알게 된 시점에서만 완전히 초기화된 것으로 간주됩니다. 따라서 이 규칙을 만족시키기 위해 지정 이니셜라이저는 체인으로 위임하기 전에 자신의 모든 속성이 초기화되었는지 확인해야 합니다.

안전 검사 2
지정 이니셜라이저는 상속된 속성에 값을 할당하기 전에 반드시 상위 클래스 이니셜라이저로 위임해야 합니다. 그렇지 않으면 지정 이니셜라이저에서 새로 할당한 값은 상위 클래스의 초기화 과정에서 덮어쓰게 됩니다.

class Vehicle {
    var numberOfWheels: Int

    init() {
        numberOfWheels = 0
    }
}

class Bicycle: Vehicle {
    var hasBasket: Bool

    override init() {
        hasBasket = false
        super.init()
        numberOfWheels = 2
    }
}

안전 검사 3
편의 이니셜라이저는 (해당 클래스에서 정의한 속성을 포함하여) 어떤 속성에 값을 할당하기 전에 반드시 다른 이니셜라이저로 위임해야 합니다. 그렇지 않으면 편의 이니셜라이저에서 새로 할당한 값은 해당 클래스의 지정 이니셜라이저에서 덮어쓰게 됩니다.

class Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    convenience init(name: String) {
        self.init(name: name, age: 0)
        self.age = 20
    }
}

안전 검사 4
인스턴스 메서드를 호출하거나 인스턴스 속성 값을 읽거나 self를 값으로 참조하는 것은 초기화의 첫 번째 단계가 완료된 후에만 가능합니다.

class Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
        print("Person 객체가 초기화되었습니다.")
        
        // Error: 'self' used in method call 'greet' before super.init initializes self
        self.greet()
    }
    
    func greet() {
        print("안녕하세요, 저의 이름은 \(name)이고 나이는 \(age)살입니다.")
    }
}

클래스 인스턴스는 첫 번째 단계가 끝난 후에 완전히 유효합니다. 속성에 접근하거나 메서드를 호출할 수 있는 시점은 첫 번째 단계가 끝난 후에 해당 클래스 인스턴스가 유효하게 되었을 때입니다.

이러한 네 가지 안전 검사를 기반으로 두 단계 초기화가 이루어집니다

두 단계 초기화

1단계

  • 클래스의 지정 이니셜라이저 또는 편의 이니셜라이저가 호출됩니다.
  • 새로운 인스턴스의 메모리가 할당됩니다. 이 메모리는 아직 초기화되지 않은 상태입니다.
  • 해당 클래스에서 도입한 모든 저장 속성이 값을 갖는지 확인합니다. 이 저장 속성들의 메모리는 이제 초기화된 상태입니다.
  • 해당 클래스의 지정 이니셜라이저는 수퍼 클래스 이니셜라이저로 이를 위임하여 해당 수퍼 클래스의 저장 속성도 초기화합니다.
  • 이를 수퍼 클래스 체인을 따라 올라가며 반복합니다.
  • 수퍼 클래스 체인의 끝에 도달하면, 체인의 최종 클래스에서 모든 저장 속성이 값을 갖도록 확인한 후 인스턴스의 메모리는 완전히 초기화된 것으로 간주되고 1단계가 완료됩니다.

2단계

  • 수퍼 클래스 체인을 따라 아래로 돌아가면서 각 지정 이니셜라이저는 인스턴스를 추가로 커스터마이즈할 수 있습니다.
  • 이제 이니셜라이저는 self에 접근하고 해당 속성을 수정하거나 인스턴스 메서드를 호출할 수 있습니다.
  • 최종적으로, 수퍼 클래스 체인의 어떤 편의 이니셜라이저도 인스턴스를 커스터마이즈하고 self와 작업할 수 있습니다.
class Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    convenience init(name: String) {
        self.init(name: name, age: 0)
    }
}

class Student: Person {
    var studentID: Int
    
    init(name: String, age: Int, studentID: Int) {
        self.studentID = studentID
        super.init(name: name, age: age)
    }
    
    convenience init(name: String, studentID: Int) {
        self.init(name: name, age: 0, studentID: studentID)
    }
}

let person1 = Person(name: "John", age: 25)
let person2 = Person(name: "Jane")

let student1 = Student(name: "Mike", age: 20, studentID: 1234)
let student2 = Student(name: "Emily", studentID: 5678)

위 코드에서 Person 클래스는 이름과 나이 속성을 가지고 있습니다. Student 클래스는 Person 클래스를 상속받고, 학생 ID 속성을 추가로 가지고 있습니다.

Person 클래스의 지정 이니셜라이저는 nameage 속성을 모두 초기화해야 합니다. 이니셜라이저에서는 이 속성들의 값을 직접 할당합니다.

Person 클래스의 편의 이니셜라이저는 age 속성을 0으로 초기화해줍니다.

Student 클래스의 지정 이니셜라이저는 studentID 속성을 추가로 초기화합니다. 이니셜라이저에서는 super.init 메서드를 호출하여 Person 클래스의 지정 이니셜라이저를 호출하고, 이후 studentID 속성을 초기화합니다.

Student 클래스의 편의 이니셜라이저는 age 속성을 0으로 초기화하고, super.init 메서드를 호출하여 Person 클래스의 지정 이니셜라이저를 호출합니다.

마지막으로, 객체를 생성할 때 초기화 과정을 거칩니다. 객체 생성 시 지정 이니셜라이저 또는 편의 이니셜라이저 중 하나가 호출됩니다. 이니셜라이저에서 속성을 초기화하고, 이후 초기화가 완료된 객체를 사용할 수 있습니다.

Initializer Inheritance and Overriding

Swift의 서브클래스는 기본적으로 슈퍼클래스의 초기화자를 상속하지 않습니다.

예를 들어, 슈퍼클래스인 Vehicle 클래스가 init 초기화 메서드를 가지고 있고, 이 메서드는 numberOfWheels와 같은 속성을 초기화합니다. 이제 Vehicle 클래스를 상속받는 Car 서브클래스를 만들고, Car 클래스가 init 초기화 메서드를 가지도록 하겠습니다.

class Vehicle {
    var numberOfWheels: Int
    
    init(numberOfWheels: Int) {
        self.numberOfWheels = numberOfWheels
    }
}

class Car: Vehicle {
    var color: String
    
    override init(numberOfWheels: Int) {
        self.color = "Red"
        super.init(numberOfWheels: numberOfWheels)
    }
}

위 예제에서 Car 클래스는 Vehicle 클래스의 init 메서드를 오버라이드하고, color 속성을 초기화합니다. 또한 super.init을 호출하여 Vehicle 클래스의 초기화 메서드를 호출하고, numberOfWheels 속성도 초기화합니다. 이렇게 하면 Car 클래스는 Vehicle 클래스와 동일한 초기화 메서드를 가지며, 동시에 color 속성도 초기화할 수 있습니다.

슈퍼클래스의 지정 이니셜라이저를 오버라이드할 때는 override 한정자를 작성합니다. 심지어 서브클래스의 이니셜라이저 구현이 convenience initializer 인 경우에도 마찬가지입니다. 오버라이드 한정자를 사용하여 지정초기자를 상속받은 클래스에서 구현하면 수퍼 클래스의 지정초기자는 실행되지 않습니다.

class Vehicle {
    var numberOfWheels: Int
    
    init(wheels: Int) {
        numberOfWheels = wheels
        print("Vehicle init")
    }
    
    convenience init() {
        self.init(wheels: 8)
        print("super class convenience init exe")
    }
}

class Car: Vehicle {
    var color: String
    
    override init(wheels: Int) {
        color = "white"
        super.init(wheels: wheels)
        print("Car init")
    }
    
    convenience init() {
        self.init(wheels: 16)
        print("Car class convenience init exe")
    }
}

let testVehicleClass: Vehicle = Vehicle()
print(testVehicleClass.numberOfWheels)

let testCarClass: Car = Car()
print(testCarClass.numberOfWheels)

//출력값
//Vehicle init
//super class convenience init exe
//8
//Vehicle init
//Car init
//Car class convenience init exe
//16

계산 프로퍼티

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

0개의 댓글