swift alamofire 예제
●구현할 기능
UISearchBar
구조체가 DecodableJSON을 데이터 모델로 변환
CodingKey
Decodable
UIStoryboardSegue
● Stored Property(저장 프로퍼티)
var로 선언하면 "변수"를 저장
let으로 선언하면 "상수"를 저장
lazy 프로퍼티는 항상 변수로서 선언즉, var로 선언!
게으른 저장 프로퍼티는 "값이 필요할 때" 초기화를 함.
출처 https://zeddios.tistory.com/243
● Computed Property(연산 프로퍼티)
연산프로퍼티는 값을 "저장"하기 보다는 연산을 한다 - (연산결과를 저장해줄 저장프로퍼티 필요함)
반드시 var로 선언되어야 한다.(값이 고정되어 있지 않기때문)
1. 클래스, 구조체, 열거형에 사용된다.
2. var로 선언해야한다.
3. 클래스, 구조체, 열거형에 값을 저장할 저장프로퍼티가 하나 있어야한다.==> 연산프로퍼티 자기 자신을 리턴하거나 값을 지정할 수 없다.
4. get, set을 동시에 구현 가능하며, get만 구현하는 것도 가능. 하지만 set만 구현하는 것은 안된다!
5. set의 파라미터를 생략할 수 있으며 생략했을 시, newValue라는 키워드를 사용한다.
출처 : https://zeddios.tistory.com/245?category=685736
● 프로퍼티 옵저버
프로퍼티 옵저버(willSet, didSet)은 "저장프로퍼티"에 추가될 수 있다
willSet은 값이 저장되기 직전에 호출
didSet은 새로운 값이 저장된 직후에 호출
출처 : https://zeddios.tistory.com/247?category=685736
● Type Property(타입 프로퍼티)
1. 프로퍼티를 "타입 자체"에 연결할 수 있는데, 그게 타입 프로퍼티야
2. 타입프로퍼티에는, 저장 타입 프로퍼티와 연산 타입 프로퍼티가 있어!
3. 저장 타입 프로퍼티는 상수/변수 일 수 있어!! -> let / var로 선언이 가능, 또한 무조건 기본값을 줘야해, 또!! 처음 엑세스 할 때는 초기화를 게으르게해!! 하지만 lazy키워드는 필요없어 ㅎㅎ
4. 연산 타입 프로퍼티는 무조건 변수로 선언되어야해 -> var로만 선언이 가능
출처: https://zeddios.tistory.com/251?category=685736 [ZeddiOS]
● 프로토콜 1
0.프로토콜은 메소드, 프로퍼티등을 "정의"만 하고, 이 프로토콜을 채택한 곳에서 "구현"을 한다
1. 프로토콜은 프로퍼티가 저장 프로퍼티인지, 연산 프로퍼티인지 명시하지 않는다.
2. 대신 읽기만 가능한지, 읽기/쓰기 모두가 가능한지 명시해야한다 ( setter만 있는건 없다고 그랬죠?)
3. 프로퍼티 요구사항은 항상 var로 선언되어야 한다.
프로토콜에서 gettable(읽기전용)만 요구하면, 이 요구사항을 저장프로퍼티로 선언하든, 연산프로퍼티로 선언하든 상관없다
출처: https://zeddios.tistory.com/255 [ZeddiOS]
코드블 - 인코딩 디코딩
https://zeddios.tistory.com/373
코딩키
https://zeddios.tistory.com/394
✅완성화면
✅스토리 보드
✅프로젝트 구성도
✅Model 구성
Displayable.swift
protocol Displayable {
var titleLabelText: String { get }
var subtitleLabelText: String { get }
var item1: (label: String, value:String) { get }
var item2: (label: String, value:String) { get }
var item3: (label: String, value:String) { get }
var listTitle: String { get }
var listItems: [String] { get }
}
반환 된 JSON 데이터를 어떻게 사용할까?
JSON으로 직접 작업하는 것은 중첩 된 구조로 인해 지저분해질 수 있으므로
이를 지원하기 위해 데이터를 저장할 모델을 생성한다.
Decodable : jsonString을 data화하기
Film.swift
struct Film:Decodable {
let id : Int
let title : String
let openingCrawl : String
let director : String
let producer : String
let releaseDate : String
let starships : [String]
//CodingKeys는 json key가 아닌 내가 원하는 이름으로 지정해줄 수 있게 해주는 프로토콜
//json key와 내가 지정하는 이름이 같다면 case에 json key만 작성
enum CodingKeys: String, CodingKey {
case id = "episode_id"
case title
case openingCrawl = "opening_crawl"
case director
case producer
case releaseDate = "release_date"
case starships
}
}
/*
이 확장을 통해 상세 정보 디스플레이의 뷰 컨트롤러는
모델 자체에서 필름에 대한 올바른 레이블과 값을 가져올 수 있습니다.
*/
extension Film: Displayable {
var titleLabelText: String {
title
}
var subtitleLabelText: String {
"Episode \(String(id))"
}
var item1: (label: String, value:String) {
("DIRECTOR", director)
}
var item2: (label: String, value:String) {
("PRODUCER", producer)
}
var item3: (label: String, value:String) {
("RELEASE DATE", releaseDate)
}
var listTitle: String {
"STARSHIPS"
}
var listItems: [String] {
starships
}
}
Films.swift
struct Films : Decodable {
let count : Int
let all : [Film]
enum CodingKeys: String, CodingKey {
case count
case all = "results"
}
}
starship.swift
struct Starship: Decodable {
var name: String
var model: String
var manufacturer: String
var cost: String
var length: String
var maximumSpeed: String
var crewTotal: String
var passengerTotal: String
var cargoCapacity: String
var consumables: String
var hyperdriveRating: String
var starshipClass: String
var films: [String]
enum CodingKeys: String, CodingKey {
case name
case model
case manufacturer
case cost = "cost_in_credits"
case length
case maximumSpeed = "max_atmosphering_speed"
case crewTotal = "crew"
case passengerTotal = "passengers"
case cargoCapacity = "cargo_capacity"
case consumables
case hyperdriveRating = "hyperdrive_rating"
case starshipClass = "starship_class"
case films
}
}
extension Starship: Displayable {
var titleLabelText: String {
name
}
var subtitleLabelText: String {
model
}
var item1: (label: String, value: String) {
("MANUFACTURER", manufacturer)
}
var item2: (label: String, value: String) {
("CLASS", starshipClass)
}
var item3: (label: String, value: String) {
("HYPERDRIVE RATING", hyperdriveRating)
}
var listTitle: String {
"FILMS"
}
var listItems: [String] {
films
}
}
Starships.swift
struct Starships: Decodable {
var count: Int
var all: [Starship]
enum CodingKeys: String, CodingKey {
case count
case all = "results"
}
}
✅컨트롤러 구성
MainTableViewController.swift
import Alamofire
import UIKit
class MainTableViewController: UITableViewController, UISearchBarDelegate {
//검색바
@IBOutlet weak var searchBar: UISearchBar!
var films : [Film] = []
var items : [Displayable] = [] //프로토콜
var selectedItem : Displayable?
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
fetchFilms()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
//테이블 구성
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dataCell", for: indexPath)
let item = items[indexPath.row]
cell.textLabel?.text = item.titleLabelText
cell.detailTextLabel?.text = item.subtitleLabelText
return cell
}
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
selectedItem = items[indexPath.row]
return indexPath
}
//MARK: UIStoryboardSegue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destinationVC = segue.destination as? DetailViewController else { return }
destinationVC.data = selectedItem
}
//MARK: UISearchBarDelegate
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let shipName = searchBar.text else {
return
}
searchStarships(for: shipName)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = nil
searchBar.resignFirstResponder()
items = films
tableView.reloadData()
}
}
extension MainTableViewController{
//MARK: - fetchFilms
func fetchFilms(){
let request = AF.request("https://swapi.dev/api/films")
//validate() : 응답이 200–299 범위의 HTTP 상태 코드를 반환했는지 확인하여 응답을 검증하고 응답을 데이터 모델로 디코딩합니다.
request.validate().responseDecodable(of: Films.self) { (res) in
guard let films = res.value else { return }
self.films = films.all
self.items = films.all
self.tableView.reloadData()
}
}
//검색 바
func searchStarships(for name : String){
let url = "https://swapi.dev/api/starships"
let params : [String:String] = ["search" : name]
AF.request(url, parameters: params).validate().responseDecodable(of: Starships.self) { (res) in
guard let starships = res.value else { return }
self.items = starships.all
self.tableView.reloadData()
}
}
}
DetailViewController.swift
import Alamofire
import UIKit
class DetailViewController: UIViewController, UITableViewDataSource {
//MARK: VARIABLE
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var subtitleLabel: UILabel!
@IBOutlet weak var item1TitleLabel: UILabel!
@IBOutlet weak var item1Label: UILabel!
@IBOutlet weak var item2TitleLabel: UILabel!
@IBOutlet weak var item2Label: UILabel!
@IBOutlet weak var item3TitleLabel: UILabel!
@IBOutlet weak var item3Label: UILabel!
@IBOutlet weak var listTitleLabel: UILabel!
@IBOutlet weak var listTableView: UITableView!
//MARK: - DATA
var data: Displayable?
var listData: [Displayable] = []
override func viewDidLoad() {
super.viewDidLoad()
commonInit()
listTableView.dataSource = self
fetchList()
}
private func commonInit(){
guard let data = data else { return }
titleLabel.text = data.titleLabelText
subtitleLabel.text = data.subtitleLabelText
item1Label.text = data.item1.label
item1Label.text = data.item1.value
item2Label.text = data.item2.label
item2Label.text = data.item2.value
item3Label.text = data.item3.label
item3Label.text = data.item3.value
listTitleLabel.text = data.listTitle
}
// MARK: - UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "listCell", for: indexPath)
cell.textLabel?.text = listData[indexPath.row].titleLabelText
return cell
}
}
//https://zeddios.tistory.com/226
/*
저 T라는 것은 Placeholder 타입 "이름"
T라는 타입 "이름"이 들어간거
이 T는 함수가 호출될때마다 "결정"됩니다.
*/
extension DetailViewController{
private func fetch<T:Decodable & Displayable> (_ list : [String] , of: T.Type){
var items: [T] = []
let fetchGroup = DispatchGroup()
list.forEach { (url) in
fetchGroup.enter()
AF.request(url).validate().responseDecodable(of: T.self) { (res) in
if let value = res.value {
items.append(value)
}
fetchGroup.leave()
}
}// - forEach
fetchGroup.notify(queue: .main) {
self.listData = items
self.listTableView.reloadData()
}
}
func fetchList(){
guard let data = data else {
return
}
switch data {
case is Film:
fetch(data.listItems, of: Starship.self)
case is Starship:
fetch(data.listItems, of: Film.self)
default:
print("Unknown type: ", String(describing: type(of: data)))
}
}
}
✅참고
https://www.raywenderlich.com/6587213-alamofire-5-tutorial-for-ios-getting-started
https://www.raywenderlich.com/3418439-encoding-and-decoding-in-swift
'아이폰 개발 > Swift' 카테고리의 다른 글
swift -Realm 예제 2 - ToDoList 일대다 관계 (0) | 2021.04.12 |
---|---|
swift -Realm 예제 1 - 단순 CRUD (0) | 2021.04.12 |
swift socket io 예제 (0) | 2021.03.31 |
swift fmdb 예제 1 - feat: tableview , typealias , 튜플, do catch (0) | 2021.03.30 |
swift sqlite 예제 feat : FileManager, Bundle.main, defer (0) | 2021.03.30 |