-
[맥주] 맥주 추천 어플 만들기(1) - API Requset카테고리 없음 2021. 12. 23. 22:42
Device : 2021 M1 Macbook pro
OS : macOS Monterey 12.1
Xcode : Version 13.1 (13A1030d)이제 Alamofire와 맥주 API를 이용해서 추천 맥주의 정보를 받아오는 부분을 구현하려합니다.
API 테스트
맥주 API에 들어가서 문서를 보면, 추천 맥주를 받아오는 API가 굉장히 간단하게 구현되어있습니다.
이걸 테스트 해 보려면 간단하게
curl
을 제외한 주소를 복사해서 safari에서 열어주면 됩니다.위 화면은 Safari Json Extension을 통해서 확인한 결과이므로, 필요하다면 링크에 들어가서 설치하시면 됩니다.
구조체 Model 만들기
이 JSON결과를 우리가 사용할 수 있게 디코딩하려면, JSON결과와 매칭되는 Codable(Encodable & Decodable) Model객체가 필요합니다.
예를 들어서 위 이미지에서
id
속성을 코드에서 Beer.id와 같은 형태로 접근하기 위해서는 JSON결과를 디코딩해서 구조체에 저장해야 하는 것 입니다.Generate Model를 이용해서 간단하게 구조체 모델을 만들 수 있습니다.
위에서 테스트해본 response를 그대로 복사해서 위 홈페이지에 붙여넣어주면, 오른쪽에 swift 객체가 만들어지게 됩니다.
일단 Copy Code를 눌러 모든 코드를 복사해옵니다.
프로젝트 내에 Model 폴더를 만들고, 그 안에 새로운 Swift파일을 만든 뒤, 복사한 코드를 붙여넣어줍니다.
그 후, 필요한 프로퍼티들만 남기고 모두 제거해줍니다.
구조체의 이름도 목적에 맞게 변경 해 주었습니다.
import Foundation typealias Beer = [BeerInfo] // MARK: - BeerInfo struct BeerInfo: Codable { let id: Int let name, tagline: String let beerDescription: String let imageURL: String let foodPairing: [String] let brewersTips: String enum CodingKeys: String, CodingKey { case id case name, tagline case beerDescription = "description" case imageURL = "image_url" case foodPairing = "food_pairing" case brewersTips = "brewers_tips" } }
맥주 API에 Request해보기
이제, 모든 준비는 끝났고 원하는 정보를 요청해볼 차례입니다.
Alamofire
을 import한 뒤에,ViewController
파일에 다음과 같은 함수를 만들어봅시다.func requestRandomBeer() { let baseURL = URL(string: "https://api.punkapi.com/v2/beers/random")! // 1 AF.request(baseURL).response { response in // 2 if let error = response.error { print("Request Error : ", error) return } guard let data = response.data else { print("Response Data Error") return } // 3 guard let decodedData = try? JSONDecoder().decode(Beer.self, from: data) else { print("Decode Error") return } // 4 guard let beer = decodedData.first else { return } print(beer) } }
간단하게
viewDidLoad()
에서 함수를 실행 해보면, 아래와 같은 결과를 얻을 수 있습니다.간단하게 코드에 대해서 설명 해 보자면 다음과 같습니다.
1
에서는 Alamofire의 기본적으로 사용하는 Session(AF
== URLSession.default)을 통해서baseURL
에request
를 보내고, 그 응답을response
를 통해 받아옵니다.
그 뒤의 closure은 비동기적인 request요청이 끝나면 실행되는 compeletion hander입니다.2
에서는 응답으로 들어온response.error가 nil이 아닐경우
, error가 발생 한 것이므로 그 경우에 대해 처리해줍니다.
또한data가 nil일 경우
에 대해서도 처리해줍니다.3
에서는 response를 디코딩 해주는 과정입니다. 앞서 만들어 놓은Model class(Beer)
의 메타타입(Beer.self)과 디코딩할 데이터를 파라미터로 넣어줍니다.4
에서는 배열의 첫번째 원소를 추출하는 과정입니다. 앞서 API를 테스트 했을 때의 response를 보면, Array의 형태인 것을 알 수 있습니다. 그렇기 때문에 첫번째 원소만 추출해서 사용할 것입니다.
response를 통해 다시 맥주 image 받아오기
위에서 얻은 response를 잘 살펴보면 image주소를 찾을 수 있습니다.
잘 안 보인다면 print(beer) 대신
print(beer.imageURL)
을 해 보면 한눈에 알 수 있습니다.이제 다시 이 imageURL을 통해서 image를 받아올 수 있습니다.
Decoding Model 디버깅
그런데 여러번 request를 돌리는 와중에 종종
Decode Error
가 발생합니다.이것은 Model이 디코딩할 JSON과 형식이 맞지 않기 때문에 디코딩을 실패한 것입니다.
어느 요소가 형식과 일치하지 않는지 살펴보기 위해서 의심이 가는 Modle 요소에 Optional을 추가한 뒤 테스트 해 보았습니다.
가끔씩 imageURL이 없는 경우가 있기 때문에 imageURL 요소를 Optional로 만들어 주어야 합니다.
다시 request
Image를 request하기 위해서 처음 만들었던 함수도 수정 해 줍니다.
func requestRandomBeer() { let baseURL = URL(string: "https://api.punkapi.com/v2/beers/random")! // 1 AF.request(baseURL).response { [weak self] response in if let error = response.error { print("Request Error : ", error) return } guard let data = response.data else { print("Response Data Error") return } guard let decodedData = try? JSONDecoder().decode(Beer.self, from: data) else { print("Decode Error") return } guard let beer = decodedData.first else { return } guard let imageURLString = beer.imageURL, let imageURL = URL(string: imageURLString) else { return } // 2 self?.requestBeerImage(url: imageURL) } }
간단하게 달라진 부분에 대해서 설명하자면
1
weak selfSwift ARC(Autometic Reference Counting)의 동작원리상, 참조 카운트(누군가 자신의 참조를 갖고있으면 +1)가 0이 되어야만 해당 객체의 메모리를 청소해준다.
response
의 함수 원형을 보면, completionHander에 해당하는 closure가@escaping
인 것을 볼 수 있습니다.@escaping
은 함수의 실행이 종료 된 뒤에 실행되는 closure인데, 그 말은 즉 비동기적으로 동작하는response
의 동작이 완전히 끝날 때 까지 메모리상에 closure가 남아서 함수의 동작이 끝난 뒤에 실행이 된다는 뜻입니다.방금 한 말과, ARM의 동작 원리를 생각 해 본다면, closure가 끝날 때 까지 closure에서 캡쳐한
self
는 참조카운트가 +1 되어있는 상태입니다.weak
키워드는 해당 객체의 참조 카운트를 +1 하지 않으면서 객체를 참조하는 형태입니다.따라서 ViewController는 completionHander의 존재 유무에 상관없이 필요할 때 사라질 수 있습니다.
2
에서는 weak self의 영향을 받은 것으로, self가 사라진 상황에서는 실행할 수 없기 때문에 optional chaining을 했습니다.간단하게 설명한다면서 너무 길어져버렸네요..다음, image를 받아오는 함수를 살펴보면 아래와 같습니다.
func requestBeerImage(url: URL) { AF.request(url).response { response in if let error = response.error { print("Request Error : ", error) return } guard let data = response.data else { print("Data Error") return } guard let image = UIImage(data: data) else { print("Image Error") return } print(image) } }
위와 거의 동일하므로 설명은 생략하겠습니다.
그 결과 이미지 객체가 잘 생성 됐습니다!