iOS 개발자 되기

[swift] Alamofire 걸음마 3 (Alamofire)

순진이 2022. 1. 30. 12:13

오늘은 마지막 걸음마입니더

첫 걸음에는 CocoaPods을 설치하고, pod을 추가했구여,

두 걸음에는 SnapKit을 통해 기본 UI를 잡았드랬져.

 

Alamofire은 많은 블로거분들이 자세하게 잘 써주셨었던데 ㅠㅠ 저는 걸음마니까여,,!!

시작해볼게유.

 


📍xcworkspace 파일로 여는 것 잊지 말기

 

일단 지난 번까지 했던 코드를 볼게요.

import UIKit
import SnapKit
import Alamofire

class ViewController: UIViewController {
    
    let mainLbl = UILabel()
    let myButton = UIButton()
    let imgView = UIImageView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
        view.backgroundColor = .white
        
    }
}

//MARK: -UI
extension ViewController {
    final private func configureUI() {
        setAttributes()
        addTarget()
        setConstraints()
    }
    
    final private func setAttributes() {
        mainLbl.text = "No info"
        mainLbl.textColor = UIColor.darkGray
        mainLbl.layer.borderWidth = 1
        mainLbl.layer.cornerRadius = 10
        mainLbl.textAlignment = .center
        myButton.setTitle("Get Temperature", for: .normal)
        myButton.setTitleColor(.blue, for: .normal)
        imgView.image = UIImage(named: "cool-background")
        
    }
    final private func addTarget() {
        myButton.addTarget(self, action: #selector(getTempTapped(_:)), for: .touchUpInside)
    }
    
    final private func setConstraints() {
        [imgView, mainLbl, myButton].forEach {
            view.addSubview($0)
        }
        imgView.snp.makeConstraints {
            $0.top.leading.trailing.bottom.equalToSuperview()
        }
        
        
        mainLbl.snp.makeConstraints {
            $0.center.equalToSuperview()
            $0.width.equalTo(120)
            $0.height.equalTo(40)
        }
        
        myButton.snp.makeConstraints {
            $0.top.equalTo(mainLbl.snp.bottom).offset(120)
            $0.centerX.equalToSuperview()
        }
    }
}

 


 

지난 번에 addTarget()이라는 함수의 바디를 채우지 않고 끝났는데요.

addTarget()에서는 myButton의 이벤트를 addTarget을 해줄거기 때문이져.

'Get temperature' 버튼을 누르면 Alamofire가 API를 쌱 받아와서 myLabel에 나오게 해야겠져?

일단 버튼을 누르면 발생할 이벤트를 extension을 통해 선언해보겠슴돠.

 

 

extension ViewController{
    @objc func getTempTapped(_ sender: UIButton) {
        // API 받아오기
    }
}

 

그리고 오픈 웨더 사이트에서 받아오는 정보는 JSON 데이터였던 게 기억나시나요?

JSON데이터를 다시 우리가 원하는 형식으로 바꿔주는 절차가 필요해요.

(JSON -> 구조체로 바꾸고 싶은 분들은 여기롱)

 

command + N을 눌러서 swift 파일을 하나 추가해주시구요.

이름은 WeatherManager.swift로 하겠슴더

 

 

이번에는 온도만 받아오기 때문에 엄청 간단하져?! 🥳

(📍Decodable 채택하는 거 잊지말기)

import Foundation

struct WeatherManager: Decodable {
    let main: Main
    
    struct Main: Decodable {
        let temp: Double
}

자 이제 버튼의 이벤트를 담는 함수를 만들어줄게요.

우선 request url을 만들어줍니다.

 

extension ViewController{
    @objc func getTempTapped(_ sender: UIButton) {
        let request = AF.request("https://api.openweathermap.org/data/2.5/weather?appid=cc67530774268e4f6e4250794df2dca2&units=metric&q=seoul")
}

 

request 메서드는 아래와 같이 엄청나게 많은 파라미터를 받는데여! 그래서 저도 처음에는 저걸 다 어떻게 채워야 하나 했는데, 다행히도(?) 기본값이 다 설정되어 있더라구요.

 

 

validate 메서드는 200-299의 응답 상태 코드가 있는지, 내용 유형이 HTTP 헤더 허용 필드에 지정된 것과 일치하는지 확인한다고 하네요. 

 

    @objc func getTempTapped(_ sender: UIButton) {
        let request = AF.request("https://api.openweathermap.org/data/2.5/weather?appid=cc67530774268e4f6e4250794df2dca2&units=metric&q=seoul")
        request.validate().responseDecodable(of: WeatherManager.self) { response in
        	// 응답(response)을 받아서 할 일
        }
    }

 

그리고 responseDecodable 메서드를 쓸게요!

이 메서드 또한 많은 파라미터를 받는데, 기본값이 다 있는 제법 착한 놈이에요. 그리고 저는 completionHandler를 사용해줄겁니다.

 

of type 파라미터는 응답 데이터로부터 decode할 decodable 타입을 넣으래요.

저희는 아~까 WeatherManager.swift 파일을 만들어놨죠? 

그걸 써주고, completionHandler를 통해 받아온 응답을 처리해줄게요.

 

 

그 다음이 좀 어려울 수 있는데요. 

우리가 받아온 응답(response)을 option을 눌러서 보면?!

 

타입이 신기하쥬?

Result Type이라는 놈이래요.

저도 Result Type을 배우긴 했는데, 써보는 건 처음이에요 허허

 

Result Type 같은 경우에는 아래와 같이 정의가 되어 있대요.

(다음에는 이 Result Type을 공부해서 써볼 수 있도록 해볼게여...! 나와의 약속...! 믿음...!)

enum Result<Success, Failure> where Failure: Error {
//Failure는 반드시 Error를 채택해야 함
//success, failure는 연관값을 담을 수 있음->제네릭으로 선언되어 있기 때문에 어떤 타입이든 담을 수 있음
	case sucess
	case failure
}

 

그래서 Result Type을 쓸 때 Switch 문으로 이용하기 편하겠쥬?

여기서도 그렇게 이용해볼 예정이에요. switch문으로 나눠서.

 

response.result를 switch문으로 나눠보면, 아래처럼 failure와 success가 자동완성돼요.

위에 Result Type의 정의를 보면 그렇게 정의했기 때문이에요.

 

성공하면 그 결과를 weather로, 실패하면 error로 받아서 case문 안에서 사용할 거에요.

switch response.result {
    case .success(let weather):
        // 성공 시
    case .failure(let error):
        // 실패시
}

 

만약 응답이 성공할 경우, 저는 레이블에 temp값을 넣을 예정이었죠?

그리고 실패할 경우는 어떤 error인지 print해볼게요.

    @objc func getTempTapped(_ sender: UIButton) {
        let request = AF.request("https://api.openweathermap.org/data/2.5/weather?appid=cc67530774268e4f6e4250794df2dca2&units=metric&q=seoul")
        request.validate().responseDecodable(of: WeatherManager.self) { response in
            switch response.result {
            case .success(let weather):
                self.mainLbl.text = String(weather.main.temp)
            case .failure(let error):
                print(error)
            }
        }
    }

 

뭔가 이상한 게 느껴지시나요?

responseDecodable 메서드의 경우, decode를 할 필요도 없고 메인큐로 옮겨주는 작업 또한 필요가 없어여!!

그래서 코드가 진짜 진짜 간단해지져!!

 

지난 번에 거의 URLSession이 거의 50줄이었던 반면, 10줄만에 끝났어여,,ㅎㅎ,, (이건 못 참지,,,ㅎ)

 


그리고 마지막으로 우리가 만든 이벤트를 버튼에 부여해줄게요! 

이렇게 하면 진짜 끝!

final private func addTarget() {
    myButton.addTarget(self, action: #selector(getTempTapped(_:)), for: .touchUpInside)
}

 


tmi타임 🥳

사실 이 예제는 선생님이 SnapKit, Alamofire을 사용해서 한 번 만들어 보라고 내준 숙제였는데요!

여러 블로그를 보고 responseJSON를 사용해봤더니 안 되더라구요 ㅠㅠ (나중에 알고보니 업데이트 됐다나봐요 ㅠㅠ)

우여곡절 끝에 responseDecodable을 쓰니, decode와 비동기작업까지 전부 해결되어 넘나 간단하네여!

 

오늘도 틀린 게 있으면 언제든! 알려주세여!! 많이 배우겠습니다 슨배님!!