아래는 제가 선언한 구조체입니다. dictionary를 받아서 각각의 stored property를 초기화하고 있습니다. 하지만 이 initializer는 코드로는 무조건 성공할 수 있게 되어 있지만 결과적으로는 실패할 수 있는 여지를 가지고 있습니다.
studyState라는 property를 예로 들어보겠습니다. 이 property는 enum인 StudyState 타입을 가지고 있습니다. 그리고 init을 할 때 일단 dictionary 안에 있는 rawValue를 Int로 캐스팅하고 그 rawValue를 사용해서 다시 enum의 인스턴스를 만들어서 property에 할당합니다.
이 과정에서 dict["studyState"]가 nil인 경우, dict["studyState”]를 Int로 캐스팅하는 것을 실패한 경우, 마지막으로 StudyState(rawValue: rawValue)가 nil인 경우 3가지 케이스의 경우 해당 init은 실패했다고 간주해야 합니다.
하지만 아래에 구현된 initializer의 경우 ??를 통해 기본값을 넣어줌으로서 일단 init이 되도록 하고 있습니다. 하지만 올바른 방법은 아닙니다. 위 예시와 같은 문제가 생기는 경우 error를 던지도록 initializer를 수정해보겠습니다.
import Foundation
enum StudyState: Int {
case undefined = 0, success, fail
}
protocol Word {
var id: String { get }
var wordBookID: String { get }
var meaningText: String { get }
var meaningImageURL: String { get }
var ganaText: String { get }
var ganaImageURL: String { get }
var kanjiText: String { get }
var kanjiImageURL: String { get }
var studyState: StudyState { get set }
var timestamp: Date { get }
var hasImage: Bool { get }
}
struct WordImpl: Word {
let id: String
let wordBookID: String
let meaningText: String
let meaningImageURL: String
let ganaText: String
let ganaImageURL: String
let kanjiText: String
let kanjiImageURL: String
var studyState: StudyState
let timestamp: Date
var hasImage: Bool {
!self.meaningImageURL.isEmpty || !self.ganaImageURL.isEmpty || !self.kanjiImageURL.isEmpty
}
// TODO: Handle Parsing Error
init(id: String, wordBookID: String, dict: [String : Any]) {
self.id = id
self.wordBookID = wordBookID
self.meaningText = dict["meaningText"] as? String ?? ""
self.meaningImageURL = dict["meaningImageURL"] as? String ?? ""
self.ganaText = dict["ganaText"] as? String ?? ""
self.ganaImageURL = dict["ganaImageURL"] as? String ?? ""
self.kanjiText = dict["kanjiText"] as? String ?? ""
self.kanjiImageURL = dict["kanjiImageURL"] as? String ?? ""
let rawValue = dict["studyState"] as? Int ?? 0
self.studyState = StudyState(rawValue: rawValue) ?? .undefined
self.timestamp = dict["timestamp"] as? Date ?? Date()
}
}
일반적인 함수나 메소드와 마찬가지로 에러를 던지는 initializer도 마찬가지로 throws라는 키워드를 써서 에러를 던지는 메소드라는 것을 알려줍니다.
그리고 내부에서 if문을 통해서 stored property에 할당할 변수가 nil이 된다면 에러를 던지도록 구현하면 됩니다.
init(id: String, wordBookID: String, dict: [String : Any]) throws {
self.id = id
self.wordBookID = wordBookID
if let meaningText = dict["meaningText"] as? String,
let meaningImageURL = dict["meaningImageURL"] as? String,
let ganaText = dict["ganaText"] as? String,
let ganaImageURL = dict["ganaImageURL"] as? String,
let kanjiText = dict["kanjiText"] as? String,
let kanjiImageURL = dict["kanjiImageURL"] as? String,
let rawValue = dict["studyState"] as? Int,
let studyState = StudyState(rawValue: rawValue),
let createdAt = dict["createdAt"] as? Date
{
self.meaningText = meaningText
self.meaningImageURL = meaningImageURL
self.ganaText = ganaText
self.ganaImageURL = ganaImageURL
self.kanjiText = kanjiText
self.kanjiImageURL = kanjiImageURL
self.studyState = studyState
self.createdAt = createdAt
} else {
throw AppError.Initializer.wordImpl
}
}