본문 바로가기
아이폰 개발/Swift

swift alamofire 예제

by 인생여희 2021. 4. 5.

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)))
        
    }
 }
}

 

 

NetwrokingTest.zip
0.22MB

 

 

참고

 

https://www.raywenderlich.com/6587213-alamofire-5-tutorial-for-ios-getting-started

https://www.raywenderlich.com/3418439-encoding-and-decoding-in-swift