구조체의 확장과 생성자 관련 세부사항

확장에서도 생성자를 구현할 수 있지만, 모든 생성자를 구현할 수는 없다.

잘생각해보자
class에서 편의생성자는 delegate across로 같은 스코프? 계층에 있는 생성자를 호출 호출된 지정생성자는 메모리를 찍어내는 역할을 함.

  • 확장에서도, 말 의미 그대로 지정생성자를 추가하기보다는, 편의 생성자만 생성가능 (지정생성자 추가 불가/소멸자 추가 불가)
  • 다만 클래스가 아닌 경우에, 본체에 지정생성자를 호출하는 방법만 가능(convinence는 아니지만, 비슷한 방식)
  • [예외] 값타입(구조체)의 경우 저장속성에 기본값/생성자 정의안한 경우, 생성자 구현 가능

클래스에서의 확장예시

// UIColor 기본 생성자
var color = UIColor(red: 0.3, green: 0.5, blue: 0.4, alpha: 1)
    


extension UIColor {      // 익스텐션 + 편의생성자 조합
    
    convenience init(color: CGFloat) {   // Float   / Double
        self.init(red: color/255, green: color/255, blue: color/255, alpha: 1)
    }

}


// 아주 쉽게 객체 생성하는 방법을 제공 가능해짐

UIColor(color: 1)
//UIColor(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)

구조체에서의 확장 예시

//일단 확장 개념 뺴고!!

//클래스였다면
class Point {
    var x = 0.0, y = 0.0

    convenience init(){
        self.init(x:0.0, y:0.0)
    }

    init(x: Double, y:Double){
        self.x = x
        self.y = y
    }
}

//구조체라서 클래스랑 비슷한 방식이지만 convenience 키워드 생략 가능하다.
struct Point {
    var x = 0.0, y = 0.0
    
    init(){
        self.init(x:0.0, y:0.0)
    }

    init(x: Double, y:Double){
        self.x = x
        self.y = y
    }
}

구조체는 (원래) 편의 생성자가 존재하지 않고, 상속과 관련이 없기 때문에 지정생성자의 형태로도 자유롭게 생성자 구현 가능


struct Point {
    var x = 0.0, y = 0.0
}

extension Point{
    init(){
        self.init(x:0.0, y:0.0)
    }
}

에러가 나는 이유? -> 본체에 멤버와이즈 이니셜라이저랑 기본생성자가 자동 구현이 됨. 따라서 확장에서 같은 기본 생성자를 한번 더 구현하게 되면 에러가 난다!


struct Point {
    var x = 0.0, y = 0.0
}

extension Point{
    init(num:Double){
        self.init(x:num, y:num) // 본체의 생성자 호출 부분→
    }
}

이런식으로 다른 생성자를 호출하면 에러가 나지 않음. → 약간 편의 생성자 느낌이긴 하다.


구조체에서 직접 생성자 구현

직접 생성자 구현하면, 기본 생성자 init() → 멤버와이즈 생성자 제공 안되는 것이 원칙

struct Point {
    var x = 0.0, y = 0.0

    init(x: Double, y:Double){
        self.x = x
        self.y = y
    }
}

extension Point{
    init(){
        init(x: 0.0, y:0.0)
    }

}

예외! 기본값 + 생성자 정의 안한 경우

모든 저장속성에 기본값제공 + 본체에 직접 생성자를 구현하지 않았다면, 멤버와이즈 생성자 자동생성 + 확장에서 생성자 구현해도 괜찮음

struct Point {
   var x = 0.0, y = 0.0
}

extension Point{
   init(num:Double){
       self.x = num
       self.y = num
   }
}

Swift 공식문서의 예시

struct Point {
   var x = 0.0, y = 0.0
   
   //init(x: Double, y: Double)
   //init()
}


struct Size {
   var width = 0.0, height = 0.0
}


// Rect구조체
struct Rect {     //기본 생성자 / 멤버와이즈 생성자가 자동 제공 중
   var origin = Point()
   var size = Size()
}



let defaultRect = Rect()    // 기본생성자

Rect(origin: Point(x: Double, y: Double), size: Size(width: Double, height: Double))

let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
                         size: Size(width: 5.0, height: 5.0))    
// 멤버와이즈 생성자



extension Rect {
   // 센터값으로 Rect 생성하는 생성자 만들기 
   init(center: Point, size: Size) {
       let originX = center.x - (size.width / 2)
       let originY = center.y - (size.height / 2)
       
       // (1) 본체의 멤버와이즈 생성자 호출 방식으로 구현 가능
       self.init(origin: Point(x: originX, y: originY), size: size)
       
       // (2) 예외적인 경우, 직접 값을 설정하는 방식으로도 구현 가능
       self.origin = Point(x: originX, y: originY)
       self.size = size
   }
}


// 새로 추가한 생성자로 인스턴스 생성해보기

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
                     size: Size(width: 3.0, height: 3.0))

예외적인 경우 → 저장속성에 기본값 + 본체에 생성자 구현 안한 경우 여전히 기본생성자/멤버와이즈 생성자 제공

생성자 회고

class Dog{
   var name: String?
}

Dog()      //가능 -> Nil

사실 모든 저장속성에 기본값이 있다면 init을 자동적으로 swift에서 만들어주고 있는 셈

struct Dog{
   var name: String
   var weght: Int
   var height: Int
}


Dog(name: String, weight: Int, height: Int)       
//기본생성자 + 멤버와이즈 이니셜라이저 제공

그렇다면 이런 경우엔?

 struct Dog{
    var name: String = "초코"
    var weght: Int = 0
    var height: Int
 }


 Dog(height: Int)    
 Dog(name: String, weight: Int, height: Int)       

멤버와이즈 이니셜라이저는 기본으로 생성되어지고, 추가로 우리는 height값만 있다면 인스턴스를 찍어낼 수 있기 때문에 height만 파라미터로 받는 생성자도 추가적으로 제공되는 것이다.


 struct Dog{
    var name: String = "초코"
    var weght: Int = 0
    var height: Int = 0
 }

 extension Dog{
    init(name: String){
        self.name = name
    }
 }

 Dog()
 Dog(name: String, weight: Int, height:Int)
 Dog(name: String)
 //셋 모두 제공

원칙적으로는 init을 개발자가 추가했으니, 본체의 멤버와이즈는 작동되지 않아야하는 것이 원칙이나, 구조체에 한해서, 예외적으로 모두 다 제공이 된다.

멤버의 확장(서브스크립트)

  • 메서드이기 뭐 당연한 소리겠다.
extension Int {
    subscript(num: Int) -> Int {
        
        var decimalBase = 1
        
        //자릿수 선정(10의 자리, 100의 자리..)
        for _ in 0..<num {  
            decimalBase *= 10
        }
        
        return (self / decimalBase) % 10
        
    }
}

중첩타입

클래스, 구조체 및 열거형에 새 중첩 유형을 추가 가능한 속성이다. 다만, class 안에 class… 어디서 많이 본 듯하다. 오버라이딩 가능한 타입속성에서 static 키워드 대신 class 키워드를 사용했었다. 헷갈리지 말자!

오버라이드? 슈퍼클래스로부터 상속받은 메서드, 프로퍼티들을 재정의하는 키워드

class Dongchan {
   class func sayBye() {
        print("Bye")
    }
}
 
class DongchanAlex: Dongchan{
    override class func sayBye() {     
    }
}

그럼 이제 중첩이 어떤 식으로 이루어지는 지 알아보자
// Int(정수형 타입)에 종류(Kind) ====> 중첩 열거형 추가해 보기

extension Int {
    
    enum Kind {       // 음수인지, 0인지, 양수인지
        case negative, zero, positive
    }
    
    var kind: Kind {    // 계산 속성으로 구현
        switch self {
        case 0:                   // 0인 경우
            return Kind.zero
        case let x where x > 0:   // 0보다 큰경우
            return Kind.positive
        default:                  // 나머지 (0보다 작은 경우)
            return Kind.negative
        }
    }
}

????그럼 rawValue 사용할 수 있나?
Int.Kind.positive.rawValue
Int.Kind.zero
Int.Kind.negative

에러발생 → 열거형 다시 복습해야겠다 열거형 자체에 Int형으로 되어 있지 않기 때문에 아무리 rawvalue로 접근하고 싶어도 그럴 수 없다. 만약 열거형을 Int형으로 바꿔서 정의해준다면 가능하다!

extension Int {
    
    enum Kind: Int {       // 열거형 Int형으로 교체
        case negative, zero, positive
    }
    
    var kind: Kind {    
        switch self {
        case 0:                   
            return Kind.zero
        case let x where x > 0:   
            return Kind.positive
        default:                 
            return Kind.negative
        }
    }
}

Int.Kind.positive.rawValue      //2