포스트

Clima (2)

API 사용.

  • 개발자들에게 일반 작업을 수행할 수있는 표준 명령을 제공해준다.
  • 일종의 단순 계약으로 볼 수 있다. (개발자와 API공급자 간의 계약)

  • 날씨 정보를 사용할 API : Weather API
  • 링크

https://openweathermap.org/api

  • API를 다루려면 Key가 필요하다.
  • 가입하고 키를 복사해두자. (여긴 가입하고 시간이 지나야 key가 Activate가 되는듯 하다. 기다리고 해보자.)

  • 도시이름을 이용한 날씨 API 사용
  • https://api.openweathermap.org/data/2.5/weather?id={city id}&appid={API key} 이런식의 형태를 가진다.

이런식으로 결과 값이 나오는데, json viewer를 chrome extensions에 설치 해두자.

json viewer 를 통해 날씨 값을 가져왔다.

키 활성화 하는데 한 20분 정도 걸린것같다.

API를 사용하는 스위프트 파일 생성.

  • swift 파일을 하나 생성하자 (Model → WeatherManager.swift)

그리고 구조체를 만들어 준다.

1
2
3
4
5
6
7
8
9
10
import Foundation

struct WeatherManager {
    let weatherURL = "http://api.openweathermap.org/data/2.5/weather?appid=b5005bab606b11d2b82b3dae1b2bc221&units=metric"
    
    func fetchWeather (cityName : String) {
        let urlString = "\(weatherURL)&q=\(cityName)"
        print(urlString)
    }
}

weatherURL에 실제로 테스트를 해본 URL을 적고 도시만 빼고 적는다.

뷰 컨트롤러로 돌아와 도시입력을 끝마치고 url을 리턴을 하기 위해 textFieldDidEndEditing 함수를 고쳐준다.

1
2
3
4
5
6
7
8
9
func textFieldDidEndEditing(_ textField: UITextField) {
        
        if let city = searchTextField.text {
            weatherManager.fetchWeather(cityName: city)
        }
        
        //Use searchTextField.text to get the weather for that city
        searchTextField.text = ""
    }

if let을 사용한것은, 옵셔널 타입을 방지하기 위해서이다.

실행을 해보자!

주소가 리턴이 되는걸 알 수 있다.

실제로 네트워킹을 통해 값을 가져오기.

  • 네트워킹
    • 우리가 만든 앱은 API를 통해 웹 서버로 데이터를 요청한다.
      • 이때 요청하면서 일부 쿼리를 넘긴다
      • 여기서 쿼리에는 도시이름 같은 원하는것에 대한 쿼리이다.
    • 웹서버는 우리 앱에 반응해 요청한 데이터를 전송한다.
  • 네트워킹의 4단계
    1. URL 생성
      • URL 구조체를 통해 변수 생성
    2. URL세션 생성
      • let session = URLSession(configuration: .default)
    3. 세션에 작업을 준다.
      • let task = session.dataTask(with: url, completionHandler: <#T##(Data?, URLResponse?, Error?) -> Void#>)
      • with : url주소
      • completionHandler : Parameter처럼 보이지만 함수이다!
        • 작업이 끝나고 하는 행동이다.
        • 여기선 새로운 함수를 만들어 주었다.
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        
          func handle(data: Data?, response: URLResponse?, error: Error?) {
          // -> Void 해도되고, return Void도 가능, 아예 언급을 안해도 된다.
          if error != nil { // 에러가 발생하면 출력
              print(error!)
              return // 여기선 return을 사용하면 함수를 빠져나가 아무것도 하지말라는 것이다
          }
                    
          if let safeData = data {
              let dataString = String(data: safeData, encoding: .utf8)
              // Data타입은 그냥 프린트 할수 없으므로 위의 작업을 고쳐 프린트 해줘야한다.
              print(dataString)
              }
          }
        
    4. 실제로 작업시작.
      • task.resume()
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
40
41
42
43
44
45
import Foundation

struct WeatherManager {
    let weatherURL = "http://api.openweathermap.org/data/2.5/weather?appid=b5005bab606b11d2b82b3dae1b2bc221&units=metric"
    
    func fetchWeather (cityName : String) {
        let urlString = "\(weatherURL)&q=\(cityName)"
        performRequest(urlString: urlString)
    }
    
    func performRequest(urlString : String){
        // 1. Create a URL
        
        if let url = URL(string: urlString) {
            
            // 2. Create a URLSession
            
            let session = URLSession(configuration: .default)
            
            // 3. Give the session a task
            
            let task = session.dataTask(with: url, completionHandler: handle(data:response:error:))
            
            // 4. Start the task
            
            task.resume()
        }
    }
    
    func handle(data: Data?, response: URLResponse?, error: Error?) {
        // -> Void 해도되고, return Void도 가능, 아예 언급을 안해도 된다.
        if error != nil { // 에러가 발생하면 출력
            print(error!)
            return // 여기선 return을 사용하면 함수를 빠져나가 아무것도 하지말라는 것이다
        }
        
        if let safeData = data {
            let dataString = String(data: safeData, encoding: .utf8)
            // Data타입은 그냥 프린트 할수 없으므로 위의 작업을 고쳐 프린트 해줘야한다.
            print(dataString)
        }
    }
    
}

일단 에러코드가 어떻게 나오는지 실행해보자! 도시를 입력하고 검색하니

바로 에러가 발생한다!

에러메세지는 다음과 같다.

뭐 간단하게 하면 안전한 경로로 하지않았다는 것이다.

http://~ 였던 주소를 https://~ 로 바꿔주자!

그리고 실행을 했더니!

아까 웹으로 봤을때의 그 값이 출력이 된다!

즉 서로 통신이 되었다는것이다!!!

다시 dataTask를 고쳐주자. with는 url그대로 적어주고

다시 만들면 이렇게 회색이되는데 클릭을 하면 파란색으로 바뀌는데 이떄 엔터를 치면 트레일링 클로저로 바뀐다!

여기서 엔터를 치면?

이렇게 바뀐다!!

그리고 handle 함수 만들었던것을 지우고 dataTask에 넣어주자.

1
2
3
4
5
6
7
8
9
10
11
12
let task = session.dataTask(with: url) { (data, response, error) in
    if error != nil { // 에러가 발생하면 출력
        print(error!) 
        return // 여기선 return을 사용하면 함수를 빠져나가 아무것도 하지말라는 것이다
    }
                
    if let safeData = data {
        let dataString = String(data: safeData, encoding: .utf8)
        // Data타입은 그냥 프린트 할수 없으므로 위의 작업을 고쳐 프린트 해줘야한다.
        print(dataString)
        }
    }

safeData 테스트를 통해 값을 전달 받는걸 알았다.

현재 이값은 JSON 형식으로 받고있다.

JSON?

JavaScript Object Notation의 약자이다. 속성-값 쌍(attribute–value pairs), 배열 자료형(array data types) 또는 기타 모든 시리얼화 가능한 값(serializable value) 또는 키-값 쌍으로 이루어진 데이터 오브젝트를 전달하기 위해 인간이 읽을 수 있는 텍스트를 사용하는 개방형 표준 포맷이다. 비동기 브라우저/서버 통신 (AJAX)을 위해, 넓게는 XML(AJAX가 사용)을 대체하는 주요 데이터 포맷이다. 특히 인터넷에서 자료를 주고 받을 때 그 자료를 표현하는 방법으로 알려져 있다. 자료의 종류에 큰 제한은 없으며, 특히 컴퓨터 프로그램의 변수값을 표현하는 데 적합하다 JSON을 Parse할 함수를 만들어 주었다. (출처: 위키백과)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func performRequest(urlString : String){
        if let url = URL(string: urlString) {
            let session = URLSession(configuration: .default)
            let task = session.dataTask(with: url) { (data, response, error) in
                if error != nil { // 에러가 발생하면 출력
                    print(error!)
                    return // 여기선 return을 사용하면 함수를 빠져나가 아무것도 하지말라는 것이다
                }
                
                if let safeData = data {
                    self.parseJSON(weatherData: safeData)
                }
            }

            task.resume()
        }
    }
    
    func parseJSON(weatherData: Data) {
        
    }

새로운 파일을 하나 생성해준다

Json 내용을 담을 WeatherData이다.

우리가 아까 얻었던 그 여러 결과값을 담을 변수를 구조체 안에 만들어주자

그전에 우리가 가져온 데이터를 디코더 할 프로토콜을 적어주자.

1
2
3
4
5
6
7
import Foundation

struct WeatherData : Decodable{
    //               ----------
    let name : String
    
}

그리고 다시 WeatherManager로 돌아가 parseJSON 함수를 수정해준다.

1
2
3
4
func parseJSON(weatherData: Data) {
        let decoder = JSONDecoder()
        decoder.decode(WeatherData.self, from: weatherData)
    }

weatherData.self인 이유는 날씨 데이터 형식을 지정하려면, 그 개체에.self를 하면 전달하는 데이터 형식으로 바뀐다.

하지만 여전히 에러가 발생한다.

디코딩할 호출은 던질수 있으나, try로 되어있지 않아 오류를 처리할 수 없다고 한다.

decode 설명을 자세히 보면 () 뒤에 throws -> T where T : Decodable 이렇게 되어있다.

지금 우리가 적은것엔 그내용이 없어서 에러가 뜬것이다.

1
2
3
4
5
do {
    try decoder.decode(WeatherData.self, from: weatherData)
    } catch {
    print(error)
    }

do는 일반적인 상황이고 catch가 에러가 났을때 이다.

에러가나면 에러를 출력하게 했다.

저걸보니 자바가 생각이 났다.

디코드 출력.

WeatherData 오브젝트 생성 let decodedData = try decoder.decode(WeatherData.self, from: weatherData)

decodedData가 우리가 만든 weatherData Type으로 되었다.

다시 JSON 양식으로 돌아가서…

temp의 path를 복사해보자

decodedData.namedecodedData.main.temp 이렇게 바꾸자.

당연히 없는 양식이니 에러가 발생할것이다.

WeatherData로 가서 변수를 만들어주자!

Main은 이하 6개의 변수를 가지고 있다.

그러므로 그냥 변수를 만들게 아니라, Main이라는 이름의 구조체를 만들어 주어야한다!

그리고 temp의 데이터타입은 우리가 임의대로 해서는 절대 안된다.

위의 사진대로 소수점을 표시하고 있기에 소수점을 나타내는 데이터 타입을 해줘야한다!

1
2
3
4
5
6
7
8
struct WeatherData : Decodable{
    let name : String
    let main : Main
}

struct Main :Decodable {
    let temp : Double
}

weather description이 나오게 해보기.

우선 path를 복사해보자.

weather[0].description 다음과 같다.

즉 우린 배열이 필요하다는걸 알게되었다.

1
2
3
4
5
6
struct WeatherData : Decodable{
    let name : String
    let main : Main
    let weather : [Weather]
}

이렇게 weather 변수를 만들고 Weather를 담을 배열을 만들었다.

그리고 구조체도 만들어주자

1
2
3
struct Weather : Decodable {
    let description : String
}

그나저나 강의대로 하고있는데 출력이 안된다…

한번 고쳐봐야겠다.

어디서 부터 문제일까 해서 혹시나 하는 마음에 url주소부터 보았다.

왜냐 프린트가 안된다는것은 API를 통한 네트워킹이 안된다고 생각을 하였기 때문이다.

아니나 다를까…

공백이 있다!?!

1
"https ://api.openweathermap.org/data/2.5/weather?appid=b5005bab606b11d2b82b3dae1b2bc221&units=metric"

아마 http: https:의 차이를 적으면서 그렇게 된것같다…

조심하자 ㅠㅠ

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.