[Swift] 네트워킹

임승섭·2023년 7월 3일
0

Swift

목록 보기
22/35

HTTP 프로토콜

  • HTML : HyperText Markup Languager
  • HTTP : HyperText Transfer Protocol
  • URL : Uniform Resource Locator
  • HyperText 전송에서 시작
    -> 이미지, 영상, 파일, JSON, ... 모든 형태의 데이터 전송
  • 계층별 프로토콜
    • 애플리케이션 : HTTP (데이터를 어떻게 주고 받을지에 대한 약속. request/reply)
    • 트랜스포트 : TCP (주고받을 상태 확인 및 검중. port(어떤 앱과 통신하는지)
    • 인터넷 : IP (주고 받는 주소, 경로)
    • 링크 : 네트워크
  • HTTP 요청 메세지
    • 시작 라인 (메소드 + 요청대상 + HTTP 버전)
    • 헤더 필드 (각종 부가 정보)
    • 공백라인
    • 메세지 본문 (실제 전송할 데이터. JSON, 이미지, ...)
  • HTTP 응답 메세지
    • 시작 라인 (HTTP 버전 + 상태코드 + 문구)
    • 헤더 필드
    • 공백라인
    • 메세지 본문 (실제 전송할 데이터. JSON, 이미지, ...)
  • 요청 메소드 종류
    • GET : 조회 (게시글 읽어오기)
    • POST : 등록 (게시글 생성, 댓글 달기)
    • PUT : 데이터 대체/생성 (게시글 수정)
    • PATCH : (게시글 좋아요)
    • DELETE : 삭제 (게시글 삭제)
  • 상태 코드 종류
    • 2XX (Success)
    • 3XX (Redirection)
    • 4XX (Client Error)
    • 5XX (Serveer Error)
  • 데이터 전송 분류
    • GET 메소드 : 쿼리 파라미터 통해서 데이터 전송
      (ex. 검색어 / 정렬 기준)
    • POST / PUT / PATCH 메소드 : 메세지 바디 통해서 데이터 전송
      (ex. 회원가입 / 게시글 작성 / 게시글 수정 )
  • 응답(Response) 데이터의 형태 : JSON

Rest API

  • API 주소 만들 때 알아볼 수 있도록 만든다

iOS에서의 네트워킹

  • "서버와 어떻게 통신하는지"
    • 요청(Request) - query
    • 응답(Response) - data
  • 데이터 요청 4단계
      1. URL 구조체 (URL 만들고)
      1. URL Session (객체 만들고)
      1. dataTask
      1. 시작 (resume)
  • 응답 - JSON
    -> Class/Struct 형태로 변환
    • JSONDecoder()라는 객체
    • decode(형태, from: 데이터) 메서드
// 1. URL 주소 - 문자열
let movieURL =  "http://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?&key=본인키주소&targetDt=20210201"

// 1. URL 구조체로 만들기
let url = URL(string: movieURL)!	// 옵셔널 타입이기 때문에 강제 언래핑

// 2. URLSession 만들기 (네트워킹 하는 객체)
let session = URLSession.shared

// 3. 세션에 작업 부여 (일시정지 상태)
let task = session.dataTask(with: url) { (data, response, error) in		
	// ↑ : trailing closure 형태 - 파라미터는 내 맘대로
    // url을 가지고 서버와 통신한 후에, 그 결과를 data, response, error에 넣어준다!!
    
    // 일단 에러가 있는지부터 체크한다 (관습적으로)
	if error != nil {	// 에러가 발생했다면, 출력 한번 해주고 종료
    	print(error!)
        return
    }
    
    // data 확인
    if let safeData = data {
    	print(String(decoding: safeData, as: UTF8.self))
    }
    
    // 아래에서 정의한 함수 parseJSON1 -> 배열을 반환한다
    var movieArray = parseJSON1(safeData)
    
    print(movieArray!)
    dump(movieArray!)	// print보다 더 정리된 형태로 출력
}

// 4. 작업 시작 
task.resume()	// 아니면 위에서 변수에 담지 않고, 그냥 바로 .resume() 실행시켜도 된다

JSON Parsing

  • 데이터를 분석한다
// 현재의 형태
func parseJSON1(_ movieData: Data) -> [DailyBoxOfficeList]? {	// data 형태를 받아서 배열로 리턴
    
    do {
        // 스위프트5
        // 자동으로 원하는 클래스/구조체 형태로 분석
        // JSONDecoder
        let decoder = JSONDecoder()
        
        // 변형하고 싶은 형태(MovieData)로 변형 -> .self 붙여줘야 한다
        let decodedData = try decoder.decode(MovieData.self, from: movieData)

        return decodedData.boxOfficeResult.dailyBoxOfficeList
        
    } catch {	// decode는 에러를 발생시킬 수 있는 함수! (자세히 보면 throw 키워드 달려있다)
        
        return nil
    }
    
}




// 예전의 형태
func parseJSON2(_ movieData: Data) -> [DailyBoxOfficeList]? {
    
    do {
        
        var movieLists = [DailyBoxOfficeList]()
        
        // 스위프트4 버전까지
        // 딕셔너리 형태로 분석
        // JSONSerialization
        if let json = try JSONSerialization.jsonObject(with: movieData) as? [String: Any] {
            if let boxOfficeResult = json["boxOfficeResult"] as? [String: Any] {
                if let dailyBoxOfficeList = boxOfficeResult["dailyBoxOfficeList"] as? [[String: Any]] {
                    
                    for item in dailyBoxOfficeList {
                        let rank = item["rank"] as! String
                        let movieNm = item["movieNm"] as! String
                        let audiCnt = item["audiCnt"] as! String
                        let audiAcc = item["audiAcc"] as! String
                        let openDt = item["openDt"] as! String
                        
                        // 하나씩 인스턴스 만들어서 배열에 append
                        let movie = DailyBoxOfficeList(rank: rank, movieNm: movieNm, audiCnt: audiCnt, audiAcc: audiAcc, openDt: openDt)

                        
                        movieLists.append(movie)
                    }

                    return movieLists

                }
            }
        }

        return nil
        
    } catch {
        
        return nil
    }
    
}


// 서버에서 주는 데이터의 형태 ====================================================

struct MovieData: Codable {		// Codable : Decodable + Incodable
    let boxOfficeResult: BoxOfficeResult
}

// MARK: - BoxOfficeResult
struct BoxOfficeResult: Codable {
    let dailyBoxOfficeList: [DailyBoxOfficeList]
}

// MARK: - DailyBoxOfficeList
struct DailyBoxOfficeList: Codable {
    let rank: String
    let movieNm: String
    let audiCnt: String
    let audiAcc: String
    let openDt: String
}

네트워크 통신의 예시

  • 요청(Request) -> 서버데이터(JSON) -> 분석(Parse) -> 변환(Struct/Class)
// 서버에서 주는 데이터 (맘대로 변경할 수 없음) ==================================
struct MovieData: Codable {
    let boxOfficeResult: BoxOfficeResult
}

// MARK: - BoxOfficeResult
struct BoxOfficeResult: Codable {
    let dailyBoxOfficeList: [DailyBoxOfficeList]
}

// MARK: - DailyBoxOfficeList
struct DailyBoxOfficeList: Codable {
    let rank: String
    let movieNm: String
    let audiCnt: String
    let audiAcc: String
    let openDt: String
}

// 내가 만들고 싶은 데이터 (우리가 쓰려는 Struct / Class) =======================
struct Movie {
    static var movieId: Int = 0   // (타입 속성) 아이디가 하나씩 부여되도록 만듦
    let movieName: String
    let rank: Int				// 기존에 string이었던 것 int로 수정
    let openDate: String
    let todayAudience: Int
    let totalAudience: Int
    
    init(movieNm: String, rank: String, openDate: String, audiCnt: String, accAudi: String) {
        self.movieName = movieNm
        self.rank = Int(rank)!		// 문자열 -> 정수로 바꿀 때 옵셔널로 나옴
        self.openDate = openDate
        self.todayAudience = Int(audiCnt)!
        self.totalAudience = Int(accAudi)!
        Movie.movieId += 1			// 개체 찍어낼 때마다  + 1
    }
    
}



// 서버와 통신 ===========================================================
struct MovieDataManager {
    
    let movieURL = "http://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?"
    
    let myKey = ""
    
    func fetchMovie(date: String, completion: @escaping ([Movie]?) -> Void) {
        let urlString = "\(movieURL)&key=\(myKey)&targetDt=\(date)"
        performRequest(with: urlString) { movies in
            completion(movies)
        }
    }
    
    func performRequest(with urlString: String, completion: @escaping ([Movie]?) -> Void) {
        print(#function)
        
        // 1. URL 구조체 만들기
        guard let url = URL(string: urlString) else { return }
        
        // 2. URLSession 만들기 (네트워킹을 하는 객체 - 브라우저 같은 역할)
        let session = URLSession(configuration: .default)
        
        // 3. 세션에 작업 부여
        let task = session.dataTask(with: url) { (data, response, error) in
            if error != nil {
                print(error!)
                completion(nil)
                return
            }
            
            guard let safeData = data else {
                completion(nil)
                return
            }
            
            
            // 데이터 분석하기
            if let movies = self.parseJSON(safeData) {
                //print("parse")
                completion(movies)
            } else {
                completion(nil)
            }
        }
        
        // 4.Start the task
        task.resume()   // 일시정지된 상태로 작업이 시작하기 때문
    }
    
    // JSON 형태로 데이터 분석
    func parseJSON(_ movieData: Data) -> [Movie]? {
        // 함수실행 확인 코드
        print(#function)	// 함수의 이름 출력
        
        let decoder = JSONDecoder()
        
        do {
            let decodedData = try decoder.decode(MovieData.self, from: movieData)
            
            let dailyLists = decodedData.boxOfficeResult.dailyBoxOfficeList
            
            // 반복문으로 movie배열 생성 ⭐️
//            var myMovielists = [Movie]()
//
//            for movie in dailyLists {
//
//                let name = movie.movieNm
//                let rank = movie.rank
//                let openDate = movie.openDt
//                let todayAudi = movie.audiCnt
//                let accAudi = movie.audiAcc
//
//                let myMovie = Movie(movieNm: name, rank: rank, openDate: openDate, audiCnt: todayAudi, accAudi: accAudi)
//
//                myMovielists.append(myMovie)
//            }
            
            // 고차함수를 이용해 movie배열 생성하는 경우 ⭐️
            let myMovielists = dailyLists.map {
                Movie(movieNm: $0.movieNm, rank: $0.rank, openDate: $0.openDt, audiCnt: $0.audiCnt, accAudi: $0.audiAcc)
            }
            
            return myMovielists
            
        } catch {
            //print(error.localizedDescription)
            
            // (파싱 실패 에러)
            print("파싱 실패")
            
            return nil
        }
        
    }
    
}


// 뷰컨트롤러에서 일어나는 일 ===========================================================
// 빈배열
var downloadedMovies = [Movie]()

// 데이터를 다운로드 및 분석/변환하는 구조체
let movieManager = MovieDataManager()


// 실제 다운로드 코드
movieManager.fetchMovie(date: "20210201") { (movies) in
    
    if let movies = movies {
        
        // 배열 받아서 빈배열에 넣기
        downloadedMovies = movies
        dump(downloadedMovies)
        
        print("전체 영화 갯수 확인: \(Movie.movieId)")
    } else {
        print("영화데이터가 없습니다. 또는 다운로드에 실패했습니다.")
    }
}

0개의 댓글