1. 오전일정 : API & JSON 파싱
import Foundation
// playground에서 URLSession 통신하려면 다음의 코드가 필요하다
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
// 1. carData.json 파일을 mocki.io에 올린다.
// 2. URLSession으로 데이터를 가져와 바로 출력이 가능한지 살펴본다
// 3. 차 이름들만 출력되도록 만들어본다
// 4. 목록 데이터들을 더 활용해서 차 이름과 설명, 하이브리드 여부를 print로 출력하도록 한다
struct Res : Codable {
var response: Response
struct Response: Codable {
var header: Header
var body: Body
struct Header : Codable {
var resultCode: String
var resultMsg: String
}
struct Body: Codable {
var items: [MP]
}
}
}
struct MP : Codable {
var o3Value: String
var sidoName: String
var stationName: String
var hour: String
}
var revieseList = [Any]()
func loadJson<T: Decodable>(data: Data) -> T {
do {
return try JSONDecoder().decode(T.self, from: data)
} catch {
fatalError("Unable to parse data: \(error)")
}
}
//func encode<T: Encodable>(data: T) -> Data {
// do {
// return try JSONEncoder().encode(data)
// } catch {
// fatalError("Unable to encode data: \(data)")
// }
//}
let url = URL(string: "https://seogu.go.kr/data/usr/openApi/openApiList.do")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
// data, response, error을 활용한 데이터 가져온 이후의 작업
// data가 nil로서 옵셔널 바인딩에서 else처리 된다면 끝
guard let data else {
print("nothing by error")
return
}
var json = try! JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]
print(json)
// let data1: Res = loadJson(data: data)
// print(data1.response.body.items[0])
}
task.resume()
2. 오후일정 : Actor & Async & Await
https://bbiguduk.gitbook.io/swift/language-guide-1/concurrency
- 동시성이 없으면 '비동기'코드(이 일 끝나면, 저 일 처리해줘!! 하는 코드)를 따로 작성해야 하는데, 동시성 기능을 제공하여 비동기 코드 작성 없이 간편하게 코드를 작성할 수 있게 되었다.
동기 (Synchronous)와 비동기(Asynchronous)
- 동기 방식은 서버에서 요청을 보냈을 때 응답이 돌아와야 다음 동작을 수행할 수 있다. 즉 A작업이 모두 진행 될때까지 B작업은 대기해야한다.
- 비동기 방식은 반대로 요청을 보냈을 때 응답 상태와 상관없이 다음 동작을 수행 할 수 있다. 즉 A작업이 시작하면 동시에 B작업이 실행된다. A작업은 결과값이 나오는대로 출력된다.
https://velog.io/@daybreak/%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC
자 그럼 이제부터 할 일은!!! JSON 파일을 URL을 통해 가져와서 앱에 출력해줄 수 있도록 해보겠다!!
1. JSON 파일을 가져올 공공 데이터 URL
- 코로나 방역센터 데이터
https://api.odcloud.kr/api/15077586/v1/centers?page=1&perPage=10&serviceKey=IqV8U3oL39Xq5A1gSbKYKHnRESAPgGu397bHbKxmiAlqiHoR1Zkf5yNLne8Xtc%2B9MEt8XKceRDvE%2F%2Bu4%2Fn6rbQ%3D%3D
- 위 URL을 통해 받아오는 JSON 구조
(https://jsonlint.com/) 이 사이트를 통하면 JSON 파일의 유효성을 확인하고, 가독성을 확보할 수 있습니다.
{
"currentCount": 10,
"data": [{
"address": "서울특별시 중구 을지로 39길 29",
"centerName": "코로나19 중앙 예방접종센터",
"centerType": "중앙/권역",
"createdAt": "2021-03-03 07:00:52",
"facilityName": "국립중앙의료원 D동",
"id": 1,
"lat": "37.567817",
"lng": "127.004501",
"org": "국립중앙의료원",
"phoneNumber": "02-2260-7114",
"sido": "서울특별시",
"sigungu": "중구",
"updatedAt": "2021-07-16 04:55:08",
"zipCode": "04562"
}, {
"address": "충청남도 천안시 동남구 천안대로 357",
"centerName": "코로나19 중부권역 예방접종센터",
"centerType": "중앙/권역",
"createdAt": "2021-03-03 07:00:52",
"facilityName": "천안시 실내배드민턴장 1층",
"id": 2,
"lat": "36.779887",
"lng": "127.164717",
"org": "순천향대 천안병원",
"phoneNumber": "",
"sido": "충청남도",
"sigungu": "천안시 동남구",
"updatedAt": "2021-07-16 04:55:08",
"zipCode": "31212"
}, {
"address": "광주광역시 동구 필문대로 365",
"centerName": "코로나19 호남권역 예방접종센터",
"centerType": "중앙/권역",
"createdAt": "2021-03-03 07:00:52",
"facilityName": "조선대학교병원 의성관 5층",
"id": 3,
"lat": "35.139465",
"lng": "126.925563",
"org": "조선대병원",
"phoneNumber": "062-220-3739",
"sido": "광주광역시",
"sigungu": "동구",
"updatedAt": "2021-07-16 04:55:08",
"zipCode": "61452"
}, {
"address": "경상남도 양산시 물금읍 금오로 20",
"centerName": "코로나19 영남권역 예방접종센터",
"centerType": "중앙/권역",
"createdAt": "2021-03-03 07:00:53",
"facilityName": "양산 부산대병원 기숙사동 1층",
"id": 4,
"lat": "35.3239",
"lng": "129.009337",
"org": "양산 부산대병원",
"phoneNumber": "055-360-6701",
"sido": "경상남도",
"sigungu": "양산시",
"updatedAt": "2021-07-16 04:55:08",
"zipCode": "50612"
}, {
"address": "대구시 중구 달성로 56",
"centerName": "코로나19 대구광역시 중구 예방접종센터",
"centerType": "지역",
"createdAt": "2021-03-03 07:00:53",
"facilityName": "계명대학교 대구동산병원 별관",
"id": 5,
"lat": "35.869985",
"lng": "128.583716",
"org": "",
"phoneNumber": "053-661-3955",
"sido": "대구광역시",
"sigungu": "중구",
"updatedAt": "2021-07-16 04:55:09",
"zipCode": "41931"
}, {
"address": "서울특별시 성동구 고산자로 270",
"centerName": "코로나19 서울특별시 성동구 예방접종센터",
"centerType": "지역",
"createdAt": "2021-03-15 00:03:43",
"facilityName": "성동구청 대강당(3층)",
"id": 6,
"lat": "37.563457",
"lng": "127.036981",
"org": "",
"phoneNumber": "02-2286-5084",
"sido": "서울특별시",
"sigungu": "성동구",
"updatedAt": "2021-07-16 04:55:09",
"zipCode": "04750"
}, {
"address": "부산 부산진구 시민공원로 73",
"centerName": "코로나19 부산광역시 부산진구 예방접종센터",
"centerType": "지역",
"createdAt": "2021-03-15 00:03:43",
"facilityName": "부산시민공원 시민사랑채",
"id": 7,
"lat": "35.170182",
"lng": "129.059301",
"org": "",
"phoneNumber": "051-605-8633",
"sido": "부산광역시",
"sigungu": "부산진구",
"updatedAt": "2021-07-16 04:55:09",
"zipCode": "47197"
}, {
"address": "인천광역시 연수구 경원대로 526",
"centerName": "코로나19 인천광역시 연수구 예방접종센터",
"centerType": "지역",
"createdAt": "2021-03-15 00:03:43",
"facilityName": "선학경기장 선학체육관",
"id": 8,
"lat": "37.429571",
"lng": "126.703271",
"org": "",
"phoneNumber": "032-749-8121",
"sido": "인천광역시",
"sigungu": "연수구",
"updatedAt": "2021-07-16 04:55:10",
"zipCode": "21908"
}, {
"address": "광주광역시 서구 금화로 278",
"centerName": "코로나19 광주광역시 서구 예방접종센터",
"centerType": "지역",
"createdAt": "2021-03-15 00:03:43",
"facilityName": "빛고을체육관",
"id": 9,
"lat": "35.135361",
"lng": "126.8771731",
"org": "",
"phoneNumber": "062-371-8731",
"sido": "광주광역시",
"sigungu": "서구",
"updatedAt": "2021-07-16 04:55:10",
"zipCode": "62048"
}, {
"address": "대전광역시 유성구 유성대로 978",
"centerName": "코로나19 대전광역시 유성구 예방접종센터",
"centerType": "지역",
"createdAt": "2021-03-15 00:03:44",
"facilityName": "유성종합스포츠센터",
"id": 10,
"lat": "36.378512",
"lng": "127.344399",
"org": "",
"phoneNumber": "042-611-2498",
"sido": "대전광역시",
"sigungu": "유성구",
"updatedAt": "2021-07-16 04:55:11",
"zipCode": "34128"
}],
"matchCount": 284,
"page": 1,
"perPage": 10,
"totalCount": 284
}
-> 위 데이터 구조를 요약하면 아래와 같습니다.
//전체 구조
"currentCount"
"matchCount"
"page"
"perPage",
"totalCount"
"data": [{
"address"
"centerName"
"centerType"
"sido"
.
.
.
.
}]
2. url을 사용해서 데이터를 불러오기 위한 준비를 해줍니다.(url 등록 및 버튼 제작)
파일명 : ContentView.swift(View)
import SwiftUI
struct ContentView: View {
//우리가 가져올 JSON 데이터가 담긴 공공 API url 주소
let url: String = "https://api.odcloud.kr/api/15077586/v1/centers?page=1&perPage=10&serviceKey=IqV8U3oL39Xq5A1gSbKYKHnRESAPgGu397bHbKxmiAlqiHoR1Zkf5yNLne8Xtc%2B9MEt8XKceRDvE%2F%2Bu4%2Fn6rbQ%3D%3D"
var body: some View {
NavigationStack {
VStack {
Button(action: {
//버튼이 눌리면 json 파일에서 데이터를 가져와주는 일을 해야 한다.
}) {
//버튼 디자인(진찰 이미지 + "Fetch Data")
HStack {
Image(systemName: "stethoscope.circle")
.foregroundColor(.accentColor)
.font(.largeTitle)
Text("Fetch Data")
}
}
}
.navigationTitle("COVID-19 Centers")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
3. JSON 데이터를 파싱해서 코드 내로 가져오기 위하여, 1번 하단에 정리된 데이터 구조과 일치하는 구조체를 만들어 줍니다.
파일명 : CovidCenters.swift
import Foundation
//전체 구조
struct CovidCenters: Codable, Hashable {
var currentCount: Int
var data: [CovidCenter]
var matchCount: Int
var page: Int
var perPage: Int
var totalCount: Int
}
//data 하위 세부구조
struct CovidCenter: Codable, Hashable {
var address: String
var centerName: String
var centerType: String
var sido: String
}
여기서 중요한 개념!! 오전 수업처럼 그냥 데이터를 받아오게 하면 안되는 걸까?? 왜 async와 await를 사용해야 할까??
-> 일반적으로 코드는 한줄한줄 실행이 되는 형식이다.
-> 우리가 필요한 건 버튼을 누른 뒤 -> 인터넷으로 데이터를 불러온 후 -> 해당 데이터를 가지고 앱에 보여주는 작업
-> 그럼 내가 짠 코드가 인터넷에 요청을 보냈는데 응답이 오던말던 내 코드는 한줄 실행을 끝났으니 다음줄을 실행하지 않겠는가??
-> 근데 다음줄에 인터넷으로부터 데이터를 가져와서 써야하는 코드라면???!?? 코드가 nil이 되어서 내가 생각한 코드 처리가 안되겠지욥??!?? 그렇게 때문에 인터넷에서 데이터 응답을 받을 때까지 다음줄을 실행하지 말라고 알려줘야 합니다!!
4. URL을 사용해서 데이터를 가져오고, 디코딩을 해서 앱 내 데이터로 반환하는 함수를 Async로 제작
파일명 : WebService.swift
import Foundation
class WebService {
func fetchData(url: String) async throws -> [CovidCenter] {
guard let url = URL(string: url) else { return [] }
//URLSession으로부터 데이터를 받아오는 부분을 await로 선언함
let (data, _) = try await URLSession.shared.data(from: url)
let centers = try JSONDecoder().decode(CovidCenters.self, from: data)
return centers.data
}
}
5. 받아온 JSON 데이터를 함수 리턴 시에 받아줘야 하는데, 받아주는 변수를 기존에 만들어진 CovidCenter로 선언해줘야 함
import Foundation
struct CovidCenters: Codable, Hashable {
var currentCount: Int
var data: [CovidCenter]
var matchCount: Int
var page: Int
var perPage: Int
var totalCount: Int
}
struct CovidCenter: Codable, Hashable {
var address: String
var centerName: String
var centerType: String
var sido: String
}
//ObservableObject로 이 부분을 선언해서 위 구조체를 어디서든 참조해서 쓸 수 있도록 함
class CovidCenterStore : ObservableObject {
@Published var centers: [CovidCenter]
init (centers: [CovidCenter] = []) {
self.centers = centers
}
}
6. async로 선언된 fectch 함수를 불러오는 부분에 함수의 끝에서 await가 된다는 것을 알려주기 위한 코드 작성
import SwiftUI
struct ContentView: View {
//fetchData() return 값을 담는 변수를 생성하기 위한 준비
@ObservedObject var covidCenterStore: CovidCenterStore = CovidCenterStore(centers: [])
//fetchData()함수를 가진 WebService 클래스를 객체로 가져옴 -> 해당 뷰에서 fetchData()를 사용하기 위한 절차
var webService: WebService = WebService()
let url: String = "https://api.odcloud.kr/api/15077586/v1/centers?page=1&perPage=10&serviceKey=IqV8U3oL39Xq5A1gSbKYKHnRESAPgGu397bHbKxmiAlqiHoR1Zkf5yNLne8Xtc%2B9MEt8XKceRDvE%2F%2Bu4%2Fn6rbQ%3D%3D"
var body: some View {
NavigationStack {
VStack {
Button(action: {
Task {
//webService.fetchData 앞에 async함수의 사용이 끝났다는 의미인 await를 선언해줌
//불러온 변수를 JSON과 같은 포맷의 구조체에 저장을 해주기 위해서 covidCeterStore.centers : [CovidCenter]형으로 선언된 변수에 넣어줌
covidCenterStore.centers = try await webService.fetchData(url: url)
}
}) {
HStack {
Image(systemName: "stethoscope.circle")
.foregroundColor(.accentColor)
.font(.largeTitle)
Text("Fetch Data")
}
}
}
.navigationTitle("COVID-19 Centers")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
7. 이제 버튼을 누르면 CovidCenterStore.centers에 우리가 원하는 값이 담겨지면서 화면에 리스트로 출력됨
import SwiftUI
struct ContentView: View {
@ObservedObject var covidCenterStore: CovidCenterStore = CovidCenterStore(centers: [])
var webService: WebService = WebService()
let url: String = "https://api.odcloud.kr/api/15077586/v1/centers?page=1&perPage=10&serviceKey=IqV8U3oL39Xq5A1gSbKYKHnRESAPgGu397bHbKxmiAlqiHoR1Zkf5yNLne8Xtc%2B9MEt8XKceRDvE%2F%2Bu4%2Fn6rbQ%3D%3D"
var body: some View {
NavigationStack {
VStack {
List {
//데이터를 리스트로 출력
ForEach(covidCenterStore.centers, id:\.self) { center in
VStack(alignment: .leading) {
Text("\(center.centerName)")
.font(.headline)
Text("\(center.address)")
.font(.subheadline)
}
}
}
Button(action: {
Task {
covidCenterStore.centers = try await webService.fetchData(url: url)
}
}) {
HStack {
Image(systemName: "stethoscope.circle")
.foregroundColor(.accentColor)
.font(.largeTitle)
Text("Fetch Data")
}
}
}
.navigationTitle("COVID-19 Centers")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
8. 최종 화면
9. 최종 코드
파일명 : ContentView.swift(View)
import SwiftUI
struct ContentView: View {
@ObservedObject var covidCenterStore: CovidCenterStore = CovidCenterStore(centers: [])
var webService: WebService = WebService()
let url: String = "https://api.odcloud.kr/api/15077586/v1/centers?page=1&perPage=10&serviceKey=IqV8U3oL39Xq5A1gSbKYKHnRESAPgGu397bHbKxmiAlqiHoR1Zkf5yNLne8Xtc%2B9MEt8XKceRDvE%2F%2Bu4%2Fn6rbQ%3D%3D"
var body: some View {
NavigationStack {
VStack {
List {
ForEach(covidCenterStore.centers, id:\.self) { center in
VStack(alignment: .leading) {
Text("\(center.centerName)")
.font(.headline)
Text("\(center.address)")
.font(.subheadline)
}
}
}
Button(action: {
Task {
covidCenterStore.centers = try await webService.fetchData(url: url)
}
}) {
HStack {
Image(systemName: "stethoscope.circle")
.foregroundColor(.accentColor)
.font(.largeTitle)
Text("Fetch Data")
}
}
}
.navigationTitle("COVID-19 Centers")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
파일명 : CovidCenters.swift
import Foundation
struct CovidCenters: Codable, Hashable {
var currentCount: Int
var data: [CovidCenter]
var matchCount: Int
var page: Int
var perPage: Int
var totalCount: Int
}
struct CovidCenter: Codable, Hashable {
var address: String
var centerName: String
var centerType: String
var sido: String
}
class CovidCenterStore : ObservableObject {
@Published var centers: [CovidCenter]
init (centers: [CovidCenter] = []) {
self.centers = centers
}
}
파일명 : WevService.swift
import Foundation
class WebService {
func fetchData(url: String) async throws -> [CovidCenter] {
guard let url = URL(string: url) else { return [] }
let (data, _) = try await URLSession.shared.data(from: url)
let centers = try JSONDecoder().decode(CovidCenters.self, from: data)
return centers.data
}
}
3. 오늘의 리뷰
..... 동기 비동기 동기 비동기...
'멋쟁이사자처럼 앱스쿨 1기' 카테고리의 다른 글
[멋쟁이사자처럼] 앱스쿨 1기 - View & Scene & App (22.11.30) (0) | 2022.11.30 |
---|---|
[멋쟁이사자처럼] 앱스쿨 1기 - SwiftUI (22일차 22.10.18) (1) | 2022.10.18 |
[멋쟁이사자처럼] 앱스쿨 1기 - Objective-C (17일차 22.10.11) (1) | 2022.10.11 |
[멋쟁이사자처럼] 앱스쿨 1기 - Objective-C (16일차 22.10.07) (0) | 2022.10.07 |
[멋쟁이사자처럼] 앱스쿨 1기 - Objective-C (15일차 22.10.06) (2) | 2022.10.06 |