앱과 서버 간 네트워크 통신이 이루어지는 방식은 크게 두가지로 구분할 수 있다.

  1. TCP/UDP 를 사용하는 소켓 방식의 연결성 통신
    1. 저수준의 통신
    2. 앤과 서버가 연결되면 한쪽에서 명시적으로 끊을 때까지 지속해서 연결 유지
    3. 연결을 종료하기 전까지는 한번 연결된 통신을 유지하기 때문에 재연결 필요없이 빠르게 통신가능. → 하지만 부하가 커서 제한적이다.
    4. TCP는 데이터 유실을 방지하고 완전한 전송을 보장하지만 덜 빠름
    5. UDP는 더 빠르지만 데이터의 완전한 전송을 보장하지 못함
  2. HTTP/HTTPS/SMTP등의 프로토콜을 이용한 비연결성 통신
    1. 요청이 들어오면 이에 맞는 응답을 보낸 후 바로 연결을 종료
    2. 다시 요청을 하기위해서는 새로운 연결 필요해서 속도는 덜 빠르지만 부하가 적어서 모바일 서비스에 많이 이용된다.

웹 서비스란?

HTTP/HTTPS 프로토콜을 사용하지만 웹페이지와 달리 데이터만들 주고 받을 수 있도록 설계된 모듈

아키텍쳐 구조에 따라 SOAP , Restful

데이터 타입에 따라 XML , JSON 으로 나뉘어진다.

SOAP

RESTful

JSON

XML은 태그로 구성된 마크업 형식을 기본 규격으로 사용하므로 플랫폼에 의존적이지 않는 표준 데이터를 제공할 수 있다는 장점이 있지만, 용량이 지나치게 커질 수 있다.

이 단점을 극복한게 JSON 이다.

JSON객체는 {키: 데이터, 키: 데이터} 형태로 이루어진 딕셔너리식 데이터 집합이다.

성격에 따라 데이터를 계층적으로 구분할 수 있으므로 높은 수준으로 구조화된 데이터를 만들 수 있다는 장점이 있다.

실습

그럼 네트워크 실습을 진행해보겠다.

GET방식으로 RESTAPI 호출을 하는 방법은 다음과 같다.

var list = Data(contentsOf: URL타입의 객체)

Data는 파운데이션 프레임워크에서 제공하는 클래스로, 텍스트 기반의 데이터 뿐만 아니라 이미지나 동영상과 같은 바이너리 데이터도 담을 수 있어 여러 가지 종류의 데이터를 변환과정 없이 처리하는 데에 탁월하다.

다음과 같이 통신 코드를 작성해본다.

//
//  ListViewController.swift
//  MyMovieChart
//
//  Created by 김희진 on 2022/04/26.
//

import UIKit

class ListViewController: UITableViewController {

    var dataset = [("다크나이트", "영웅물에 철학에 음악까기 더해져 예술이 된다.", "2008-09-04", 9.41, "1.png"),
                   ("다크나이트", "영웅물에 철학에 음악까기 더해져 예술이 된다.", "2008-09-05", 9.41, "2.png"),
                   ("다크나이트", "영웅물에 철학에 음악까기 더해져 예술이 된다.", "2008-09-06", 9.41, "3.png")]

    lazy var list: [MovieVO] = {
        var dataList = [MovieVO]()
        for (title, desc, opendate, rating, thumbnail) in self.dataset {
            var mvo = MovieVO()
            mvo.title = title
            mvo.description = desc
            mvo.opendate = opendate
            mvo.rating = rating
            mvo.thumbnail = thumbnail
            
            dataList.append(mvo)
        }
        return dataList
    }()
    
    override func viewDidLoad() {
        let url = "<http://swiftapi.rubypaper.co.kr:2029/hoppin/movies?version=1&page=1&count=30&genreId=&order=releasedateasc>"
        let apiURI: URL! = URL(string: url)
        
        let apiData = try! Data(contentsOf: apiURI)
 
        // apiData에 저장된 데이터를 출력하기 위해 NSString타입의 문자열로 변환해야함.
        // 두번째 파라미터 encoding이 인코딩 형식이다.
        let log = NSString(data: apiData, encoding: String.Encoding.utf8.rawValue) ?? ""
        NSLog("API Result=\\(log)")
        
//스태틱 셀일때만 쓰인다.
//        tableView.register(MovieCell.self, forCellReuseIdentifier: "MovieCell")
//        if #available(iOS 15.0, *) {
//            tableView.sectionHeaderTopPadding = 0.0
//        } else {
//            // Fallback on earlier versions
//        }

//        var mvo = MovieVO()
//        mvo.title = "다크나이트"
//        mvo.description = "영웅물에 철학에 음악까지 더해져 예술이 되다."
//        mvo.opendate = "2009-09-04"
//        mvo.rating = 9.89
//        self.list.append(mvo)
//
//
//        mvo = MovieVO()
//        mvo.title = "호무시절"
//        mvo.description = "때를 알고 내리는 좋은 비"
//        mvo.opendate = "2009-07-14"
//        mvo.rating = 7.91
//        self.list.append(mvo)
//
//
//        mvo = MovieVO()
//        mvo.title = "말할 수 없는 비밀"
//        mvo.description = "여기서 너까지 다섯 걸음"
//        mvo.opendate = "2015-10-31"
//        mvo.rating = 9.19
//        self.list.append(mvo)
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return list.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        //셀에 들어갈 데이터
        let row = list[indexPath.row]

        //재사용 큐를 이용해 테1이블 뷰 셀 인스턴스 생성
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell", for: indexPath) as? MovieCell else {
            fatalError("not found")
        }
        
        cell.title.text = row.title
        cell.desc.text = row.description
        cell.opendate.text = row.opendate
        cell.rate.text = "\\(row.rating!)"
        cell.thumbnail.image = UIImage(named: row.thumbnail!)
        
        
// 커스텀 셀을 태그를 이용해서 사용. 따로 셀 클래스를 만들지 않고 identifier로 구분했다.
//        let title = cell.viewWithTag(101) as? UILabel
//        let desc = cell.viewWithTag(102) as? UILabel
//        let opendate = cell.viewWithTag(103) as? UILabel
//        let rating = cell.viewWithTag(104) as? UILabel
//
//        title?.text = row.title
//        desc?.text = row.description
//        opendate?.text = row.opendate
//        rating?.text = "\\(row.rating!)"

// 스태틱 셀
//        cell.textLabel?.text = row.title
//        cell.detailTextLabel?.text = row.description
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        NSLog("선택된 행은 \\(indexPath.row) 번째 행입니다.")
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }
 }

그리고 plist에 다음과같이 http 허용을 해줘야햔다.

스크린샷 2022-04-30 오전 10.46.48.png

아래처럼 통신 후 파싱하는 코드까지작성하면

//
//  ListViewController.swift
//  MyMovieChart
//
//  Created by 김희진 on 2022/04/26.
//

import UIKit

class ListViewController: UITableViewController {

    var dataset = [("다크나이트", "영웅물에 철학에 음악까기 더해져 예술이 된다.", "2008-09-04", 9.41, "1.png"),
                   ("다크나이트", "영웅물에 철학에 음악까기 더해져 예술이 된다.", "2008-09-05", 9.41, "2.png"),
                   ("다크나이트", "영웅물에 철학에 음악까기 더해져 예술이 된다.", "2008-09-06", 9.41, "3.png")]

    lazy var list: [MovieVO] = {
        var dataList = [MovieVO]()
        for (title, desc, opendate, rating, thumbnail) in self.dataset {
            var mvo = MovieVO()
            mvo.title = title
            mvo.description = desc
            mvo.opendate = opendate
            mvo.rating = rating
            mvo.thumbnail = thumbnail
            
            dataList.append(mvo)
        }
        return dataList
    }()
    
    override func viewDidLoad() {
        
        let url = "<http://swiftapi.rubypaper.co.kr:2029/hoppin/movies?version=1&page=1&count=30&genreId=&order=releasedateasc>"
        let apiURI: URL! = URL(string: url)
        
        let apiData = try! Data(contentsOf: apiURI)
 
        // apiData에 저장된 데이터를 출력하기 위해 NSString타입의 문자열로 변환해야함.
        // 두번째 파라미터 encoding이 인코딩 형식이다.
        let log = NSString(data: apiData, encoding: String.Encoding.utf8.rawValue) ?? ""
        NSLog("API Result=\\(log)")
        
        
        do{
            // api호출결과는 Data 타입이여서 로그를 출력하기 위해 NSString 으로 변환하였듯이, 테이블을 구성하는 데이터로 사용하려면 NSDictionary 객체로 변환해야한다.
            // NSDictionary 객체는 키-값 쌍으로 되어있어 JSONObject와 호환이 된다.
            // 만약 데이터가 리스트 형해로 전달되었다면 NSArray객체를 이용해야한다.
            // 파싱할 떄는 jsonObject를 이용한다. 근데 jsonObject는 파싱 과정에서 오류가 발생하면 이를 예외로 던지게 설계되어 있어 do~try~catch 구문으로 감싸줘야한다.
            // jsonObject실행 결과로 NSDictionary, NSArray 형태가 나온다. 양쪽을 모두 지원하기 위해 jsonObject 은 Any를 리턴하므로 이 결과값을 원하는 값으로 캐스팅해서 받아야한다.
            let apiDictionary = try JSONSerialization.jsonObject(with: apiData, options: []) as! NSDictionary
            let hoppin = apiDictionary["hoppin"] as! NSDictionary
            let movies = hoppin["movies"] as! NSDictionary
            let movie = movies["movie"] as! NSArray
            
            for row in movie {
                let r = row as! NSDictionary
                let mvo = MovieVO()
                
                mvo.title = r["title"] as? String
                mvo.description = r["genreNames"] as? String
                mvo.thumbnail = r["thumbnailImage"] as? String
                mvo.detail = r["linkUrl"] as? String
                mvo.rating = ((r["ratingAverage"] as! NSString).doubleValue)
                
                list.append(mvo)
            }
            
        }catch{}
        
        
        
//스태틱 셀일때만 쓰인다.
//        tableView.register(MovieCell.self, forCellReuseIdentifier: "MovieCell")
//        if #available(iOS 15.0, *) {
//            tableView.sectionHeaderTopPadding = 0.0
//        } else {
//            // Fallback on earlier versions
//        }

//        var mvo = MovieVO()
//        mvo.title = "다크나이트"
//        mvo.description = "영웅물에 철학에 음악까지 더해져 예술이 되다."
//        mvo.opendate = "2009-09-04"
//        mvo.rating = 9.89
//        self.list.append(mvo)
//
//
//        mvo = MovieVO()
//        mvo.title = "호무시절"
//        mvo.description = "때를 알고 내리는 좋은 비"
//        mvo.opendate = "2009-07-14"
//        mvo.rating = 7.91
//        self.list.append(mvo)
//
//
//        mvo = MovieVO()
//        mvo.title = "말할 수 없는 비밀"
//        mvo.description = "여기서 너까지 다섯 걸음"
//        mvo.opendate = "2015-10-31"
//        mvo.rating = 9.19
//        self.list.append(mvo)
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return list.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        //셀에 들어갈 데이터
        let row = list[indexPath.row]

        //재사용 큐를 이용해 테1이블 뷰 셀 인스턴스 생성
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell", for: indexPath) as? MovieCell else {
            fatalError("not found")
        }
        
        cell.title.text = row.title
        cell.desc.text = row.description
        cell.opendate.text = row.opendate
        cell.rate.text = "\\(row.rating!)"
        if indexPath.row < 3 {
            cell.thumbnail.image = UIImage(named: row.thumbnail!)
        } else {
            let url: URL! = URL(string: row.thumbnail!)
            let imageData = try! Data(contentsOf: url)
            cell.thumbnail.image = UIImage(data: imageData)
        }
        
        
// 커스텀 셀을 태그를 이용해서 사용. 따로 셀 클래스를 만들지 않고 identifier로 구분했다.
//        let title = cell.viewWithTag(101) as? UILabel
//        let desc = cell.viewWithTag(102) as? UILabel
//        let opendate = cell.viewWithTag(103) as? UILabel
//        let rating = cell.viewWithTag(104) as? UILabel
//
//        title?.text = row.title
//        desc?.text = row.description
//        opendate?.text = row.opendate
//        rating?.text = "\\(row.rating!)"

// 스태틱 셀
//        cell.textLabel?.text = row.title
//        cell.detailTextLabel?.text = row.description
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        NSLog("선택된 행은 \\(indexPath.row) 번째 행입니다.")
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }
    
 }

Simulator Screen Shot - x - 2022-04-30 at 12.20.49.png

쨔잔~!

이번에는 더보기 기능도 추가해보겠다.

아래와 같이 화면과 코드를 구현한다.

스크린샷 2022-04-30 오후 12.40.38.png

//
//  ListViewController.swift
//  MyMovieChart
//
//  Created by 김희진 on 2022/04/26.
//

import UIKit

class ListViewController: UITableViewController {

    var dataset = [("다크나이트", "영웅물에 철학에 음악까기 더해져 예술이 된다.", "2008-09-04", 9.41, "1.png"),
                   ("다크나이트", "영웅물에 철학에 음악까기 더해져 예술이 된다.", "2008-09-05", 9.41, "2.png"),
                   ("다크나이트", "영웅물에 철학에 음악까기 더해져 예술이 된다.", "2008-09-06", 9.41, "3.png")]

    var page = 1
    
    lazy var list: [MovieVO] = {
        var dataList = [MovieVO]()
        for (title, desc, opendate, rating, thumbnail) in self.dataset {
            var mvo = MovieVO()
            mvo.title = title
            mvo.description = desc
            mvo.opendate = opendate
            mvo.rating = rating
            mvo.thumbnail = thumbnail
            
            dataList.append(mvo)
        }
        return dataList
    }()
    
    @IBOutlet var moreButton: UIButton!
    
    override func viewDidLoad() {
        
       callMovieAPI()

        
//스태틱 셀일때만 쓰인다.
//        tableView.register(MovieCell.self, forCellReuseIdentifier: "MovieCell")
//        if #available(iOS 15.0, *) {
//            tableView.sectionHeaderTopPadding = 0.0
//        } else {
//            // Fallback on earlier versions
//        }

//        var mvo = MovieVO()
//        mvo.title = "다크나이트"
//        mvo.description = "영웅물에 철학에 음악까지 더해져 예술이 되다."
//        mvo.opendate = "2009-09-04"
//        mvo.rating = 9.89
//        self.list.append(mvo)
//
//
//        mvo = MovieVO()
//        mvo.title = "호무시절"
//        mvo.description = "때를 알고 내리는 좋은 비"
//        mvo.opendate = "2009-07-14"
//        mvo.rating = 7.91
//        self.list.append(mvo)
//
//
//        mvo = MovieVO()
//        mvo.title = "말할 수 없는 비밀"
//        mvo.description = "여기서 너까지 다섯 걸음"
//        mvo.opendate = "2015-10-31"
//        mvo.rating = 9.19
//        self.list.append(mvo)
    }
    
    func callMovieAPI(){
        
        let url = "<http://swiftapi.rubypaper.co.kr:2029/hoppin/movies?version=1&page=\\(self.page)&count=30&genreId=&order=releasedateasc>"
        let apiURI: URL! = URL(string: url)
        
        let apiData = try! Data(contentsOf: apiURI)
 
        // apiData에 저장된 데이터를 출력하기 위해 NSString타입의 문자열로 변환해야함.
        // 두번째 파라미터 encoding이 인코딩 형식이다.
        let log = NSString(data: apiData, encoding: String.Encoding.utf8.rawValue) ?? ""
        NSLog("API Result=\\(log)")
        
        do{
            // api호출결과는 Data 타입이여서 로그를 출력하기 위해 NSString 으로 변환하였듯이, 테이블을 구성하는 데이터로 사용하려면 NSDictionary 객체로 변환해야한다.
            // NSDictionary 객체는 키-값 쌍으로 되어있어 JSONObject와 호환이 된다.
            // 만약 데이터가 리스트 형해로 전달되었다면 NSArray객체를 이용해야한다.
            // 파싱할 떄는 jsonObject를 이용한다. 근데 jsonObject는 파싱 과정에서 오류가 발생하면 이를 예외로 던지게 설계되어 있어 do~try~catch 구문으로 감싸줘야한다.
            // jsonObject실행 결과로 NSDictionary, NSArray 형태가 나온다. 양쪽을 모두 지원하기 위해 jsonObject 은 Any를 리턴하므로 이 결과값을 원하는 값으로 캐스팅해서 받아야한다.
            let apiDictionary = try JSONSerialization.jsonObject(with: apiData, options: []) as! NSDictionary
            let hoppin = apiDictionary["hoppin"] as! NSDictionary
            let totalCount = (hoppin["totalCount"] as? NSString)!.integerValue
            let movies = hoppin["movies"] as! NSDictionary
            let movie = movies["movie"] as! NSArray
            
            for row in movie {
                let r = row as! NSDictionary
                let mvo = MovieVO()
                
                mvo.title = r["title"] as? String
                mvo.description = r["genreNames"] as? String
                mvo.thumbnail = r["thumbnailImage"] as? String
                mvo.detail = r["linkUrl"] as? String
                mvo.rating = ((r["ratingAverage"] as! NSString).doubleValue)
                
                list.append(mvo)
            }
            
            if list.count > totalCount {
                self.moreButton.isHidden = true
            }
                        
        }catch{
            NSLog("Parse Error!!")
        }
        
    }
    
    @IBAction func more(_ sender: Any) {
        self.page += 1
        callMovieAPI()
        self.tableView.reloadData()
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return list.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        //셀에 들어갈 데이터
        let row = list[indexPath.row]

        //재사용 큐를 이용해 테1이블 뷰 셀 인스턴스 생성
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell", for: indexPath) as? MovieCell else {
            fatalError("not found")
        }
        
        cell.title.text = row.title
        cell.desc.text = row.description
        cell.opendate.text = row.opendate
        cell.rate.text = "\\(row.rating!)"
        if indexPath.row < 3 {
            cell.thumbnail.image = UIImage(named: row.thumbnail!)
        } else {
            let url: URL! = URL(string: row.thumbnail!)
            let imageData = try! Data(contentsOf: url)
            cell.thumbnail.image = UIImage(data: imageData)
        }
        
        
// 커스텀 셀을 태그를 이용해서 사용. 따로 셀 클래스를 만들지 않고 identifier로 구분했다.
//        let title = cell.viewWithTag(101) as? UILabel
//        let desc = cell.viewWithTag(102) as? UILabel
//        let opendate = cell.viewWithTag(103) as? UILabel
//        let rating = cell.viewWithTag(104) as? UILabel
//
//        title?.text = row.title
//        desc?.text = row.description
//        opendate?.text = row.opendate
//        rating?.text = "\\(row.rating!)"

// 스태틱 셀
//        cell.textLabel?.text = row.title
//        cell.detailTextLabel?.text = row.description
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        NSLog("선택된 행은 \\(indexPath.row) 번째 행입니다.")
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }
    
 }

더보기 기능이 제대로 활용되는것을 확인할 수 있따!!!