SSAC iOS 앱 개발자 데뷔과정 - 19

Sangwon Shin·2021년 10월 27일
0

SSAC

목록 보기
16/19

📡 네트워크 통신

지난시간에 HTTP 통신에 대한 개념을 공부했습니다.
오늘은 간단한 예제를 통해서 네트워크 통신을 통해 얻은 파일을 View 를 구성해보겠습니다.


🌍 OpenWeather

OpenWeather API 를 이용해서 간단하게 날씨를 표현하는 앱을 만들면서 지난 시간에 공부했던 HTTP 네트워크 통신에 대해 알아보겠습니다.

우선, Swift 에서 HTTP 통신을 위해서 Alamofire 라는 라이브러리를 사용합니다.
❗️애플에서 제공하는 URLSession 을 통해서도 가능합니다

그리고 링크를 통해서 우리가 사용하는 의도에 맞는 API Call 을 확인합니다.

CLLocation 을 이용했던 현재 위치를 받아오면, 사용자 위치의 경도와 위도를 알 수 있기 때문에 위 그림의 API Call 을 사용 해보겠습니다.

우리가 서버에게 위와 같이 데이터를 요청하게 되면 json 또는 xml 형태로 받게 됩니다. 문자열로 보내주기 때문에 Serialization 필요!

서버를 통해 받은 json 은 {key : value} 형태로 구성되어 있기 때문에 바로 사용하기 전에 API Test 도구 를 통해서 어떤 형태로 값이 구성되어 있는지 확인 해보겠습니다. (Insomnia 를 이용했습니다.)

ex ) 온도: json["main"]["temp"] 이런식으로 접근할 수 있겠다. 라는 갈피를 잡을 수 있습니다.

그럼 Swift 에서 어떻게 Alamofire , SwiftJSON 라이브러리를 통해서 위와 같이 서버로 부터 데이터를 가져올 수 있을까요?

라이브러리 문서를 확인보겠습니다.

Alamofire.request(url, method: .get).validate().responseJSON { response in
    switch response.result {
    case .success(let value):
        let json = JSON(value)
        print("JSON: \(json)")
    case .failure(let error):
        print(error)
    }
}

api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API key}

우리는 위와 같은 url 을 만들어서 API Call 을 해줘야 하는 상황입니다.

import Foundation

struct APIKey {

    static let OPENWEATHER = "API key"
}

struct EndPoint {
    
    static let weatherURL = "https://api.openweathermap.org/data/2.5/weather?"
    
}

프로젝트를 깃허브에 올릴 때, API key 를 보호(?) 하기 위해서 위와 같이 구조체에 url 을 구성할 구성요소들을 만들고 따로 파일로 만들었습니다.


그럼, 우리는 사용자의 위치정보를 받아왔을 때의 위도와 경도를 받아와 API Call 을 해주고 뷰객체에 서버로 부터 받은 데이터들을 표현해주면 됩니다.

import CoreLocation
import UIKit
import Foundation
import Alamofire
import SwiftyJSON

class WeatherManager {
    
    static let shared = WeatherManager()
    typealias CompletionHandler = (Int, JSON) -> ()
    
    func fetchWeatherData(lon: CLLocationDegrees, lat: CLLocationDegrees, result: @escaping CompletionHandler) {
        
        let url = EndPoint.weatherURL + "lat=\(lat)&lon=\(lon)&appid=\(APIKey.OPENWEATHER)"

        AF.request(url, method: .get).validate(statusCode: 200...500).responseJSON { response in
            switch response.result {
                
            case .success(let value):
                let json = JSON(value)
                let code = response.response?.statusCode ?? 500
                result(code, json)
                
            case .failure(let error):
                print(error)
            }
        }
    }
}

이를 싱글턴 패턴을 이용해서 만들어 보겠습니다.

서버와 통신하는 함수 fetchWeatherData 는 사용자의 위치 정보와 탈출 클로저를 매개변수로 가집니다.

탈출 클로저를 매개변수로 가지는 이유는, 서버와 통신을 성공한 경우에 상태코드와 json 파일을 사용하기 위함입니다.
(현재 함수내부에서는 뷰를 구성하는 뷰객체에 접근할 수 없기 때문에)

if let coordinate = locations.last?.coordinate {
            showTitle(coordi: coordinate)
            locationManager.stopUpdatingLocation()
            
            let lon = coordinate.longitude
            let lat = coordinate.latitude
            
 WeatherManager.shared.fetchWeatherData(lon: lon, lat: lat) { code, json in
                switch code {
                    case 200:
                        print(json)
                        let currentTemp = json["main"]["temp"].doubleValue - 273.15
                        self.tempLabel.text = " \(Int(currentTemp))도에요 "
                       
                        self.humidityLabel.text = "  \(json["main"]["humidity"].intValue)% 만큼 습해요 "
                        let speed = json["wind"]["speed"].doubleValue
                        self.windLabel.text = "  \(Int(speed))m/s의 바람이 불어요 "

                        let icon = json["weather"][0]["icon"].stringValue
                        let url = URL(string: "http://openweathermap.org/img/wn/\(icon)@2x.png")
                        self.weatherImageView.kf.setImage(with: url)

                    case 400:
                        print(json)

                    default:
                        print("error")
                    }
                }
            }

실제 함수를 사용하는 코드를 보면 이해 할 수 있습니다!


📸 KAKAO Vision API

위의 예제는 get Http Method 를 사용했습니다. 이번에는 post 를 사용하는 예제를 한번 살펴보겠습니다.

post 는 우리가 데이터를 Http Body 에 포함시켜서 서버에 전달하는 과정이 필요합니다.

(Insomnia 를 통해 호출하면 위와 같습니다)

Http 통신은 Alamofire 라이브러리를 이용하고 있기 때문에 해당 라이브러리 문서를 확인 해보겠습니다.

위와 같은 형태를 이용해서 서버에 파일을 업로드 합니다.
API Call 까지 함께하면 아래와 같습니다.

import UIKit
import Foundation
import Alamofire
import SwiftyJSON

//싱글톤 패턴
class OcrAPIManager {
    
    static let shared = OcrAPIManager()
    
    typealias CompletionHandler = (Int, JSON) -> ()
    
    func fetchOcrData(image: UIImage, result: @escaping (Int, JSON) -> () ) {
        
        let header: HTTPHeaders = [
            "Authorization": APIkey.KAKAO,
            "Content-Type": "multipart/form-data"
        ]
        
        //UIImage 를 바이너리 타입으로 변환
        guard let imageData = image.pngData() else { return }
        
        AF.upload(multipartFormData: { multipartFormData in
            multipartFormData.append(imageData, withName: "image", fileName: "image")
        }, to: Endpoint.ocrURL, headers: header)//어디로 보낼것인가?
            .validate(statusCode: 200...500).responseJSON { response in
            
            switch response.result {
            
            case .success(let value):
                let json = JSON(value)
                print("JSON: \(json)")
                
                let code = response.response?.statusCode ?? 500
                result(code, json)
                
            //네트워크 통신 자체가 불가능한 경우
            case .failure(let error):
                print(error)
            }
        }
    }
}

Http header 를 업로드할 파일의 타입과 API key 로 구성하고, 헤더와 업로드할 파일을 url 에 보내주는 형식입니다.
전체적은 틀은 동일합니다!

❗️우리가 서버에 데이터를 보내줄 때도 우리가 서버에서 데이터를 받을 때 문자열 데이터를 받는것과 마찬가지로 바이너리 타입으로 변경해서 보내줘야 합니다


🏷 P.S.

어제, 카카오톡 API 를 통해서 얼굴인식과 OCR 예제를 수행하면서 기분이 이상했습니다.
작년까지만 해도 연구실에서 Computer Vision 을 공부하면서 직접 딥러닝 네트워크 구조를 만들고 학습시키고 있었는데 👻

기회가 된다면 졸업작품을 위해 만들었던 그림자 제거를 위해 학습 시켰던 네트워크를 이용해서 저런 간단한 앱을 만들어 보고싶습니다 🔥

오늘 과제를 하면서 복습의 중요성을 다시 깨달았습니다.

전체적인 복습겸, 초기 구성했던 TrendMedia 가 현재 과제와 너무 멀어져서 새로운 프로젝트에서 다시 만들었습니다.

생각보다 지금까지 했던 내용임에도 불구하고 헷갈려서 블로그에 정리했던 글들을 보면서 겨우 완성했습니다..😿

그리고 웹뷰를 통해서 영화의 예고편을 보여줄 때, 데이터 통신 결과 key 값이 나오기 전에 웹이 열려서 유튜브 메인 화면만 보이는 문제가 있었는데 이를 어떻게 해결할지 고민하는데 시간을 많이 썼던것 같습니다.

override func viewDidLoad() {
    super.viewDidLoad()
    fetchMovieTrailerData() //이 함수가 종료되기 전에 onWebPage 			      가 먼저 실행되는 문제점
    //onWebPage()
    }

처음에는 서버와의 통신에 문제가 있는지 알았으나 print 를 통해서 값을 찍어본 결과 fetchMoviTrailerData() 함수가 종료되기 전에 onWebPage() 함수가 실행돼서 key 값을 서버로 부터 받지 못해 유뷰트 메인 화면이 뜨는 문제였습니다.

어떻게 하면 fetchMovieTrailerData() 가 종료될 때 까지 딜레이를 줄 수 있을까?

첫번째 방법은 이중 for 문을 돌면서 두 함수 사이에 딜레이를 강제로 주는 방법입니다. 실패했습니다.

두번째 방법은 didSet 을 이용하는 것입니다!

import UIKit
import WebKit

class WebViewController: UIViewController {
    
    var movieKey: String = ""
    var movieURL: String = "" {
        didSet {
            onWebPage()
        }
    }
    
    @IBOutlet weak var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        fetchMovieTrailerData()
    }
    
    func fetchMovieTrailerData() {
        TmdbGetMovieManager.shared.fetchMovieTrailerData(movieID: movieKey) { code, json in
            print("1-------------")
            self.movieURL = json["results"][0]["key"].stringValue
            print(self.movieURL)
        }
    }
    
    func onWebPage() {
        let urlRaw = "https://www.youtube.com/watch?v=" + movieURL
        print("2---------------")
        print(urlRaw)
        guard let url = URL(string: urlRaw) else {
            print("Invalid URL")
            return
        }
        let request = URLRequest(url: url)
        webView.load(request)
    }
}

didSet 을 이용하면 fetchMovieTrailerData() 를 통해 movieURL 값이 변경될 때, onWebPage() 를 호출해 우리가 원하는 결과를 얻을 수 있습니다!

주말에 복습하면서 search, map 기능도 새로운 프로젝트에 추가할 예정입니다.

빨리 밀린 블로그 정리도 마무리 하도록 하겠습니다!

profile
개발자가 되고싶어요

0개의 댓글