-
Alamofire 5 Tutorial for iOS: Getting StartedRaywenderlich/Articles 2020. 11. 23. 17:32
www.raywenderlich.com/6587213-alamofire-5-tutorial-for-ios-getting-started#toc-anchor-014
Alamofire 5 Tutorial for iOS: Getting Started
In this Alamofire tutorial, you’ll build an iOS companion app to perform networking tasks, send request parameters, decode/encode responses and more.
www.raywenderlich.com
Version
Swift 5, iOS 13, Xcode 11
iOS 앱(apps)을 개발(developing)해왔다면, 네트워크(network)를 사용해 데이터에 접근(access)하기 위해 Foundation의
URLSession
을 사용했을지도 모른다. 이것은 괜찮지만(fine), 때로는 사용하기가 번거롭다(cumbersome). 이것이 Alamofire 튜토리얼(tutorial)이 필요한 이유이다.Alamofire는 Swift 기반의 HTTP 네트워킹(networking) 라이브러리(library)이다. Apple의 Foundation 네트워킹(networking) 스택(stack) 위에 일반적인 네트워킹(networking) 작업을 단순화(simplifies)한 우아한(elegant) 인터페이스(interface)를 제공(provides)한다. 그 기능(features)에는 연결 가능한(chainable) 요청(request)/응답(response) 메서드(methods), JSON 및 Codable 디코딩(decoding), 인증(authentication) 등이 포함된다.
이 Alamofire 튜토리얼(tutorial)에서는 다음과 같은 기본(basic) 네트워킹(networking) 작업을 수행(perform)한다:
- 서드파티(third-party) RESTful API에서 데이터 요청(Requesting)
- 요청(request) 매개 변수(parameters) 전송
- 응답(response)을 JSON으로 변환(converting)
- Codable 프로토콜(protocol)을 사용해 응답(response)을 Swift 데이터 모델(model)로 변환(converting)
Note: 이 튜토리얼(tutorial)를 시작하기 전에 HTTP 네트워킹(networking)에 대한 개념(conceptual)을 이해해야 한다. Apple의 네트워킹(networking) 클래스(classes)에 대한 이해(exposure)가 도움이 되지만, 반드시 필요한(necessary) 것은 아니다. Alamofire는 구현(implementation) 세부 정보(details)를 숨기지만(obscures), 네트워크(network) 요청(requests) 문제를 해결(troubleshoot)해야하는 경우 배경 지식(background knowledge)을 알고 있는 것이 좋다.
Getting Started
시작하려면, 이 문서(article) 상단(top) 또는 하단(bottom)에 있는 Download Materials 버튼(button)을 사용하여 시작 프로젝트(begin project)를 다운로드(download)한다.
이 튜토리얼(tutorial) 앱(app)은 StarWarsOpedia로, Star Wars 영화(films)에 대한 데이터와 해당 영화에서 사용된 우주선(starships)에 대해 데이터에 빠르게 접근(access)할 수 있다.
시작 프로젝트(begin project)에서 StarWarsOpedia.xcworkspace를 열어 시작한다.
빌드(build)하고 실행(run)한다. 다음과 같은 화면을 볼 수 있다:
지금은 백지상태(blank slate)이지만, 곧 데이터로 채워질(populate) 것이다.
Note: 일반적으로 CocoaPods 또는 다른 종속성 관리자(dependency manager)를 사용하여 Alamofire를 통합(integrate)한다. 이 튜토리얼의 경우에는 다운로드(downloaded)한 프로젝트(projects)에 이미 설치(pre-installed)되어 있다. CocoaPods를 사용하여 Alamofire를 프로젝트(projects)에 통합(integrating)하는 데 도움이 필요하다면 CocoaPods Tutorial for Swift: Getting Started를 참조할 수 있다.
Using the SW API
SW API는 Star Wars 데이터를 제공(provides)하는 무료(free) 오픈(open) API이다. 주기적으로(periodically) 업데이트되지만, Alamofire에 대해 알아가는 재미있는 방법이다. swapi.dev에서 API에 접근(access)한다.
특정(specific) 데이터에 접근(access)하기 위한 여러 엔드 포인트(endpoints)가 있지만, https://swapi.dev/api/films 와https://swapi.dev/api/starships에 집중한다.
자세한 내용(information)은 Swapi 문서(documentation)를 참조(explore)한다.
Understanding HTTP, REST and JSON
인터넷(Internet)으로 서드 파티(third-party) 서비스(services)에 접근(accessing)하는 것이 처음이라면, 이 간단한 설명(explanation)이 도움이 될 것이다.
HTTP는 서버(server)에서 웹 브라우저(web browser) 또는 iOS 앱(app)과 같은 클라이언트(client)로 데이터를 전송(transfer)하는 데 사용되는 응용 프로그램(application) 프로토콜(protocol) 이다. HTTP는 클라이언트(client)가 원하는 작업(desired action)을 나타내는 데 사용하는 여러(several) 요청(request) 메서드(methods)를 정의(defines)한다. 예를 들면 다음과 같다:
- GET: 웹 페이지(web page)와 같은 데이터를 검색(retrieves)하지만, 서버(server)의 데이터는 변경(alter)하지 않는다.
- HEAD: GET과 동일(Identical)하지만, 헤더(headers)만 반환(sends back)하고 실제 데이터는 보내지 않는다.
- POST: 데이터를 서버(server)로 보낸다. 예를 들어(for example), 양식(form)을 작성(filling)하고 제출(submit)할 때 사용된다.
- PUT: 제공된 특정 위치(specific location provided)로 데이터를 보낸다(sends). 예를 들어(for example), 사용자 프로필(profile)을 업데이트(updating)할 때 사용된다.
- DELETE: 제공된 특정 위치(specific location provided)에서 데이터를 삭제(deletes)한다.
JSON은 JavaScript Object Notation을 나타내며, 시스템(systems)간에 데이터를 전송(transporting)하기 위한 간단하고(straightforward) 사람이 읽을 수 있는(human-readable) 이식 가능한(portable) 메커니즘(mechanism)을 제공(provides)한다. JSON은string, boolean, array, object/dictionary, number, null로 데이터 유형(types)이 제한(limited)되어 있다.
Swift 4 이전의 어두운 시절(dark days)에는
JSONSerialization
클래스(class)를 사용하여 JSON을 데이터 객체(objects)로 또는 그 반대로(vice-versa) 변환(convert)해야 했다.그것은 잘 작동했고 여전히 사용할 수 있지만, 이제는 더 나은 방법이 있다. 바로
Codable
이다. 데이터 모델(models)이Codable
을 준수(conforming)하면, JSON에서 데이터 모델(models)로 거의(nearly) 자동으로(automatic) 변환(conversion)할 수 있다.REST(REpresentational State Transfer)는 일관(consistent)된 웹(web) API를 설계(designing)하기 위한 일련의 규칙(set of rules)이다. EST에는 요청(requests)간에 상태(states)를 유지(persisting)하지 않도록 하거나, 요청(requests)을 캐시할 수 있게(cacheable) 만들거나, 균일한(uniform) 인터페이스(interfaces)를 제공(providing)하는 등의 표준(standards)을 적용하는 여러(several) 아키텍처(architecture) 규칙(rules)이 있다. 이는 앱 개발자(app developers)가 여러 요청(requests)간에 데이터 상태(state)를 추적(track)할 필요없이, API를 앱(app)에 쉽게 통합(integrate)할 수 있게 한다.
HTTP, JSON, REST는 개발자(developer)가 사용할 수 있는 웹 서비스(web services)의 상당 부분(portion)을 차지한다(comprise). 모든 조각(little piece)이 어떻게 작동하는지 이해하려고 노력하는 것은 너무 과도할(overwhelming) 수 있다. 이것이 Alamofire가 필요한 이유이다.
Why Use Alamofire?
Apple은 이미 HTTP를 사용해 콘텐츠(content)에 접근(accessing)할 수 있는
URLSession
과 기타 클래스(classes)를 제공하고 있는데, 왜 Alamofire를 사용해 코드(code base)에 다른 종속성(dependency)을 추가하는지 궁금할 수 있다.단답(short answer)으로 말하면, Alamofire가 URLSession을 기반으로(based on) 하지만 네트워킹(networking) 호출(calls) 시에 발생하는 많은 어려움(difficulties)을 감춰(obscures) 비즈니스 논리(business logic)에 집중(concentrate on)할 수 있다는 것이다. 적은 노력(effort)으로 인터넷(internet) 데이터에 접근(access)할 수 있으며, 코드가 더 깨끗(cleaner)하고 읽기 쉬워진다(easier to read).
Alamofire에서 사용할 수 있는 몇 가지(several) 주요(major) 함수(functions)는 다음과 같다:
- AF.upload: 멀티 파트(multipart), 스트림(stream), 파일(file), 데이터(data) 메서드(methods)로 파일을 업로드(upload)한다.
- AF.download: 파일을 다운로드(download)하거나 이미 진행(progress) 중인 다운로드를 재개(resume)한다.
- AF.request: 파일 전송(transfers)과 관련(associated with)이 없는 다른 모든 HTTP 요청(request)이다.
이러한 Alamofire 메서드(methods)는 전역(global)이므로 사용하기 위해 클래스(class)를 인스턴스화(instantiate)할 필요가 없다. 기본(underlying) Alamofire 요소(elements)에는
SessionManager
,DataRequest
,DataResponse
와 같은 클래스(classes) 및 구조체(structs)가 포함(include)된다. 그러나 Alamofire를 사용하기 위해 전체(entire) 구조(structure)를 완전히(fully) 이해(understand)할 필요는 없다.이론(theory)은 이제 충분하다. 코드 작성을 시작할 시간이다.
Requesting Data
멋진 앱(app)을 만들기 전에 몇 가지 설정(setup)을 해야한다.
MainTableViewController.swift를 열어 시작한다.
imort UIKit
아래에 다음을 추가한다:import Alamofire
이렇게 하면, 이 view controller에서 Alamofire를 사용할 수 있다. 파일 하단(bottom)에 다음을 추가(add)한다:
extension MainTableViewController { func fetchFilms() { let request = AF.request("https://swapi.dev/api/films") //Alamofire는 namespacing을 사용하기 때문에, AF와 함께 사용하는 모든 호출에 prefix를 지정해야 한다. //request(_:method:parameters:encoding:headers:interceptor:)는 데이터에 대한 end point를 허용한다. //더 많은 parameters를 사용할 수 있지만 지금은 URL을 문자열로 보내고, 기본 parameter 값을 사용한다. request.responseJSON { (data) in //request에서 받은 response를 JSON으로 가져온다. print(data) //지금은 단순 디버깅을 위해 JSON 데이터를 출력한다. } } }
마지막으로
viewDidLoad()
의 끝에 다음을 추가한다:fetchFilms()
이는 방금 구현(implemented)한 Alamofire 요청(request)을 실행(triggers)한다.
빌드(build)하고 실행(run)한다. 콘솔(console) 상단에 다음과 같은 내용이 표시된다:
success({
count = 7;
next = "";
previous = "";
results = ({...})
})아주 간단한 몇 줄로 서버(server)에서 JSON 데이터를 가져 왔다(fetched).
Using a Codable Data Model
반환(returned)된 JSON 데이터를 JSON으로 직접(directly) 작업하는 것은 중첩(nested)된 구조(structure)로 인해 지저분(messy)할 수 있으므로 이를 지원하는 데이터 저장(store) 모델(models)을 생성할 수 있다.
프로젝트 탐색기(Project navigator)에서 Networking 그룹(group)을 찾고, 해당 그룹(group)에 Film.swift라는 새 Swift 파일(file)을 만든다.
그런 다음 아래 코드를 추가한다:
struct Film: Decodable { let id: Int let title: String let openingCrawl: String let director: String let producer: String let releaseDate: String let starships: [String] enum CodingKeys: String, CodingKey { case id = "episode_id" case title case openingCrawl = "opening_crawl" case director case producer case releaseDate = "release_date" case starships } }
이 코드를 사용하여 API의 film 엔드 포인트(endpoint)에서 데이터를 가져오는 데(pull) 필요한 데이터 속성(properties)과 코딩 키(coding keys)를 만든다. JSON을 데이터 모델(data model)로 변환할 수 있도록 구조체(struct)가
Decodable
로 된 것을 확인(note)한다.프로젝트(project)는 튜토리얼(tutorial) 뒷부분에서 자세한(detailed) 정보(information)를 간단하게(simplify) 표시하기 위해
Displayable
프로토콜(protocol)을 정의(defines)한다.Film
은 이를 준수(conform)해야 한다. Film.swift 끝에 다음을 추가한다: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 } }
이 확장(extension)은 상세 정보(detailed information)를 보여주는 뷰 컨트롤러(view controller)가 모델(model) 자체에서 영화(film)에 대한 올바른(correct) 레이블(labels)과 값(values)을 가져올 수 있도록 한다.
Networking 그룹(group)에 Films.swift라는 이름의 새 Swift 파일(file)을 생성한다.
파일(file)에 다음 코드를 추가한다:
struct Films: Decodable { let count: Int let all: [Film] enum CodingKeys: String, CodingKey { case count case all = "results" } }
이 구조체(struct)는 영화 모음(collection of films)을 나타낸다(denotes). 이전에 콘솔(console)에서 확인했듯이, 엔드 포인트(endpoint) swapi.dev/api/films는
count
,next
,previous
,results
의 네 가지 주요(main) 값(values)을 반환(returns)한다. 해당 앱(app)에서는count
와results
만 필요하므로, 구조체(struct)에 모든 속성(properties)이 있지 않다.코딩 키(coding keys)는 서버(server)의
resilts
를all
로 변환(transform)한다. 이는Films.results
보다Films.all
이 더 읽기 쉽기 때문이다. 다시 말하지만 데이터 모델(data model)은Decodable
을 준수(conforming)하기 때문에, Alamofire는 JSON 데이터를 데이터 모델(data model)로 변환(convert)할 수 있다.Note: Codable에 대한 자세한 내용(information)은 Encoding and Decoding in Swift을 참고할 수 있다.
MainTableViewController.swift로 돌아가
fetchFilms()
에서 다음을:request.responseJSON { (data) in print(data) }
아래로 바꾼다(replace):
request.responseDecodable(of: Films.self) { (response) in guard let films = response.value else { return } print(films.all[0].title) }
이제 응답(response)을 JSON으로 변환(converting)하는 대신, 내부(internal) 데이터 모델(data model)인
Films
으로 변환(convert)한다. 디버깅을 위해(for debugging purposes), 검색된(retrieved) 첫 번째 영화(film)의 제목(title)을 출력한다.빌드(build)하고 실행(run)한다. Xcode 콘솔(console)에서 배열의 첫 번째 영화(film)의 제목을 볼 수 있다. 다음 작업은 전체 영화 목록을 표시(display)하는 것이다.
Method Chaining
Alamofire는 한 메서드(method)의 응답(response)을 다른 메서드의 입력(input)으로 연결(connecting)하여 작동하는 method chaining을 사용한다. 이것은 코드를 간결(compact)하게 유지할 뿐만 아니라 코드를 더 명확하게(clearer) 한다.
fetchFilms()
의 모든 코드를 다음으로 대체(replacing)하여 이를 시도해 본다:AF.request("https://swapi.dev/api/films") .validate() .responseDecodable(of: Films.self) { (response) in guard let films = response.value else { return } print(films.all[0].title) }
이 단일 행(line)은 이전에 여러(multiple) 행(line)이 필요했던 작업을 정확히(exactly) 수행할 뿐만 아니라, 유효성 검사(validation)도 추가했다.
위에서 아래로 엔드 포인트(endpoint)를 요청(request)하고, 응답(response)이 200–299 범위(range)의 HTTP 상태 코드(status code)를 반환(returned)했는지 확인(validate)하여 응답(response)을 검증하고 응답(response)을 데이터 모델(data model)로 디코딩(decode)한다.
Setting up Your Table View
이제
MainTableViewController
의 맨 위에 다음을 추가한다:var items: [Displayable] = []
이 속성(property)을 사용하여, 서버(server)에서 가져온 정보(information) 배열(array)을 저장(store)한다. 지금은 일련의 영화(films)들이지만, 곧 더 멋진(coolness) 것을 구현할 것이다.
fetchFilms()
에서 다음을:print(films.all[0].title)
아래와 같이 바꾼다(replace):
self.items = films.all self.tableView.reloadData()
검색된(retrieved) 모든 영화(films)를
items
에 할당(assigns)하고, 테이블 뷰(table view)를 다시 로드(reloads)한다.내용(content)를 표시하는 테이블 뷰(table view)를 얻으려면, 추가적인(further) 변경(changes)이 필요하다.
tableView(_:numberOfRowsInSection:)
의 코드를 다음으로 바꾼다(replace):return items.count
이렇게 하면 영화(films)의 수 만큼 셀(cell)을 표시할 수 있다.
다음으로
tableView(_:cellForRowAt:)
의cell
선언(declaration) 바로 아래에 다음 코드를 추가한다:let item = items[indexPath.row] cell.textLabel?.text = item.titleLabelText cell.detailTextLabel?.text = item.subtitleLabelText
여기서
Displayable
로 제공(provided)되는 속성(properties)을 사용하여, 영화(film) 제목과 에피소드(episode) ID로 셀(cell)을 설정(set up)한다.빌드(build)하고 실행(run)한다. 영화(films) 목록(list)이 표시된다:
이제 조금씩 진전되고 있다(Now you’re getting somewhere). 서버(server)에서 데이터를 가져와(pulling) 내부(internal) 데이터 모델(data model)로 디코딩(decoding)하고, 해당 모델(model)을 뷰 컨트롤러(view controller)의 속성(property)에 할당(assigning)하며 해당 속성(property)을 사용해 테이블 뷰(table view)를 채운다(populate).
하지만 놀랍게도(wonderful) 작은 문제(problem)가 있다. 셀(cells) 중 하나를 탭(tap)하면, 제대로 업데이트(updating)되지 않은 상세(detail) 뷰 컨트롤러(view controller)로 이동한다. 다음으로, 이를 수정(fix)한다.
Updating the Detail View Controller
먼저 선택한(selected) 항목(item)을 등록(register)한다.
var items: [Displayable] = []
아래에 다음을 추가한다:var selectedItem: Displayable?
현재 선택한(currently-selected) 영화(film)를 이 속성(property)에 저장(store)한다.
이제
tableView(_:willSelectRowAt:)
의 코드를 다음으로 바꾼다(replace):selectedItem = items[indexPath.row] return indexPath
여기에서는 선택한 행(selected row)에서 영화(film)를 가져와
selectedItem
에 저장(saving)한다.이제
prepare(for:sender:)
에서 다음을:destinationVC.data = nil
아래로 대체(replace)한다:
destinationVC.data = selectedItem
사용자(user)의 선택(selection)을 표시할(display) 데이터로 설정(sets)한다.
빌드(build)하고 실행(run)하여 영화(films)를 탭(tap)한다. 대부분(mostly) 완전한(complete) 상세 뷰(detail view)가 표시되어야 한다.
Fetching Multiple Asynchronous Endpoints
지금까지는 단일(single) 요청(request)으로 영화(film)의 데이터 배열(array)을 반환(returns)하는 films 엔드 포인트(endpoint) 데이터만 요청(requested)했다.
Film
을 보면[String]
유형(type)의starships
을 확인할 수 있다. 이 속성(property)은 모든 우주선(starship) 데이터가 아닌, 우주선(starship) 데이터에 대한 엔드 포인트(endpoints) 배열(array)을 포함(contain)한다. 이것은 프로그래머(programmers)가 필요(necessary) 이상의 데이터를 제공(provide)하지 않으면서, 데이터에 대한 접근(access)을 제공하기 위해 사용하는 일반적인(common) 패턴(pattern)이다.예를 들어(for example), Jar Jar가 "The Phantom Menace"를 결코 탭(tap)하지 않는다고 가정(imagine)해 본다. 이를 사용하지 않기 때문에 서버(server)가 "The Phantom Menace"에 대한 모든 우주선(starship) 데이터를 보내는 것은 자원(resources)과 대역폭(bandwidth)을 낭비(waste)하는 것이다. 대신(instead) 서버(server)는 각 우주선(starship)에 대한 엔드 포인트(endpoints) 목록(list)을 보내므로(sends), 우주선(starship) 데이터를 원하는 경우 가져올(fetch) 수 있다.
Creating a Data Model for Starships
우주선(starships)을 가져 오기(fetching) 전에, 먼저 우주선(starship) 데이터를 처리(handle)할 새로운 데이터 모델(data model)이 필요(need)하다. 다음 단계(step)는 이를 만드는 것이다.
Networking 그룹(group)에서 새 Swift 파일(file)을 추가한다. 이름을 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 } }
다른 데이터 모델(data models)과 마찬가지로, 관련(relevant) 코딩 키(coding keys)와 함께 사용할 모든 응답(response) 데이터를 나열(list)하기만 하면 된다.
또한 개별(individual) 우주선(ships)에 대한 정보(information)를 표시해야 하므로,
Starship
은Displayable
을 준수(conform to)해야 한다. 파일(file) 끝에 다음을 추가한다: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 } }
이전
Film
에서 한 것과 마찬가지로, 이 확장(extension)을 사용하면DetailViewController
가 모델(model) 자체에서 올바른 레이블(labels)과 값(values)을 가져올(get) 수 있다.Fetching the Starship Data
우주선(starship) 데이터를 가져 오려(fetch)면, 새로운 네트워킹(networking) 호출(call)이 필요하다. DetailViewController.swift를 열고 다음 import 구문(statement)을 맨 위(top)에 추가한다:
import Alamofire
그런 다음, 파일 하단(bottom)에 다음을 추가한다:
extension DetailViewController { private func fetch<T: Decodable & Displayable>(_ list: [String], of: T.Type) { //Starship은 films 목록을 가지고 있다. Film과 Starship은 모두 Displayable이므로, //네트워크 request를 수행하기 위한 generic heper를 작성할 수 있다. //결과를 적절하게 decode 할수 있도록, 가져 오는 항목의 type만 알면 된다. var items: [T] = [] let fetchGroup = DispatchGroup() //item당 multiple calls이 필요하며, 이러한 call은 asynchronous이므로 순서가 맞지 않을 수 있다. //이를 처리하기 위해, 모든 calls이 completed되면 통지 받을 수 있도록 dispatch group을 사용한다. list.forEach { (url) in //list의 item을 반복한다. fetchGroup.enter() //dispatch group에 enter를 알려준다. AF.request(url) //starship 엔드 포인트(end point)에 Alamofire request하고 .validate() //response를 validate 한 후 .responseDecodable(of: T.self) { (response) in //적절한 type으로 decode한다. if let value = response.value { items.append(value) } fetchGroup.leave() //request의 completion handler에서 dispatch group에 leave를 알려준다. } } fetchGroup.notify(queue: .main) { //dispatch group이 각 enter()에 대해 leave()를 받으면, main queue에서 실행 중인지 확인하고 self.listData = items //listData에 저장한 후 self.listTableView.reloadData() //list table view를 reload한다. } } }
Note: 전체 요청(request)을 더 빨리 완료(finish)할 수 있도록, 목록(list) 항목(items)을 비동기(asynchronously)로 가져온다(fetch). 영화(movie)에 100개의 우주선(starships)이 있고, 동시에(synchronously) 한 번에 하나씩만 가져와야(fetch) 한다고 상상해(imagine) 본다. 각 요청(request)에 100ms가 걸린다면, 모든 우주선(starships)을 가져 오기(fetch) 위해서는 10초 동안 기다려야 한다. 동시에 더 많은 우주선(starships)을 가져 오면(fetching), 이를 크게 줄일(reduces) 수 있다.
이제 도우미(helper)를 만들었으므로, 실제로 영화(film)에서 우주선(starships) 목록(list)을 가져와야(fetch) 한다. 확장(extension)에 다음을 추가한다:
func fetchList() { guard let data = data else { return } //data는 optional이므로, 다른 작업을 수행하기 전에 nil이 아닌지 확인한다. switch data { //data를 사용하여 helper method를 호출하는 방법을 결정한다. case is Film: //data가 Film인 경우, 관련 목록은 starships의 list이다. fetch(data.listItems, of: Starship.self) default: print("Unknown type: ", String(describing: type(of: data))) } }
이제 우주선(starships)을 가져올(fetch) 수 있으므로, 앱(app)에 표시(display)해야 한다. 다음 단계(step)에서 이를 수행할 것이다.
Updating Your Table View
tableView(_:cellForRowAt:)
의return cell
앞에 다음을 추가한다:cell.textLabel?.text = listData[indexPath.row].titleLabelText
이 코드는 데이터 목록(list)에서 적절한(appropriate) 제목(title)으로 셀(cell)의
textLabel
을 설정(sets)한다.마지막으로
viewDidLoad()
의 끝에 다음을 추가한다:fetchList()
빌드(build)하고 실행(run)한 다음 아무 영화(film)나 탭(tap)해 본다. 영화(film)와 우주선(starship) 데이터로 완전히(fully) 채워진(populated) 상세 뷰(detail view)가 표시된다. 깔끔(neat)하다.
앱이 꽤 견고(solid)해 보이기 시작했다. 그러나 기본 뷰 컨트롤러(main view controller)를 살펴보면, 검색 창(search bar)이 작동하지 않는다는 것을 알(notice) 수 있다. 이름(name)이나 모델(model)로 우주선(starships)을 검색 할 수 있기를 원하며. 다음(next)에 이를 다룰(tackle) 것이다.
Sending Parameters With a Request
검색(search)이 작동(work)하려면, 검색(search) 기준(criteria)과 일치(match)하는 우주선(starships) 목록(list)이 필요하다. 이를 수행(accomplish)하려면, 우주선(starships)을 얻기 위해 검색 기준(search criteria)을 엔드 포인트(endpoint)로 보내야(send) 한다.
이전에는 영화(films)의 엔드 포인트(endpoint)인 https://swapi.dev/api/films를 사용하여 영화 목록(list of films)을 가져 왔다. https://swapi.dev/api/starships 엔드 포인트(endpoint)를 사용하여 모든 우주선 목록(list of all starships)을 가져올 수도 있다.
엔드 포인트(endpoint)를 살펴보면(take a look), 영화(film)의 응답(response)과 유사한(similar to) 응답(response)이 표시된다:
success({
count = 37;
next = "";
previous = "";
results = ({...})
})유일한 차이점(difference)은 이번 결과 데이터는 모든 우주선(starships)의 목록(list)이라는 것이다.
Alamofire의
request
는 지금까지 보낸 URL 문자열(string) 이상의 것을 받아들일(accept) 수 있다. 또한 키(key)/값(value) 쌍(pairs)의 배열(array)을 매개 변수(parameters)로 허용(accept)할 수 있다.swapi.dev API를 사용하면 매개 변수(parameters)를 starships 엔드 포인트(endpoint)로 보내 검색(search)을 수행(perform)할 수 있다. 이렇게 하려면 검색 기준(search criteria)을 값(value)으로 하는
search
의 키(key)를 사용한다.그러나 그것을 시작하기 전에(before you dive into that),
Starships
라는 새 모델(model)을 설정(set up)해야 다른 응답(response)에서와 마찬가지로 응답(response)을 디코딩(decode)할 수 있다.Decoding Starships
Networking 그룹(group)에서 새 Swift 파일(file)을 만든다. 이름을 Starships.swift로 지정하고 다음 코드를 입력한다:
struct Starships: Decodable { var count: Int var all: [Starship] enum CodingKeys: String, CodingKey { case count case all = "results" } }
Films
과 마찬가지로count
와results
에만 관심이 있다.다음으로 MainTableViewController.swift를 열고,
fetchFilms()
다음에 우주선(starships)을 검색(searching)하기 위한 다음 메서드(method)를 추가한다:func searchStarships(for name: String) { let url = "https://swapi.dev/api/starships" //starship 데이터에 access하는 데 사용할 URL을 설정한다. let parameters: [String: String] = ["search": name] //endpoint로 보낼 key-value parameters를 설정한다. AF.request(url, parameters: parameters) //이전과 같은 request이지만, parameters를 추가했다. .validate() //validate .responseDecodable(of: Starships.self) { response in //decoding //request가 completes되면, guard let starships = response.value else { return } self.items = starships.all //starships 목록을 tableView의 데이터로 할당하고 self.tableView.reloadData() //tableView를 다시 로드한다. } }
이 요청(request)을 실행(executing)하면,
https://swapi.dev/api/starships?search={name}
URL이 생성된다. 여기서{name}
은 전달 된(passed in) 검색 쿼리(search query)이다.Searching for Ships
searchBarSearchButtonClicked(_:)
에 다음 코드를 추가하여 시작한다:guard let shipName = searchBar.text else { return } searchStarships(for: shipName)
이 코드는 검색 창(search bar)에 입력된(typed) 텍스트(text)를 가져오고, 방금 구현(implemented)한 새로운
searchStarships(for:)
메서드(method)를 호출(calls)한다.사용자가 검색(search)을 취소(cancels)하면, 영화 목록(list of films)을 다시 표시(redisplay)한다. API에서 다시 가져올(fetch) 수 있지만, 좋은 설계(design) 방식(practice)이 아니다. 대신(instead) 빠르고(quick) 효율적(efficient)으로 다시 표시할 수 있도록, 영화 목록(list of films)을 캐시(cache)할 수 있다. 영화 목록(list of films)을 캐시(cache)하려면 클래스(class) 맨 위에 다음 속성(property)을 추가한다:
var films: [Film] = []
다음으로
fetchFilms()
의guard
문(statement) 뒤에 다음 코드를 추가한다:self.films = films.all
이렇게하면 나중에 쉽게 접근(access)할 수 있도록, 영화 목록(list for films)을 저장(saves)한다.
이제
searchBarCancelButtonClicked(_:)
에 다음 코드를 추가한다:searchBar.text = nil searchBar.resignFirstResponder() items = films tableView.reloadData()
여기서는 입력한(entered) 모든 검색 텍스트(search text)를 제거(remove)하고,
resignFirstResponder()
를 사용하여 키보드(keyboard)를 숨긴 후 테이블 뷰(table view)를 다시 로드(reload)하면 영화(films)가 다시 표시된다.빌드(build)하고 실행(run)한 후, wing을 검색(search for)한다. 이름(name)이나 모델(model)에 "wing"라는 단어가 포함된 모든 우주선(ships)이 표시된다.
훌륭(great)하지만 완전(complete)하지는 않다. 우주선(ships) 중 하나를 탭(tap)하면, 해당 우주선이 출연(appears)하는 영화 목록(list of films)이 비어(empty) 있다. 이전에 수행한 모든 작업 덕분에 쉽게 수정(fix)할 수 있다. 디버그 콘솔(debug console)에도 엄청난 힌트(huge hint)가 있다.
Display a Ship’s List of Films
DetailViewController.swift를 열고,
fetchList()
를 찾는다(find). 지금은 영화(film)와 관련된(associated with) 목록(list)을 가져 오는(fetch) 방법만 알고 있다. 우주선(starship) 목록을 가져와야(fetch) 한다.switch
문(statement)에서default:
레이블(label) 바로 앞에 다음을 추가한다:case is Starship: fetch(data.listItems, of: Film.self)
이것은 제네릭(generic) 도우미(helper)에게 주어진(given) 우주선(starship)의 영화 목록(list of films)을 가져 오도록(fetch) 지시한다.
빌드(build)하고 실행(run)한 후, 우주선(starship)을 찾아(search for) 선택(select)한다. 우주선(starship)의 세부 정보(details)와 출연(appeared)한 영화 목록(list of films)이 표시된다.
이제 완전히(fully) 작동(functioning)하는 앱(app)이 생겼다.
Where to Go From Here?
이 글(article)의 상단(top) 또는 하단(bottom)에 있는 Download Materials 버튼(button)을 사용하여 완성된(completed) 프로젝트(project)를 다운로드(download)할 수 있다.
앱(app)을 제작(building)하는 동안, Alamofire의 기본(basics)에 대해 많은 것을 배웠다. Alamofire로 URL 문자열(string)만 전송하여 매우 적은 설정(setup)과 기본(basic) 요청(request) 함수(function)만 사용하여 네트워킹(networking)를 호출(calls)하는 방법을 알게 되었다.
또한 검색(searching)과 같은 작업을 위해 매개 변수(parameters)를 전달(sending)하여 더 복잡한(complex) 호출(calls)을 수행하는 방법을 배웠다.
요청 체이닝(request chaining)과 요청 유효성 검사(request validation)를 사용하는 방법, 응답(response)을 JSON으로 변환(convert)하는 방법, 응답 데이터(response data)를 사용자 정의 데이터 모델(custom data model)로 변환(convert)하는 방법에 대해서도 배웠다.
이 글(article)에서는 기본 사항(basics)을 다뤘다. https://github.com/Alamofire/Alamofire의 Alamofire 문서(documentation)를 살펴보면 더 자세히(deeper dive) 알아볼 수 있다.
Alamofire 내부(under the hood)에서 사용하는 Apple의 URLSession에 대해 자세히 알아 보는 것을 강력하게 추천(highly suggest)한다:
이 튜토리얼(tutorial)이 즐거웠기를 바란다. 아래 포럼(forum)의 토론(discussion)에서 이 글(article)에 대한 의견(comments)이나 질문(questions)을 공유(share)할 수 있다.
'Raywenderlich > Articles' 카테고리의 다른 글
Fastlane Tutorial: Actions and Plugins (0) 2020.12.02 Alamofire Tutorial for iOS: Advanced Usage (0) 2020.11.23 Alamofire Tutorial: Getting Started (0) 2020.11.23 Fastlane Tutorial: Getting Started (0) 2020.11.02 SnapKit for iOS: Constraints in a Snap (0) 2020.09.10