[Swift] 필수생성자 & 실패가능생성자 & 소멸자

임승섭·2023년 6월 23일
0

Swift

목록 보기
9/35

필수생성자

  • 클래스의 생성자 앞에 required 키워드를 붙이면, 하위클래스에서 반드시 해당 생성자를 구현해야 한다.
    (parameter 이름과 type이 동일한)
  • 필수생성자 자동 상속 : 다른 지정 생성자를 구현하지 않으면, 자동으로 필수생성자 상속
class Aclass {
	var x: Int
    
    required init(x: Int) {	
    	self.x = x
    }
}
// 하위 클래스에서도 반드시 필수 생성자를 구현해야 한다. 
// 재정의 키워드 override가 필요 없고, required 키워드만 붙여주면 된다.


// 다른 지정 생성자를 구현하지 않으면 자동 상속
class Bclass: Aclass {
//    required init(x: Int) {	// 자동 상속
//        super.init(x: x)
//    }
}


// 자동 상속 조건 아닐 때는 반드시 필수생성자 구현해야 한다
class Cclass: Aclass {
    init() {
        super.init(x: 0)
    }
    
    required init(x: Int) {
        super.init(x: x)       // 호출시 required init으로 호출하지 않음
    }
}

실패가능생성자

  • 인스턴스 생성에 실패할 수도 있는 가능성을 가짐 (클래스, 구조체, 열거형 가능)
  • 생성자에 ?를 붙여서 init? 라고 정의
struct Animal {
    let species: String
    
    // 실패가능 생성자
    // (인스턴스를 찍어내는 걸 실패했을 때 nil을 리턴한다
    init?(species: String) {
        if species.isEmpty {		// if species == "" 
            return nil              // 생성자 내에서 실패 가능 부분에 nil을 리턴하면 됨  (문법적 약속)
        }
        self.species = species
    }
}

let a = Animal(species: "Giraffe")		// Giraffe
let b = Animal(species: "")				// nil



/*활용*/
enum TemperatureUnit {
    case kelvin
    case celsius
    case fahrenheit
    
    init?(symbol: Character) {
        switch symbol {
        case "K":
            self = TemperatureUnit.kelvin
        case "C":
            self = TemperatureUnit.celsius
        case "F":
            self = TemperatureUnit.fahrenheit
        default:
            return nil
        }
    }
}

// 그냥 생성하는 경우. 실제로는 이렇게 자주 사용
let c: TemperatureUnit = TemperatureUnit.celsius      // TemperatureUnit()

// 생성자를 이용해서 생성하는 경우
// nil이 될 수도 있기 때문에 Optional로 선언
let f: TemperatureUnit? = TemperatureUnit(symbol: "F")



// 원시값을 이용하는 경우
enum TemperatureUnit1: Character {
    case kelvin = "K"
    case celsius = "C"
    case fahrenheit = "F"
}


// 원시값이있는 열거형은 자동으로 실패가능 생성자 init?(rawValue :)를 구현함 ==> 일치하면 인스턴스 생성, 아니면 nil

// 원시값이 있는 경우 생성하는 방법. nil을 리턴할 수 있기 때문에 Optional
let f1: TemperatureUnit1? = TemperatureUnit1(rawValue: "F")     // .fahrenheit

// 실제로 nil이 리턴되기 때문에
let u: TemperatureUnit1? = TemperatureUnit1(rawValue: "X")      // nil


// 실패가능 생성자를 이용해서 구현한 것과 원시값을 이용해서 구현한 것이 거의 유사하다

유의할 점

호출관계

  • 동일 단계에서 호출 관계 (Delegate across)

    • 실패 가능 생성자는 실패 불가능 생성자를 호출 가능
    • 실패 불가능 생성자는 실패가능 생성자를 호출 불가능
    struct Item {
    	var name = ""
     	
        /*(1)*/
        init() {
        }
        
        init? (name: String) {	// 실패가능 생성자가 실패 불가능 생성자 호출 -> OK
        	self.init()
        }
        
        /*(2)*/
        init() {	// 실패 불가능 생성자가 실패가능 생성자 호출 -> NO
        	self.init(name: "하이")
        }
        
        init? (name: String) {
        	self.name = name
        }
  • 상속 단계에서 호출 관계 (Delegate up)

    • (하위)실패불가능 생성자가 (상위)실패가능 생성자 호출 불가능
    • (하위)실패가능 생성자가 (상위)실패불가능 생성자 호출 가능
    
    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 }     // 상품의 갯수가 1보다 작으면 ====> 카트 항목 생성 실패
            self.quantity = quantity           // 수량이 한개 이상이면 ====> 초기화 성공
            super.init(name: name)             // "" (빈문자열이면)  ====> 실패 가능 위임 OK
        }
    }

(상속 관계에서) 재정의

  • (상위에서) 실패가능 생성자를 (하위에서) 실패불가능 생성자로 재정의 가능 (강제 언래핑 활용 가능)

  • (상위에서) 실패불가능 생성자를 (하위에서) 실패가능 생성자로 재정의 불가능

    class Document {
           var name: String?
    
           init() {}                	// 실패불가능 생성자
    
           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]")!	// 실패가능 생성자이기 때문에 nil을 리턴할 수도 있다.
           // 하지만 문자열을 넣어주고 있기 때문에 nil이 나올 수가 없다.
           // 그래서 !를 붙여서 강제 언래핑을 시킨다.
       }
    }

소멸자

  • 인스턴스가 메모리에서 해제되기 직전에 정리가 필요한 내용을 구현하는 메서드
  • 클래스에만 존재
  • 클래스 정의 시 최대 1개의 소멸자
  • 파라미터 사용x
  • 인스턴스의 모든 속성에 access 가능
  • 직접 호출 불가능 (직접 호출하는 문법이 없다)
  • 인스턴스(객체)가 메모리에서 제고되기 직전에 자동으로 호출됨.
  • 특별한 작업 수행중인 경우, 몇가지 추가 정리를 직접 수행해야할 필요 있음
    • ex). 파일 열고 데이터 쓰는 경우, 인스턴스 할당 해제되기 전에 파일을 닫아야 함
class Aclass {
	var x = 0
    var y = 0
    
    deinit {		// 실제로 객체가 사라지는지 확인하기 위해 print로 구현
    	print("인스턴스의 소멸 시점")
    }
}
var a: Aclass? = Aclass()	// nil을 담을 수 있게 Optional로 선언
a = nil						// "인스턴스 소멸 시점" 출력
// nil 할당으로 인해 메모리에 있던 애가 없어지게 되고, 소멸자를 호출하게 된다.

Summary

구분StructClass
1. 지정 생성자
Designated
init() {}
init(paramter) {}
init() {}
init(parameter) {}
2. 편의 생성자
Convenience
Xconvenience init(parameter) {}
상속과 관련
3. 필수 생성자
Required
Xrequired init(parameter) {}
상속과 관련
4. 실패가능 생성자
Failable
init?(parameter) {}
init!(parameter) {}
init?(parameter) {}
init!(parameter) {}
5. 소멸자
Deinitializers
Xdeinit{}

0개의 댓글