TourApp (6)
Api 적용하기
우선 json으로 만든 파일을 웹사이트에 올려 api처럼 가져오게 했다.
이후, Medium 글을 통해서 코드를 작성했다.
이글을 통해서 작성한것은 바로 Generic
을 사용했다는 점이다. 이전에 Generic을 사용해본적이 없기에 이번에는 좀 사용하면서 내걸로 조금씩 만들고 싶었다.
ApiModel 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct ApiModel: Codable {
let tours: [Tour]
}
enum NetworkError: Error {
case badUrl
case invalidRequest
case badResponse
case badStatus
case failedToDecodeResponse
}
class ApiService {
func downloadData<T: Codable>(fromURL: String) async -> T? {
do {
guard let url = URL(string: fromURL) else { throw NetworkError.badUrl }
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse else { throw NetworkError.badResponse }
guard response.statusCode >= 200 && response.statusCode < 300 else { throw NetworkError.badStatus }
guard let decodedData = try? JSONDecoder().decode(ApiModel.self, from: data) else {
throw NetworkError.failedToDecodeResponse
}
return decodedData.tours as? T
} catch NetworkError.badUrl {
print("There was an error creating the URL")
} catch NetworkError.badResponse {
print("Did not get a valid response")
} catch NetworkError.badStatus {
print("Did not get a 2xx status code from the response")
} catch NetworkError.failedToDecodeResponse {
print("Failed to decode response into the given type")
} catch {
print("An error occured downloading the data")
}
return nil
}
}
여기서 하나 다른점이라면
1
2
3
guard let decodedData = try? JSONDecoder().decode(ApiModel.self, from: data) else {
throw NetworkError.failedToDecodeResponse
}
이 부분이다 참고글에서는 여기도 역시 T.self
를 통해 Generic을 사용했지만, 내가만든 json의 구조에서는
1
2
3
4
5
6
7
8
9
"tours": [
{
"title": "해운대",
"imageUrl": "https://www.visitbusan.net/uploadImgs/files/cntnts/20191229153531987_oen",
"description": "해운대해수욕장은 대한민국 부산광역시 해운대구 중동과 우동에 걸쳐서 위치한 대한민국 최대규모의 해수욕장이다.\n모래사장의 총면적은 120,000m², 길이는 1.5 km,\n폭은 70m ~ 90m이다",
"address": "부산광역시 해운대구 해운대해변로 280",
"latitude": 35.1594965,
"longitude": 129.162576,
"resList": [
이런식으로 tours라는 녀석으로 시작해서 담고있기에 Generic을 사용하려면 애초에 json을 구성할때 tours를 뺐어야했다.
하지만 이미 업로드 하기도 해서 저기만 ApiModel.self
를 사용했다.
ApiViewModel 만들기
@MainActor 항상 Main Queue에서 실행이 된다. 즉 Uikit에서 사용했던 DispatchQueue.main
이라고 생각하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Foundation
@MainActor class ApiViewModel: ObservableObject {
@Published var apiData = [Tour]()
init() {
Task {
await fetchData()
}
}
func fetchData() async {
let url = "https://run.mocky.io/v3/42391865-6e96-4db3-9f68-1e2970796cad"
guard let downloadedData: [Tour] = await ApiService().downloadData(fromURL: url) else { return }
apiData = downloadedData
}
}
특징 | DispatchQueue.main | @MainActor |
---|---|---|
메인 스레드에서의 실행 요청 | 명시적으로 DispatchQueue.main.async 사용 | 자동으로 메인 스레드에서 실행 |
적용 범위 | 특정 코드 블록 | 메서드, 프로퍼티, 클래스 전체 |
코드 가독성 | 복잡할 수 있음 | 가독성이 높아짐 |
비동기 메서드와의 호환성 | 추가로 async 블록 필요 | async 메서드에서도 안전하게 실행 |
즉,
- @MainActor를 사용하면, 명시적으로 DispatchQueue.main.async를 사용할 필요가 없다.
- @MainActor는 메서드나 클래스 전체에서 메인 스레드 실행을 보장하기 때문에, 코드가 더 간결하고 안전해진다.
- Swift의 최신 비동기 API(async/await)와 잘 어울리며, UI 업데이트 코드에서 더 많이 사용된다.
init을 사용한건, 여기서 내가 만든 json은 이미 내용이 정해져있고, 양이 방대하지도 않고 페이징이필요없기에 한번 로드하면 끝이라 init으로 한번 로드했을때 가져오게 했다.
그게아니라 View가 다시 로드될때마다 사용하고 싶다면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@ObservedObject var json = loadJsonModel()
@StateObject var vm = ApiViewModel()
NavigationStack {
Text("관광 고고")
List {
ForEach(vm.apiData, id: \.self) { tour in
NavigationLink(value: tour) {
CellView(title: tour.title, imageUrl: tour.imageURL)
}
}
}
.navigationDestination(for: Tour.self) { model in
DetailView(title: model.title, imageUrl: model.imageURL, description: model.description, address: model.address, coordinate: CLLocationCoordinate2D(latitude: model.latitude, longitude: model.longitude), shopList: model.resList, cameraPosition: .camera(MapCamera(centerCoordinate: CLLocationCoordinate2D(latitude: model.latitude, longitude: model.longitude), distance: 500, heading: 90, pitch: 80)))
}
}
.onAppear {
if vm.apiData.isEmpty {
Task {
await vm.fetchData()
}
}
}
onAppear
를 통해서 함수를 호출하면 된다.
@ObservedObject vs @StateObject 비교는 새롭게 글을 작성해야 할듯하다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.