모든 REST 요청과 응답을 처리하는 Swift 프로젝트에서 유틸리티 클래스를 만들었습니다. 코드를 테스트 할 수 있도록 간단한 REST API를 구축했습니다. NSArray를 반환해야하는 클래스 메서드를 만들었지 만 API 호출이 비동기이기 때문에 비동기 호출 내부의 메서드에서 반환해야합니다. 문제는 비동기가 무효를 반환한다는 것입니다. Node에서 이것을하고 있다면 JS promise를 사용할 것이지만 Swift에서 작동하는 솔루션을 찾을 수 없습니다.
import Foundation
class Bookshop {
class func getGenres() -> NSArray {
println("Hello inside getGenres")
let urlPath = "http://creative.coventry.ac.uk/~bookshop/v1.1/index.php/genre/list"
println(urlPath)
let url: NSURL = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
var resultsArray:NSArray!
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error) {
println(error.localizedDescription)
}
var err: NSError?
var options:NSJSONReadingOptions = NSJSONReadingOptions.MutableContainers
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: options, error: &err) as NSDictionary
if(err != nil) {
println("JSON Error \(err!.localizedDescription)")
}
//NSLog("jsonResults %@", jsonResult)
let results: NSArray = jsonResult["genres"] as NSArray
NSLog("jsonResults %@", results)
resultsArray = results
return resultsArray // error [anyObject] is not a subType of 'Void'
})
task.resume()
//return "Hello World!"
// I want to return the NSArray...
}
}
답변
콜백을 전달하고 비동기 호출 내에서 콜백을 호출 할 수 있습니다.
같은 것 :
class func getGenres(completionHandler: (genres: NSArray) -> ()) {
...
let task = session.dataTaskWithURL(url) {
data, response, error in
...
resultsArray = results
completionHandler(genres: resultsArray)
}
...
task.resume()
}
그런 다음이 메서드를 호출합니다.
override func viewDidLoad() {
Bookshop.getGenres {
genres in
println("View Controller: \(genres)")
}
}
답변
Swiftz는 이미 Promise의 기본 구성 요소 인 Future를 제공합니다. 퓨처는 실패 할 수없는 약속입니다 (여기서 모든 용어는 스칼라 해석을 기반으로 하며 약속은 모나드입니다 ).
https://github.com/maxpow4h/swiftz/blob/master/swiftz/Future.swift
결국 완전한 스칼라 스타일의 약속으로 확장되기를 바랍니다.
귀하의 특별한 경우에는 아마도 Result<[Book]>
( Alexandros Salazar의 버전을Result
기반으로) 만들 것입니다 . 그러면 메서드 서명은 다음과 같습니다.
class func fetchGenres() -> Future<Result<[Book]>> {
메모
get
Swift에서 함수 접두사를 사용하지 않는 것이 좋습니다 . ObjC와의 특정 종류의 상호 운용성을 깨뜨릴 것입니다.Book
결과를 .txt 파일로 반환하기 전에 객체 까지 파싱하는 것이 좋습니다Future
. 이 시스템이 실패 할 수있는 몇 가지 방법이 있으며, .NET Framework로 정리하기 전에 이러한 모든 사항을 확인하는 것이 훨씬 더 편리합니다Future
. 를[Book]
처리하는 것보다 Swift 코드의 나머지 부분에서NSArray
.
답변
기본 패턴은 완료 핸들러 클로저를 사용하는 것입니다.
예를 들어, 곧 나올 Swift 5에서는 다음을 사용합니다 Result
.
func fetchGenres(completion: @escaping (Result<[Genre], Error>) -> Void) {
...
URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
return
}
// parse response here
let results = ...
DispatchQueue.main.async {
completion(.success(results))
}
}.resume()
}
그리고 당신은 그것을 그렇게 부를 것입니다.
fetchGenres { results in
switch results {
case .success(let genres):
// use genres here, e.g. update model and UI
case .failure(let error):
print(error.localizedDescription)
}
}
// but don’t try to use genres here, as the above runs asynchronously
위에서는 모델 및 UI 업데이트를 단순화하기 위해 완료 핸들러를 메인 큐로 다시 디스패치하고 있습니다. 일부 개발자는이 관행에 예외를두고 사용 된 대기열을 URLSession
사용하거나 자체 대기열을 사용합니다 (호출자가 결과 자체를 수동으로 동기화해야 함).
그러나 그것은 여기서 중요한 것이 아닙니다. 핵심 문제는 완료 핸들러를 사용하여 비동기 요청이 완료 될 때 실행할 코드 블록을 지정하는 것입니다.
오래된 Swift 4 패턴은 다음과 같습니다.
func fetchGenres(completion: @escaping ([Genre]?, Error?) -> Void) {
...
URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error {
DispatchQueue.main.async {
completion(nil, error)
}
return
}
// parse response here
let results = ...
DispatchQueue.main.async {
completion(results, error)
}
}.resume()
}
그리고 당신은 그것을 그렇게 부를 것입니다.
fetchGenres { genres, error in
guard let genres = genres, error == nil else {
// handle failure to get valid response here
return
}
// use genres here
}
// but don’t try to use genres here, as the above runs asynchronously
위에서 저는 NSArray
사용 을 중단했습니다 (브리지 된 Objective-C 유형 은 더 이상 사용하지 않음 ). 나는 우리가 Genre
유형 을 가지고 있다고 가정하고 아마도 그것을 디코딩하는 데 사용하는 JSONDecoder
것이 아니라 JSONSerialization
. 그러나이 질문에는 여기에서 세부 사항을 다루기위한 기본 JSON에 대한 정보가 충분하지 않았기 때문에 핵심 문제인 클로저를 완료 핸들러로 사용하는 것을 피하기 위해 생략했습니다.
답변
스위프트 4.0
비동기 요청-응답의 경우 완료 처리기를 사용할 수 있습니다. 아래에서 완료 핸들 패러다임을 사용하여 솔루션을 수정했습니다.
func getGenres(_ completion: @escaping (NSArray) -> ()) {
let urlPath = "http://creative.coventry.ac.uk/~bookshop/v1.1/index.php/genre/list"
print(urlPath)
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
let results = jsonResult["genres"] as! NSArray
print(results)
completion(results)
}
} catch {
//Catch Error here...
}
}
task.resume()
}
이 함수는 아래와 같이 호출 할 수 있습니다.
getGenres { (array) in
// Do operation with array
}
답변
@Alexey Globchastyy의 답변의 Swift 3 버전 :
class func getGenres(completionHandler: @escaping (genres: NSArray) -> ()) {
...
let task = session.dataTask(with:url) {
data, response, error in
...
resultsArray = results
completionHandler(genres: resultsArray)
}
...
task.resume()
}
답변
나는 당신이 여전히 이것에 집착하지 않기를 바라지 만, 짧은 대답은 당신이 Swift에서 이것을 할 수 없다는 것입니다.
다른 방법은 필요한 데이터가 준비되는 즉시 제공하는 콜백을 반환하는 것입니다.
답변
콜백 함수를 생성하는 방법은 3 가지가 있습니다. 1. 완료 핸들러 2. 알림 3. 델리게이트
Completion Handler
내부 블록 집합이 실행되고 소스가 사용 가능할 때 반환되며, Handler는 응답이 올 때까지 대기하여 UI가 업데이트 될 수 있도록합니다.
알림
정보 묶음은 모든 앱에서 트리거되며 Listner는 해당 정보를 사용하여 검색 할 수 있습니다. 프로젝트를 통해 정보를 얻는 비동기 방식.
델리게이트
메소드 집합은 델리게이트가 호출 될 때 트리거되며 소스는 메소드 자체를 통해 제공되어야합니다.