iOS 개발자가 되려는 경우 알아야 할 몇 가지 기본 기술이 있습니다. 먼저, 테이블 뷰 생성에 익숙해지는 것이 중요합니다. 둘째, 해당 테이블 보기를 데이터로 채우는 방법을 알아야 합니다. 셋째, API에서 데이터를 가져와 테이블 보기에서 이 데이터를 사용할 수 있으면 좋습니다.
세 번째 요점은 이 기사에서 다룰 내용입니다. Codable
도입 이후 Swift 4에서는 API 호출이 훨씬 쉽습니다. 이전에는 대부분의 사람들이 Alamofire 및 SwiftyJson과 같은 포드를 사용했습니다(여기에서 수행 방법에 대해 읽을 수 있음). 이제 Swift 방식이 기본적으로 훨씬 더 좋으므로 포드를 다운로드할 이유가 없습니다.
API 호출에 자주 사용되는 몇 가지 구성 요소를 살펴보겠습니다. API 호출 방법을 이해하는 데 중요한 부분이므로 이러한 개념을 먼저 다룰 것입니다.
- 완료 핸들러
URLSession
DispatchQueue
- 유지 주기
마침내 우리는 그것을 모두 모을 것입니다. 이 프로젝트를 빌드하기 위해 오픈 소스 Star Wars API를 사용할 것입니다. GitHub에서 내 전체 프로젝트 코드를 볼 수 있습니다.
면책 조항 경고:저는 코딩을 처음 접하고 대부분 독학했습니다. 일부 개념을 잘못 표현했다면 사과드립니다.
완료 핸들러
Pheobe가 고객 서비스 담당자와 통화하기 위해 며칠 동안 전화를 붙이고 있었던 Friends의 에피소드를 기억하십니까? 전화 통화를 시작할 때 Pip이라는 사랑스러운 사람이 다음과 같이 말했다고 상상해 보십시오. "전화해 주셔서 감사합니다. 얼마나 기다려야 할지 모르겠으나 준비가 되면 다시 전화하겠습니다. 당신을 위한." 그렇게 재미있지는 않았을 것입니다. 하지만 Pip은 Pheobe의 완료 핸들러를 제안하고 있습니다.
함수가 완료되는 데 시간이 걸린다는 것을 알고 있을 때 함수에서 완료 핸들러를 사용합니다. 얼마나 오래 걸릴지 모르며 인생이 끝날 때까지 잠시 멈추고 싶지 않습니다. 그래서 당신은 Pip이 답을 줄 준비가 되었을 때 어깨를 두드려달라고 요청합니다. 그렇게 하면 생활을 하고, 심부름을 하고, 책을 읽고, TV를 볼 수 있습니다. 핍이 답이 있는 당신의 어깨를 두드리면 그녀의 답을 받아 사용할 수 있습니다.
이것은 API 호출에서 발생합니다. 서버에 URL 요청을 보내 데이터를 요청합니다. 서버가 데이터를 빨리 반환하기를 바라지만 얼마나 걸릴지 모릅니다. 서버가 데이터를 제공할 때까지 사용자가 참을성 있게 기다리게 하는 대신 완료 핸들러를 사용합니다. 즉, 앱이 꺼지고 페이지의 나머지 부분을 로드하는 등의 다른 작업을 수행하도록 지시할 수 있습니다.
원하는 정보가 있으면 완료 핸들러에게 앱을 탭하라고 지시합니다. 해당 정보가 무엇인지 지정할 수 있습니다. 그렇게 하면 앱이 어깨에 부딪힐 때 완료 처리기에서 정보를 가져와서 작업을 수행할 수 있습니다. 일반적으로 데이터가 사용자에게 표시되도록 테이블 보기를 다시 로드합니다.
다음은 완료 핸들러의 모습에 대한 예입니다. 첫 번째 예는 API 호출 자체를 설정하는 것입니다.
func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
// Setup the variable lotsOfFilms
var lotsOfFilms: [Film]
// Call the API with some code
// Using data from the API, assign a value to lotsOfFilms
// Give the completion handler the variable, lotsOfFilms
completionHandler(lotsOfFilms)
}
이제 fetchFilms
함수를 호출하려고 합니다. . 주의할 사항:
completionHandler
를 참조할 필요가 없습니다. 함수를 호출할 때.completionHandler
만 참조하는 경우 함수 선언 안에 있습니다.- 완료 핸들러는 사용할 데이터를 반환합니다. 위에서 작성한 함수를 기반으로
[Film]
유형의 데이터가 필요하다는 것을 알고 있습니다. . 참조할 수 있도록 데이터의 이름을 지정해야 합니다. 아래에서는films
라는 이름을 사용하고 있습니다. , 하지만randomData
일 수 있습니다. , 또는 내가 원하는 다른 변수 이름.
코드는 다음과 같습니다.
fetchFilms() { (films) in
// Do something with the data the completion handler returns
print(films)
}
URL세션
URLSession
팀의 감독과 같다. 매니저는 혼자서는 아무것도 하지 않는다. 그녀의 임무는 팀의 사람들과 작업을 공유하는 것이며 그들은 작업을 완료할 것입니다. 그녀의 팀은 dataTasks
입니다. . 데이터가 필요할 때마다 상사에게 편지를 쓰고 URLSession.shared.dataTask
을 사용하세요. .
dataTask
목표를 달성하는 데 도움이 되는 다양한 유형의 정보. dataTask
에 정보 제공 초기화라고 합니다. 내 dataTasks
를 이니셜로 지정합니다. URL과 함께. dataTasks
또한 초기화의 일부로 완료 핸들러를 사용합니다. 다음은 예입니다:
let url = URL(string: "https://www.swapi.co/api/films")
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
// your code here
})
task.resume()
dataTasks
완료 핸들러를 사용하고 항상 동일한 유형의 정보를 반환합니다. data
, response
및 error
. 이러한 데이터 유형에 (data, res, err)
과 같은 다른 이름을 지정할 수 있습니다. 또는 (someData, someResponse, someError)
. 관례상 새로운 변수 이름을 사용하는 것보다 분명한 것을 고수하는 것이 가장 좋습니다.
error
부터 시작하겠습니다. . dataTask
error
반환 , 미리 알고 싶을 것입니다. 이는 코드가 오류를 정상적으로 처리하도록 지시할 수 있음을 의미합니다. 또한 데이터를 반환하는 데 오류가 있으므로 데이터를 읽고 작업을 수행하려고 애쓰지 않아도 된다는 의미이기도 합니다.
아래에서는 콘솔에 오류를 인쇄하고 기능을 종료하여 오류를 처리하고 있습니다. 원하는 경우 오류를 처리할 수 있는 다른 방법이 많이 있습니다. 이 데이터가 앱에 얼마나 기본적인지 생각해 보세요. 예를 들어 은행 앱이 있고 이 API 호출이 사용자에게 잔액을 표시하는 경우 사용자에게 "죄송합니다. 지금 문제가 발생했습니다. 나중에 다시."
if let error = error {
print("Error accessing swapi.co: /(error)")
return
}
다음으로 응답을 살펴보겠습니다. 응답을 httpResponse
로 캐스팅할 수 있습니다. . 그렇게 하면 상태 코드를 보고 코드를 기반으로 몇 가지 결정을 내릴 수 있습니다. 예를 들어 상태 코드가 404이면 페이지를 찾을 수 없다는 의미입니다.
아래 코드는 guard
을 사용합니다. 두 가지가 있는지 확인합니다. 둘 다 존재하는 경우 코드가 guard
이후의 다음 명령문으로 계속되도록 허용합니다. 절. 명령문 중 하나가 실패하면 함수를 종료합니다. 이것은 guard
의 일반적인 사용 사례입니다. 절. 가드 절 뒤의 코드는 행복한 날의 흐름(즉, 오류가 없는 쉬운 흐름)을 기대합니다.
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(response)")
return
}
마지막으로 데이터 자체를 처리합니다. error
에 대한 완료 핸들러를 사용하지 않았습니다. 또는 response
. 완료 핸들러가 API의 데이터를 기다리고 있기 때문입니다. 코드의 데이터 부분에 도달하지 않으면 핸들러를 호출할 필요가 없습니다.
데이터의 경우 JSONDecoder
을 사용합니다. 좋은 방법으로 데이터를 구문 분석합니다. 이것은 매우 훌륭하지만 모델을 설정해야 합니다. 우리 모델의 이름은 FilmSummary
입니다. . JSONDecoder
인 경우 처음 사용하는 경우 온라인에서 사용 방법과 Codable
사용 방법을 살펴보세요. . Swift 3일에 비해 Swift 4 이상에서는 정말 간단합니다.
아래 코드에서는 먼저 데이터가 존재하는지 확인합니다. 오류가 없고 이상한 HTTP 응답이 없기 때문에 존재해야 한다고 확신합니다. 둘째, 수신한 데이터를 예상대로 구문 분석할 수 있는지 확인합니다. 할 수 있는 경우 완료 핸들러에 영화 요약을 반환합니다. API에서 반환할 데이터가 없는 경우를 대비하여 빈 배열에 대한 폴백 계획이 있습니다.
if let data = data,
let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
completionHandler(filmSummary.results ?? [])
}
따라서 API 호출의 전체 코드는 다음과 같습니다.
func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
let url = URL(string: domainUrlString + "films/")!
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error {
print("Error with fetching films: \(error)")
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(response)")
return
}
if let data = data,
let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
completionHandler(filmSummary.results ?? [])
}
})
task.resume()
}
유지 주기
주의:저는 유지 주기를 처음으로 이해했습니다! 온라인에서 조사한 내용의 요지는 다음과 같습니다.
유지 주기는 메모리 관리를 이해하는 데 중요합니다. 기본적으로 앱이 더 이상 필요하지 않은 메모리를 정리하기를 원합니다. 나는 이것이 앱의 성능을 더 좋게 만든다고 가정합니다.
Swift가 이를 자동으로 도와주는 많은 방법이 있습니다. 그러나 실수로 앱에 유지 주기를 코딩할 수 있는 방법이 많이 있습니다. 유지 주기는 앱이 항상 특정 코드 조각에 대한 메모리를 유지함을 의미합니다. 일반적으로 서로에 대한 강력한 포인터가 있는 두 가지가 있을 때 발생합니다.
이 문제를 해결하기 위해 사람들은 종종 weak
을 사용합니다. . 코드의 한쪽이 weak
인 경우 , 유지 주기가 없으며 앱에서 메모리를 해제할 수 있습니다.
우리의 목적을 위해 일반적인 패턴은 [weak self]
을 사용하는 것입니다. API를 호출할 때. 이렇게 하면 완료 핸들러가 일부 코드를 반환하면 앱이 메모리를 해제할 수 있습니다.
fetchFilms { [weak self] (films) in
// code in here
}
디스패치 큐
Xcode는 다른 스레드를 사용하여 코드를 병렬로 실행합니다. 다중 스레드의 장점은 다음 작업으로 넘어갈 수 있기 전에 한 작업이 완료될 때까지 기다리지 않아도 된다는 것을 의미합니다. 여기에서 완료 핸들러에 대한 링크를 볼 수 있기를 바랍니다.
이러한 스레드를 디스패치 큐라고도 합니다. API 호출은 하나의 대기열, 일반적으로 백그라운드의 대기열에서 처리됩니다. API 호출에서 데이터를 얻은 후에는 해당 데이터를 사용자에게 보여주고 싶을 것입니다. 즉, 테이블 보기를 새로고침해야 합니다.
테이블 보기는 UI의 일부이며 모든 UI 조작은 기본 디스패치 대기열에서 수행되어야 합니다. 이는 일반적으로 viewDidLoad
의 일부로 뷰 컨트롤러 파일의 어딘가에 있음을 의미합니다. 함수를 사용하려면 테이블 보기를 새로 고치도록 지시하는 약간의 코드가 있어야 합니다.
API의 새로운 데이터가 있을 때만 테이블 보기를 새로 고치기를 원합니다. 즉, 완료 핸들러를 사용하여 어깨를 두드리고 API 호출이 완료되면 알려줍니다. 표를 새로고침하기 전에 탭할 때까지 기다리겠습니다.
코드는 다음과 같습니다.
fetchFilms { [weak self] (films) in
self.films = films
// Reload the table view using the main dispatch queue
DispatchQueue.main.async {
tableView.reloadData()
}
}
viewDidLoad 대 viewDidAppear
마지막으로 fetchfilms
을 호출할 위치를 결정해야 합니다. 기능. API의 데이터를 사용하는 뷰 컨트롤러 내부에 있을 것입니다. 이 API 호출을 할 수 있는 두 가지 분명한 위치가 있습니다. 하나는 viewDidLoad
안에 있습니다. 다른 하나는 viewDidAppear
안에 있습니다. .
앱에 대한 두 가지 다른 상태입니다. 내 이해는 viewDidLoad
입니다. 전경에서 해당 뷰를 처음 로드할 때 호출됩니다. viewDidAppear
예를 들어 보기로 돌아가기 위해 뒤로 버튼을 누르면 해당 보기로 돌아올 때마다 호출됩니다.
사용자가 해당 보기로 이동하는 시간과 해당 보기에서 나오는 시간 사이에 데이터가 변경될 것으로 예상되는 경우 API 호출을 viewDidAppear
에 넣을 수 있습니다. . 그러나 거의 모든 앱에 대해 viewDidLoad
라고 생각합니다. 충분하다. Apple은 viewDidAppear
을 권장합니다. 모든 API 호출에 대해, 하지만 그것은 과잉처럼 보입니다. 필요한 API 호출을 더 많이 하기 때문에 앱의 성능이 저하될 것이라고 생각합니다.
모든 단계 결합
먼저 API를 호출하는 함수를 작성합니다. 위는 fetchFilms
입니다. . 여기에는 관심 있는 데이터를 반환하는 완료 핸들러가 있습니다. 제 예에서 완료 핸들러는 영화 배열을 반환합니다.
두 번째:뷰 컨트롤러에서 이 함수를 호출합니다. API의 데이터를 기반으로 보기를 업데이트하려고 하기 때문에 여기에서 이 작업을 수행합니다. 내 예에서는 API가 데이터를 반환하면 테이블 보기를 새로 고칩니다.
세 번째:뷰 컨트롤러에서 함수를 호출할 위치를 결정합니다. 내 예에서는 viewDidLoad
로 호출합니다. .
넷째:API의 데이터로 무엇을 할 것인지 결정하십시오. 내 예에서는 테이블 보기를 새로 고칩니다.
NetworkManager.swift
내부 (이 함수는 원하는 경우 뷰 컨트롤러에서 정의할 수 있지만 저는 MVVM 패턴을 사용하고 있습니다).
func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
let url = URL(string: domainUrlString + "films/")!
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error {
print("Error with fetching films: \(error)")
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(response)")
return
}
if let data = data,
let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
completionHandler(filmSummary.results ?? [])
}
})
task.resume()
}
FilmsViewController.swift
내부 :
final class FilmsViewController: UIViewController {
private var films: [Film]?
override func viewDidLoad() {
super.viewDidLoad()
NetworkManager().fetchFilms { [weak self] (films) in
self?.films = films
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
// other code for the view controller
}
맙소사, 우리가 해냈다! 함께해주셔서 감사합니다.