10 ByteCoin
프로젝트로 배운 것들
UIPickerView
, JSON Parsing
, Protocol
, delegate
,extension
UIPickerView
이번 프로젝트를 진행하면서 처음 배운 기능이다. Picker란 위 GIF에서도 볼 수 있듯이 하단부의 선택 부분을 의미한다. 그리고 iOS 에서는 UIPickerView로 Picker를 컨트롤 할 수 있었다.
class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
currencyPicker.dataSource = self // 중요
currencyPicker.delegate = self // 중요
coinManager.delegate = self
}
// 구성요소 수 세팅
func numberOfComponents(in pickerView: UIPickerView) -> Int {
// 이건 아마도 카테고리 같다.
// 우린 그냥 각 화폐단위를 보여줄꺼니까 카테고리는 한개
return 1
}
// 얘는 반환을 Int 로 하고
// Picker에 보여지는 개수를 출력하는 듯 하다.
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
// 아마도 코인 개수를 의미하는 듯 하다.
return coinManager.currencyArray.count
}
// 얘는 반환을 String으로 한다.
// Picker에 보여지는 화폐단위를 return한다.
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
// 아래 코드는 아마도, 해당 열의 화폐단위를 세팅하는 듯 하다.
return coinManager.currencyArray[row]
}
// 선택했을때 해당 인덱스가 반환되는 pickerView
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let selectCurrencyArray = coinManager.currencyArray[row]
// 선택한 화폐단위의 가치를 가져오는 메서드
coinManager.getCoinPrice(for: selectCurrencyArray)
}
}
UIPickerViewDataSource, UIPickerViewDelegate
사용
class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate
UIPickerViewDataSource
: UIPicker내에 데이터를 변경할 때 사용하는 Protocol
UIPickerViewDelegate
: UIPicker를 뷰에서 사용할때 사용하는 Delegate
numberOfComponents
사용
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
애플 공식문서에 따르면, 구성요소를 정의하는 곳이라고 한다. 구성요소라 함은, 아마도 내 생각엔 카테고리를 지칭하는 듯 하다. 보통 Table View에서도 이런 구성요소를 지정하는 메서드를 본적이 있다. 그때도 카테고리처럼 나눠지는 것을 볼 수 있었는데, 이것 또한 그런 듯 하다.
위 코드는 각 나라의 화폐단위를 적은 Picker를 사용해야 하니, 카테고리는 딱히 필요 없으니 1개로 설정해놓았다.
pickerView(_ pickerView:component:) -> Int
정의
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
// 아마도 코인 개수를 의미하는 듯 하다.
return coinManager.currencyArray.count
}
위 메서드는 반환타입이 존재한다. Int
로 값을 반환한다.
위 메서드는 PickerView에서 사용할 인덱스들의 개수를 정의하는 메서드인듯 하다.
아까 위 메서드는 카테고리를 지정했다면, 이건 카테고리 안에 들어있는 인덱스들의 개수 정의다.
pickerView(_ pickerView:row:component:) -> String?
정의
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
// 아래 코드는 아마도, 해당 열의 화폐단위를 세팅하는 듯 하다.
return coinManager.currencyArray[row]
}
위 메서드도 반환타입이 존재하는데, 대신 위와 다른 String
타입이다.
아까 위에는 개수를 반환했다면, 이건 각 인덱스에서 표시할 화폐단위들을 반환한다.(USD,HKD 등)
pickerView(_ pickerView:row:component: Int)
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let selectCurrencyArray = coinManager.currencyArray[row]
// 선택한 화폐단위의 가치를 가져오는 메서드
coinManager.getCoinPrice(for: selectCurrencyArray)
}
위 메서드는 PickerView에서 선택됐을 때 그 인덱스를 얻어오는 메서드이다.
하지만 우리는 인덱스 번호를 반환하는게 목적이 아니니, 그 수를 가지고 해당 인덱스에 위치하는 화폐 단위를 가치를 파악할 수 있는 메서드인 getCoinPrice
에 전달하는 역할을 해줬다.
JSON Parsing
JSON 파싱은 ViewController 에서 진행하는게 아닌 다른 스위프트 파일에서 진행한다.
CoinManager로 들어가보자.
protocol CoinManagerDelegate {
func didFailWithError(error: Error)
func didSelectedCoin(coin: CoinModel)
}
struct CoinManager {
let baseURL = "https://rest.coinapi.io/v1/exchangerate/BTC"
let apiKey = "YOUR_API_KEYS"
let currencyArray = ["AUD", "BRL","CAD","CNY","EUR","GBP","HKD","IDR",
"ILS","INR","JPY","MXN","NOK","NZD","PLN","RON",
"RUB","SEK","SGD","USD","ZAR"]
var delegate: CoinManagerDelegate?
func getCoinPrice(for currency: String) {
let urlString = "\(baseURL)/\(currency)?apikey=\(apiKey)"
performRequest(with: urlString)
}
위 코드를 보면 아까 우리가 사용했던 getCoinPrice
가 보이게 된다. 이코드는 아까도 말했듯이 선택한 화폐단위로 현재 비트코인 값을 구해오는 메서드라고 했다.
따라서 위에 미리 정해진 API URL를 조합하여 원하는 화폐단위의 정보를 받아올 수 있는 링크를 만들고 난 뒤에 performRequest
에게 그 url을 전송해주는 역할을 한다.
performRequest
func performRequest(with urlString: String) {
if let url = URL(string: urlString) {
// 2. URL 세션 만들기
// URLSession메서드는 우리가 크롬상에서 보던 JSON 과 같은 형식으로 만들어주는 역할을 한다.
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error ) in
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
if let safeData = data {
// 내 입맛대로 땡겨온 데이터를
if let coin = self.parseJSON(safeData) {
// ViewController에서 사용해야하니 얘한테 옮김
self.delegate?.didSelectedCoin(coin: coin)
}
}
}
task.resume()
}
}
JSON 파싱의 핵심이라고 할 수 있는 performRequest를 보자.
먼저 위에서 만들어진 url을 if let 구문으로 넘어가게 만들고, 세션을 만든다.
이때 URLSession
을 사용하면 우리가 크롬상에서 보는 JSON과 같은 형식으로 만들어준다.
그리고 난 뒤에 세션으로 만든 JSON Data를 task 상수에 저장하려고 dataTask를 해준다. 이때 클로저를 사용하여 해당 정보의 문제가 생기거나, 혹은 더 다양한 기능을 사용하고 싶을때 정의한다.
위 코드에서는 data,response,error
3개의 핸들러를 가지고 진행했고, 클로저 내부에 들어가게 될경우
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
먼저 에러가 발생하지 않을 경우에는 nil을 반환할 것이다. 그러나, 에러가 발생하여 error 핸들러에 일정 값이 들어갔을 경우, 위 코드가 실행되도록 한 것이다.
if let safeData = data {
// 내 입맛대로 땡겨온 데이터를
if let coin = self.parseJSON(safeData) {
// ViewController에서 사용해야하니 얘한테 옮김
self.delegate?.didSelectedCoin(coin: coin)
}
}
에러가 발생하지 않았을경우에 실행되는 코드이다. 먼저 data
핸들러는 옵셔널로 설정된다. 따라서 if let 구문으로 언래핑을 해주고 난 뒤, 파싱을 이어나간다. 언래핑을 한 데이터를 parseJSON
메서드로 옮겨주고(이 메서드는 바로 아래서설명하겠다), 받아온 데이터를 다시 ViewController로 옮긴다.
task.resume()
그리고 마지막 줄에 이 코드는 꼭 써줘야 한다. 마치 다 해놓고 마감버튼을 누르지 않는 느낌이랄까..? 라고 생각하면 된다. 그리고 task.start() 가 아니라 왜 resume() 이냐고 생각하는 사람들이 있을 것이다. 나도 정확히는 모르지만, 기억상 중간에 에러를 걸러내는 코드가 있다고 해도, 에러는 언제든 존재할 수 있기 때문에, resume()으로 중간에 생길 에러를 방지하고자, 에러가 생기면 중지될 수 있게 하기 위해서 였던걸로 기억한다. (사실 잘 모른다. 혹시 아시는 분들 있으면 댓글 부탁드린다.)
parseJSON
// 파싱해온 정보 데이터화 시키기
// 원하는 데이터만 골라 먹기 하는 parseJSON 메서드
func parseJSON(_ coinData: Data) -> CoinModel? {
// JSON을 디코딩할 수 있는 객체
let decoder = JSONDecoder()
do {
// 디코더로 땡겨와서
let decodedData = try decoder.decode(CoinData.self, from: coinData)
// 여기서 원하는 정보들 변수로 저장시키고
let lastPrice = decodedData.rate
let name = decodedData.asset_id_quote
// 모델 파일에 원하는것들만 추가시키고 리턴
let coin = CoinModel(rate: lastPrice, asset_id_quote: name)
return coin
} catch {
delegate?.didFailWithError(error: error)
return nil
}
}
위 코드는 performRequest
에서 파싱 후 언래핑 해온 JSON 데이터를 Swift에서 효율적으로 사용할 수 있게 만드는 코드이다.
parseJSON
의 반환 타입을 보면 CoinModel?
으로 보이는데, 이는 CoinModel로 따로 JSON파일 중에서 내가 필요로 하는 것만 모아논 구조체 swift 파일이다.
struct CoinData: Codable {
let time: String
let rate: Double
let asset_id_quote: String
}
struct CoinModel {
let rate: Double
let asset_id_quote: String
}
이렇게 두개의 구조체가 존재한다. CoinData
는 실제 JSON 파일에서 처음으로 데이터를 가져 오는 것이고(이때 상수의 이름과 타입이 JSON에서 제공하는 것과 동일해야 파싱이 가능하다),
CoinModel
의 경우 가져온 데이터 중에서 내가 필요로 하는 것만 따로 빼온것이다.
따라서 JSON 파일을 통해 CoinData
로 정의된 것들에 대해 파싱을 받아온 후, CoinModel
로 따로 내가 원하는 것들을 또 옮기는 작업을 하는 것이다.
무튼, do - catch 구문을 사용했는데, 먼저 do 구문에 사용한 코드를 보자.
// JSON을 디코딩할 수 있는 객체
let decoder = JSONDecoder()
do {
// 디코더로 땡겨와서
let decodedData = try decoder.decode(CoinData.self, from: coinData)
// 여기서 원하는 정보들 변수로 저장시키고
let lastPrice = decodedData.rate
let name = decodedData.asset_id_quote
// 모델 파일에 원하는것들만 추가시키고 리턴
let coin = CoinModel(rate: lastPrice, asset_id_quote: name)
return coin
}
JSONDecoder()
는 JSON을 디코딩할 수 있게 해주는 객체이다.
디코딩이란, 아까 말했던 JSON 파일을 swift에서 사용할 수 있게 해주는 작업이다.
그리고 do 구문을 열고, decoder 상수에 이미 디코딩을 할 수 있게 적어줬고, 이제 decodedData
에 CoinData
로 지정해뒀던 이름들에 해당하는 정보들을 땡겨와준다.
그리고 난 뒤에 가져온 정보들 중에, 내가 사용할 것들만 상수로 저장 시켜주고 난 뒤에, CoinModel
을 초기화 해준다.
그리고 CoinModel
반환하면 우리가 원했던 각 나라의 시세로 본 비트코인 시세를 확인할 수 있다.
Protocol
protocol CoinManagerDelegate {
func didFailWithError(error: Error)
func didSelectedCoin(coin: CoinModel)
}
프로토콜이란, 일종의 규약, 제약 같은 것이다. "이 프로토콜을 추가 하면 최소한 이정도는 있어야해!" 라고 알려주는 느낌이다. 필수적인 요소들을 넣어놓으면 좋을 기능이다.
위 코드를 예시로 들면, 위 코드는 비트코인의 시세를 각 나라의 화폐 단위로 표시하는 앱을 개발하고 있는 프로토콜이다. 그때 필요한 최소 메서드는, 선택된 화폐단위로 계산된 시세, 에러 발생시의 행동 요 두개의 메서드 정도 일 것이다. 따라서 위 코드에서 그렇게 지정 하였고, 메서드 이외에도 상수, 변수, 구조체, 클래스 등 모든게 선언이 가능하다.
그리고 이 프로토콜을 Class나 extension에서 선택했을때, 위 두개는 꼭 구현해야 한다.
extension
// exction 으로 리펙토링
extension ViewController: CoinManagerDelegate {
// 프로토콜로 추가한 두개의 메서드를 정의
func didFailWithError(error: Error) {
print(error)
}
// 매개변수는 하나면 충분
func didSelectedCoin(coin: CoinModel) {
DispatchQueue.main.async {
// 큐 돌리면서 앱 비동기 방식으로 하나하나~
self.bitcoinLabel.text = String(format: "%.2f", coin.rate)
self.currencyLabel.text = coin.asset_id_quote
}
}
}
extension이란 본래 있던 것에 대해 추가적으로 기능을 부여하거나, 위와 같이 따로 리팩토링이 가능하게 해줄 수 있다. 이로 인한 이점은 코드가 읽기 편해지고, 기존 기능에서 추가적인 기능을 사용할 수 있게 해준다.
위 코드는 아까 위에서 선언한 프로토콜을 구현한 코드이다.
didFailWithError
의 경우 에러발생시 앱이 행해야 할 것을 얘기하고 있는데, 일단 그냥 print() 해줬다.
didSelectedCoin
의 경우 JSON Parsing을 통해 받은 데이터들을 앱 label에 옮겨 사용자에게 보여지게 해야 하는 기능을 구현해야 한다.
따라서, DispatchQueue
를 이용해, 하나하나 데이터를 받아와서 과부하가 오지 않게 데이터를 처리한다. 그렇게 label을 업데이트한다.
그리고 익스텐션을 사용할 때의 주의점이 있다.
var coinManager = CoinManager()
override func viewDidLoad() {
super.viewDidLoad()
currencyPicker.dataSource = self
currencyPicker.delegate = self
coinManager.delegate = self
}
위 코드는 ViewController
의 viewDidLoad()
메서드이다. 여기에 아까 extension으로 선언한 CoinManager의 delegate를 self로 묶어줘야 한다. 그래야지 extension의 모든 코드가 정상 작동한다.
이 코드의 뜻을 내 생각대로 해석해보자면, 일단 ViewController와 연결이 되어있어야 코드가 작동하지 않을까? 애초에 delegate가 대리자 역할을 하는데, 대리자가 의뢰자 없이는 뭔가를 할 수 없듯이 self가 지정되어있어야 코드가 진행 될 것이라고 생각한다.
프로젝트를 하며 느끼고 배운점
API 사용법이 너무 어려워서 강의를 다시 듣고 새로 시작한 챌린지였다. 이번 챌린지를 진행하며 어느정도 API 파싱을 할 수 있게된 듯 하다. 사실 ~Model
, ~Data
파일들의 의미를 잘 몰랐었고, 파싱 자체를 너무나도 어렵게 생각했었지만, 진행되는 코드를 천천히 살피고 의미를 애플 공식문서를 찾아보면서 의미를 곱씹어보니, 그렇게 어려운 것은 아니라고 느끼게 되었다. 뭐..다른 API를 사용해보며 익숙해지는 방법밖에 없는 듯 하다.
extension, protocol 에 대한 기초적인 구성방법 또한 얻어가는 프로젝트였다. 이 앱을 시작으로 API가 좀 더 쉬워지는 계기가 되었으면 한다.
Author And Source
이 문제에 관하여(10 ByteCoin), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@kirri1124/10-ByteCoin저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)