
import Foundation
//Create an array of landmarks that you initialize from landmarkData.json.
var landmarks: [Landmark] = load("landmarkData.json")
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
// Data(contentsOf:) 메서드를 사용하여 파일의 내용을 Data 객체로 읽어온다.
// 파일을 읽는 과정에서 예외가 발생할 수 있으므로 try-catch 문을 사용하여 예외 처리를 한다.
// 예외가 발생한 경우, fatalError를 통해 프로그램을 중지하고 오류 메시지를 출력한다.
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
// JSONDecoder 인스턴스를 생성하고, decode(_:from:) 메서드를 사용하여 데이터를 디코딩한다.
// 디코딩할 타입은 T.self로 전달되며, 이는 제네릭으로 전달받은 타입 T의 실제 타입을 나타냅니다.
// 디코딩 과정에서 예외가 발생할 수 있으므로 try-catch 문을 사용하여 예외 처리를 한다.
// 예외가 발생한 경우, fatalError를 통해 프로그램을 중지하고 오류 메시지를 출력한다.
// 디코딩이 성공하면 디코딩된 값인 T를 반환한다.
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
위의 코드는 제네릭 함수인 load를 정의하고 있다. 파일에서 데이터를 로드하고, 해당 데이터를 지정된 타입으로 디코딩하여 반환하는 코드이다! Decodable 프로토콜을 준수하는 구조체 또는 클래스 타입을 지정하여 load 함수를 호출하면, 해당 파일의 데이터가 디코딩되어 해당 타입의 인스턴스로 반환된다. 이를 통해 JSON 데이터를 Swift 객체로 변환하여 사용할 수 있다.
file 변수를 옵셔널 바인딩 → 메서드의 결과가 nil이 아닌 경우에만 file에 값이 할당 → 파일의 내용을 Data 객체로 읽어오기 → JSONDecoder 인스턴스를 생성 → decode(_:from:) 메서드를 사용하여 데이터를 디코딩
🤔 제네릭 타입 T?
제네릭 타입 T는 함수나 타입을 정의할 때 실제로 사용될 타입을 나타내는 일종의 플레이스홀더
T는 타입 파라미터로서, 함수가 호출될 때 실제 타입으로 대체된다.
제네릭을 사용하여 함수나 타입을 정의하면, 여러 다른 타입에 대해 동작하는 유연하고 재사용 가능한 코드를 작성할 수 있다. 함수나 타입을 일반화할 수 있으며, 다양한 타입에 대해 동일한 동작을 수행할 수 있습니다.
위의 코드에서 load 함수는 T라는 제네릭 타입을 받는다. 이 함수를 호출할 때 T에는 실제로 사용할 타입이 지정된다.
예를 들어, load<Landmark>("landmarks.json")와 같이 호출하면 T는 Landmark 타입으로 대체된다.
제네릭을 사용하면 코드를 타입에 독립적으로 작성할 수 있으므로, 함수나 타입을 재사용할 수 있는 장점이 있다. 다양한 타입에 대해 동작하는 단일 함수를 작성하고, 해당 함수를 다양한 타입으로 호출할 수 있다. 이로써 코드의 중복을 줄이고 유지보수성을 향상시킬 수 있다.
🤔 filename 앞에 붙는 '_' 는 뭘까?
`load` 함수의 매개변수 `filename` 앞에 붙은 `_`는 와일드카드 식별자입니다. 와일드카드 식별자는 매개변수 이름을 생략하고자 할 때 사용된다.
`load` 함수는 호출될 때 파일 이름을 인자로 전달받아야 한다. 그러나 함수 내에서는 `filename`을 사용하지 않고 있다. 따라서, 함수 호출자에게 파일 이름을 전달하기 위한 목적으로 매개변수 이름을 필요로 하지 않는다.
와일드카드 식별자 `_`는 이러한 경우에 사용된다. 매개변수 이름을 생략함으로써 호출자에게서 파일 이름을 전달받을 수 있게 된다. 함수 내에서는 해당 매개변수를 사용하지 않으므로 생략해도 문제가 없다.
따라서, `func load<T: Decodable>(_ filename: String)`은 매개변수 `filename`을 가지지만, 호출 시에는 파일 이름만 전달하면 된다. `_`는 호출자에게 해당 매개변수의 이름을 감추고 싶을 때 사용되는 표기법이다.
+) 매개변수의 이름을 감추지 않을 경우 코드의 모습let result: MyType = load<MyType>(filename: "data.json")
🤔 guard는 뭐지?
guard 구문의 주요 목적은 조건이 충족되지 않으면 조기에 함수를 종료하거나 예외 처리를 수행하여 코드 실행을 계속하기 전에 예외 상황을 처리하는 것
: 코드의 가독성을 향상, 조건을 충족하지 않을 때의 특수한 상황을 명시적으로 처리가 가능
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
위의 코드는 guard 구문을 사용하여 Bundle.main.url(forResource:withExtension:) 메서드의 결과를 바인딩하고, 조건을 충족하지 않을 경우 fatalError를 발생시키는 코드이다.
Bundle.main.url(forResource:withExtension:) 메서드를 호출하여 파일의 URL을 가져온다. 이때 filename을 파일 이름으로 전달하고, withExtension은 파일의 확장자를 나타내는데, nil로 전달되어 확장자를 생략함을 의미한다.
guard 구문의 조건에서는 file 변수를 옵셔널 바인딩한다. Bundle.main.url(forResource:withExtension:) 메서드의 결과가 nil이 아닌 경우에만 file에 값이 할당되고, 조건을 충족하지 않을 경우 else 블록이 실행된다.
else 블록 내부에서는 fatalError를 호출하여 오류 메시지와 함께 프로그램을 중지시킨다. fatalError 함수를 호출하면 프로그램이 즉시 종료되며, 이후의 코드는 실행되지 않는다.
즉, 위의 코드는 파일의 URL을 가져오는 과정에서 문제가 발생한 경우, 프로그램을 중지하고 오류 메시지를 출력하는 것을 목적으로 한다. 이는 파일을 찾을 수 없는 상황 등 예상치 못한 오류에 대비하여 프로그램의 안정성을 높이는데 도움을 준다.
반응형
'Apple Developer Academy > 🎇 Swift' 카테고리의 다른 글
| [Swift] how to create multiple preview (45) | 2023.06.08 |
|---|---|
| [Swift] 반복문에서 id: \.id 의 의미 (0) | 2023.06.08 |
| [Swift] Swift 에서 Hashable, Codable은 뭘까? Protocol 알아보기 (0) | 2023.06.03 |
| [SwiftUI] 헷깔리는 State, Binding, ObservedObject, EnvironmentObject 총정리 (0) | 2023.05.31 |
| [SwiftUI Tutorials] SwiftUI Essentials - Creating and Combining Views (0) | 2023.03.26 |
댓글