[ios] Swift 함수의 비동기 호출에서 데이터 반환

모든 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]>> {

메모

  • getSwift에서 함수 접두사를 사용하지 않는 것이 좋습니다 . 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는 해당 정보를 사용하여 검색 할 수 있습니다. 프로젝트를 통해 정보를 얻는 비동기 방식.

델리게이트
메소드 집합은 델리게이트가 호출 될 때 트리거되며 소스는 메소드 자체를 통해 제공되어야합니다.