Swift 함수의 비동기 호출에서 데이터 반환
저는 스위프트 프로젝트에서 REST 요청과 응답을 모두 처리하는 유틸리티 클래스를 만들었습니다.간단한 REST API를 구축하여 코드를 테스트할 수 있습니다.NSray를 반환해야 하는 클래스 메소드를 만들었지만 API 호출이 비동기이기 때문에 비동기 호출 내부 메소드에서 반환해야 합니다.문제는 비동기가 공백을 반환한다는 것입니다.노드에서 이 작업을 수행하는 경우 JS 약속을 사용할 것이지만 스위프트에서 작동하는 솔루션을 찾을 수 없습니다.
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)")
}
}
에 Swift 5.5(iOS 15, macOS 12)를입니다.async
-await
패턴:
func fetchGenres() async throws -> [Genre] {
…
let (data, _) = try await URLSession.shared.dataTask(for: request)
return try JSONDecoder().decode([Genre].self, from: data)
}
우리는 이것을 이렇게 부릅니다.
let genres = try await fetchGenres()
async
-await
구문은 아래의 제 원래 답변에서 설명된 전통적인 완성 처리기 패턴보다 훨씬 간결하고 자연스럽습니다.
자세한 내용은 Swift에서 비동기/대기 충족을 참조하십시오.
역사적 패턴은 완료 핸들러 클로징을 사용하는 것입니다.
예를 들어, 우리는 종종 다음을 사용합니다.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 .failure(let error):
print(error.localizedDescription)
case .success(let genres):
// use `genres` here, e.g. update model and UI
}
}
// but don’t try to use `genres` here, as the above runs asynchronously
참고로 위에서 모델과 UI 업데이트를 단순화하기 위해 완료 핸들러를 메인 큐로 다시 보냅니다.은 이 합니다 합니다.URLSession
사용되거나 자체 큐를 사용합니다(결과를 수동으로 동기화하려면 호출자를 requiring하십시오).
하지만 그건 중요한 게 아닙니다.핵심 문제는 완료 핸들러를 사용하여 비동기 요청이 수행될 때 실행할 코드 블록을 지정하는 것입니다.
에서 저는 했습니다, 로의 했습니다.NSArray
(이러한 브리지형 Objective-C 유형은 더 이상 사용하지 않습니다.)제 생각엔 우리가 한 일이Genre
한 것으로 됩니다.JSONDecoder
에,JSONSerialization
요 그러나 은 기본 에 대한 하지 않아하는 것을 하기 위해 그러나 이 질문은 기본 JSON에 대한 정보가 충분하지 않아서 핵심 문제인 완료 처리기로 폐쇄를 사용하는 것을 피하기 위해 생략했습니다.
스위프트즈는 이미 약속의 기본 구성 요소인 미래를 제공하고 있습니다.미래는 실패할 수 없는 약속입니다(여기서 모든 용어는 스칼라 해석에 기반을 두고 있으며, 여기서 약속은 모나드입니다).
https://github.com/maxpow4h/swiftz/blob/master/swiftz/Future.swift
궁극적으로 완전한 Scala 스타일의 Promise로 확장되기를 바랍니다. (어느 시점에서는 제가 직접 작성할 수도 있고, 다른 PR들도 환영할 것입니다. Future가 이미 구축되어 있기 때문에 그렇게 어렵지 않습니다.)
아마 .Result<[Book]>
(Alexandros Salazar 의 버전을 기준으로 함.그러면 메소드 서명은 다음과 같습니다.
class func fetchGenres() -> Future<Result<[Book]>> {
메모들
- 다음으로 함수를 접두사로 지정하는 것은 권장하지 않습니다.
get
스위프트에서특정한 것입니다.그것은 ObjC와의 특정한 종류의 상호운용성을 깨뜨릴 것입니다. - 는 까지 파싱하는 을 추천합니다.
Book
에 를 선택합니다.Future
이 은 몇 할 수 , 에 이 합니다.Future
중입니다.[Book]
다를 의 나머지 이 훨씬 .NSArray
.
스위프트 4.0
비동기 Request-Response의 경우 완료 처리기를 사용할 수 있습니다.아래를 보세요. 완성 핸들 패러다임으로 솔루션을 수정했습니다.
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
}
스위프트 3 버전 @Alexey Globchasty 답변:
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 5.5, 비동기식/대기 기반 솔루션
원래 포스터에서 제공한 원래 테스트 URL이 더 이상 작동하지 않아 변경해야 했습니다.이 솔루션은 제가 발견한 농담 API를 기반으로 합니다.당 API하지만 String합니다 ([String]
.), 합니다.
class Bookshop {
class func getGenres() async -> [String] {
print("Hello inside getGenres")
let urlPath = "https://geek-jokes.sameerkumar.website/api?format=json"
print(urlPath)
let url = URL(string: urlPath)!
let session = URLSession.shared
typealias Continuation = CheckedContinuation<[String], Never>
let genres = await withCheckedContinuation { (continuation: Continuation) in
let task = session.dataTask(with: url) { data, response, error in
print("Task completed")
var result: [String] = []
defer {
continuation.resume(returning: result)
}
if let error = error {
print(error.localizedDescription)
return
}
guard let data = data else {
return
}
do {
let jsonResult = try JSONSerialization.jsonObject(with: data, options: [.mutableContainers])
print("jsonResult is \(jsonResult)")
if let joke = (jsonResult as? [String: String])?["joke"] {
result = [joke]
}
} catch {
print("JSON Error \(error.localizedDescription)")
print("data was \(String(describing: String(data: data, encoding: .utf8)))")
return
}
}
task.resume()
}
return genres
}
}
async {
let final = await Bookshop.getGenres()
print("Final is \(final)")
}
withCheckedContinuation
async
함수는 실제로 별도의 작업/thread에서 실행됩니다.
아직도 이 일에 매달리지 않았으면 좋겠는데, 단답형은 스위프트에서는 이 일을 할 수 없다는 것입니다.
필요한 데이터가 준비되는 대로 콜백을 반환하는 방법도 있습니다.
콜백 기능을 만드는 방법에는 3가지가 있습니다. 즉, 1.완료 핸들러 2.알림 3.대의원
완료 핸들러 블록 내부 세트는 소스가 사용 가능할 때 실행되어 반환됩니다. 핸들러는 응답이 올 때까지 기다렸다가 UI를 업데이트할 수 있습니다.
알림 앱 전체에서 많은 정보가 트리거되며 리스트너는 해당 정보를 사용하여 n을 검색할 수 있습니다.프로젝트 전반에 걸쳐 정보를 얻는 비동기식 방법.
딜러 호출 시 딜러 메소드 세트가 트리거됩니다. 메소드 자체를 통해 소스를 제공해야 합니다.
스위프트 5.5:
TL;DR: Swift 5.5는 아직 출시되지 않았습니다(작성 당시).swift 5.5를 사용하려면 swift toolchain development snapshot을 여기서 다운로드하고 컴파일러 플래그를 추가하면 됩니다.-Xfrontend -enable-experimental-concurrency
. 여기서 자세히 보기
는 를 할 수 .async/await
특징.
로서의 .async
다 에서 수술을 .withUnsafeThrowingContinuation
다음과 같은 블록.
class Bookshop {
class func getGenres() async throws -> NSArray {
print("Hello inside getGenres")
let urlPath = "http://creative.coventry.ac.uk/~bookshop/v1.1/index.php/genre/list"
print(urlPath)
let url = URL(string: urlPath)!
let session = URLSession.shared
return try await withUnsafeThrowingContinuation { continuation in
let task = session.dataTask(with: url, completionHandler: {data, response, error -> Void in
print("Task completed")
if(error != nil) {
print(error!.localizedDescription)
continuation.resume(throwing: error!)
return
}
do {
let jsonResult = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String: Any]
let results: NSArray = jsonResult!["genres"] as! NSArray
continuation.resume(returning: results)
} catch {
continuation.resume(throwing: error)
}
})
task.resume()
}
}
}
그리고 이 기능을 이렇게 부를 수 있습니다.
@asyncHandler
func check() {
do {
let genres = try await Bookshop.getGenres()
print("Result: \(genres)")
} catch {
print("Error: \(error)")
}
}
전화를 걸 때는 이 사실을 명심해야 합니다.Bookshop.getGenres
, method는 method, caller method는다이어야 async
s로 됩니다.@asyncHandler
self.urlSession.dataTask(with: request, completionHandler: { (data, response, error) in
self.endNetworkActivity()
var responseError: Error? = error
// handle http response status
if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode > 299 , httpResponse.statusCode != 422 {
responseError = NSError.errorForHTTPStatus(httpResponse.statusCode)
}
}
var apiResponse: Response
if let _ = responseError {
apiResponse = Response(request, response as? HTTPURLResponse, responseError!)
self.logError(apiResponse.error!, request: request)
// Handle if access token is invalid
if let nsError: NSError = responseError as NSError? , nsError.code == 401 {
DispatchQueue.main.async {
apiResponse = Response(request, response as? HTTPURLResponse, data!)
let message = apiResponse.message()
// Unautorized access
// User logout
return
}
}
else if let nsError: NSError = responseError as NSError? , nsError.code == 503 {
DispatchQueue.main.async {
apiResponse = Response(request, response as? HTTPURLResponse, data!)
let message = apiResponse.message()
// Down time
// Server is currently down due to some maintenance
return
}
}
} else {
apiResponse = Response(request, response as? HTTPURLResponse, data!)
self.logResponse(data!, forRequest: request)
}
self.removeRequestedURL(request.url!)
DispatchQueue.main.async(execute: { () -> Void in
completionHandler(apiResponse)
})
}).resume()
신속하게 콜백을 달성하는 방법은 크게 세 가지가 있습니다.
폐쇄/완료 핸들러
대의원
알림
관찰자는 비동기 작업이 완료된 후 알림을 받는 데 사용할 수도 있습니다.
프로토콜 중심의 API 클라이언트를 구현하는 모든 우수한 API Manager가 충족하기를 원하는 몇 가지 매우 일반적인 요구 사항이 있습니다.
AP 클라이언트 초기 인터페이스
protocol APIClient {
func send(_ request: APIRequest,
completion: @escaping (APIResponse?, Error?) -> Void)
}
protocol APIRequest: Encodable {
var resourceName: String { get }
}
protocol APIResponse: Decodable {
}
이제 전체 api 구조를 확인해주세요.
// ******* This is API Call Class *****
public typealias ResultCallback<Value> = (Result<Value, Error>) -> Void
/// Implementation of a generic-based API client
public class APIClient {
private let baseEndpointUrl = URL(string: "irl")!
private let session = URLSession(configuration: .default)
public init() {
}
/// Sends a request to servers, calling the completion method when finished
public func send<T: APIRequest>(_ request: T, completion: @escaping ResultCallback<DataContainer<T.Response>>) {
let endpoint = self.endpoint(for: request)
let task = session.dataTask(with: URLRequest(url: endpoint)) { data, response, error in
if let data = data {
do {
// Decode the top level response, and look up the decoded response to see
// if it's a success or a failure
let apiResponse = try JSONDecoder().decode(APIResponse<T.Response>.self, from: data)
if let dataContainer = apiResponse.data {
completion(.success(dataContainer))
} else if let message = apiResponse.message {
completion(.failure(APIError.server(message: message)))
} else {
completion(.failure(APIError.decoding))
}
} catch {
completion(.failure(error))
}
} else if let error = error {
completion(.failure(error))
}
}
task.resume()
}
/// Encodes a URL based on the given request
/// Everything needed for a public request to api servers is encoded directly in this URL
private func endpoint<T: APIRequest>(for request: T) -> URL {
guard let baseUrl = URL(string: request.resourceName, relativeTo: baseEndpointUrl) else {
fatalError("Bad resourceName: \(request.resourceName)")
}
var components = URLComponents(url: baseUrl, resolvingAgainstBaseURL: true)!
// Common query items needed for all api requests
let timestamp = "\(Date().timeIntervalSince1970)"
let hash = "\(timestamp)"
let commonQueryItems = [
URLQueryItem(name: "ts", value: timestamp),
URLQueryItem(name: "hash", value: hash),
URLQueryItem(name: "apikey", value: "")
]
// Custom query items needed for this specific request
let customQueryItems: [URLQueryItem]
do {
customQueryItems = try URLQueryItemEncoder.encode(request)
} catch {
fatalError("Wrong parameters: \(error)")
}
components.queryItems = commonQueryItems + customQueryItems
// Construct the final URL with all the previous data
return components.url!
}
}
// ****** API Request Encodable Protocol *****
public protocol APIRequest: Encodable {
/// Response (will be wrapped with a DataContainer)
associatedtype Response: Decodable
/// Endpoint for this request (the last part of the URL)
var resourceName: String { get }
}
// ****** This Results type Data Container Struct ******
public struct DataContainer<Results: Decodable>: Decodable {
public let offset: Int
public let limit: Int
public let total: Int
public let count: Int
public let results: Results
}
// ***** API Errro Enum ****
public enum APIError: Error {
case encoding
case decoding
case server(message: String)
}
// ****** API Response Struct ******
public struct APIResponse<Response: Decodable>: Decodable {
/// Whether it was ok or not
public let status: String?
/// Message that usually gives more information about some error
public let message: String?
/// Requested data
public let data: DataContainer<Response>?
}
// ***** URL Query Encoder OR JSON Encoder *****
enum URLQueryItemEncoder {
static func encode<T: Encodable>(_ encodable: T) throws -> [URLQueryItem] {
let parametersData = try JSONEncoder().encode(encodable)
let parameters = try JSONDecoder().decode([String: HTTPParam].self, from: parametersData)
return parameters.map { URLQueryItem(name: $0, value: $1.description) }
}
}
// ****** HTTP Pamater Conversion Enum *****
enum HTTPParam: CustomStringConvertible, Decodable {
case string(String)
case bool(Bool)
case int(Int)
case double(Double)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
self = .string(string)
} else if let bool = try? container.decode(Bool.self) {
self = .bool(bool)
} else if let int = try? container.decode(Int.self) {
self = .int(int)
} else if let double = try? container.decode(Double.self) {
self = .double(double)
} else {
throw APIError.decoding
}
}
var description: String {
switch self {
case .string(let string):
return string
case .bool(let bool):
return String(describing: bool)
case .int(let int):
return String(describing: int)
case .double(let double):
return String(describing: double)
}
}
}
/// **** This is your API Request Endpoint Method in Struct *****
public struct GetCharacters: APIRequest {
public typealias Response = [MyCharacter]
public var resourceName: String {
return "characters"
}
// Parameters
public let name: String?
public let nameStartsWith: String?
public let limit: Int?
public let offset: Int?
// Note that nil parameters will not be used
public init(name: String? = nil,
nameStartsWith: String? = nil,
limit: Int? = nil,
offset: Int? = nil) {
self.name = name
self.nameStartsWith = nameStartsWith
self.limit = limit
self.offset = offset
}
}
// *** This is Model for Above Api endpoint method ****
public struct MyCharacter: Decodable {
public let id: Int
public let name: String?
public let description: String?
}
// ***** These below line you used to call any api call in your controller or view model ****
func viewDidLoad() {
let apiClient = APIClient()
// A simple request with no parameters
apiClient.send(GetCharacters()) { response in
response.map { dataContainer in
print(dataContainer.results)
}
}
}
이는 도움이 될 수 있는 작은 사용 사례입니다.
func testUrlSession(urlStr:String, completionHandler: @escaping ((String) -> Void)) {
let url = URL(string: urlStr)!
let task = URLSession.shared.dataTask(with: url){(data, response, error) in
guard let data = data else { return }
if let strContent = String(data: data, encoding: .utf8) {
completionHandler(strContent)
}
}
task.resume()
}
함수를 호출하는 동안:-
testUrlSession(urlStr: "YOUR-URL") { (value) in
print("Your string value ::- \(value)")
}
언급URL : https://stackoverflow.com/questions/25203556/returning-data-from-async-call-in-swift-function
'itsource' 카테고리의 다른 글
우커머스 신규 고객 관리자 알림 이메일 (0) | 2023.10.09 |
---|---|
database-name에서 대시를 사용하여 bash를 통해 데이터베이스 생성 (0) | 2023.10.09 |
DateTime::createFromFormat을 통해 '2016.04.30 PM 7:30'의 PHP DateTime 인스턴스를 만드는 데 적합한 형식은 무엇입니까? (0) | 2023.10.04 |
dplyr: 열을 알파벳순으로 R 순서 지정 (0) | 2023.10.04 |
API에서 아이폰 IDFA를 검색하는 방법은? (0) | 2023.10.04 |