앱과 서버 간 네트워크 통신이 이루어지는 방식은 크게 두가지로 구분할 수 있다.
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 허용을 해줘야햔다.
아래처럼 통신 후 파싱하는 코드까지작성하면
//
// 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
}
}
쨔잔~!
이번에는 더보기 기능도 추가해보겠다.
아래와 같이 화면과 코드를 구현한다.
//
// 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
}
}
더보기 기능이 제대로 활용되는것을 확인할 수 있따!!!