How can I get the Data from NSURLSession.sharedSes

2019-09-15 14:12发布

问题:

class PostFOrData {
    let url = NSURL( string: "http://210.61.209.194:8088/SmarttvWebServiceTopmsoApi/GetReadlist")
    var picUrl = NSURL(string : "http://210.61.209.194:8088/SmarttvMedia/img/epi00001.png")
    var responseString : NSString = ""

    func forData() -> NSString {

        let request = NSMutableURLRequest( URL: url!)
        request.HTTPMethod = "POST"
        var s : NSString = ""

        let postString : String = "uid=59"
        request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)

        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
            data, response, error in

            if error != nil {
                println("error=\(error)")
                return
            } else {
                println("response = \(response!)")
                self.responseString = NSString(data: data, encoding: NSUTF8StringEncoding)!
                println("responseString = \(self.responseString)")
            }

        }

        // I want to return NSString here, but I always get nothing

        return self.responseString

    }
}

Anyone know how to get the data from task?

回答1:

You can't return data directly from an asynchronous task.

The solution with Swift 2 is to make a completion handler like this:

class PostFOrData {
    // the completion closure signature is (NSString) -> ()
    func forData(completion: (NSString) -> ()) {
        if let url = NSURL(string: "http://210.61.209.194:8088/SmarttvWebServiceTopmsoApi/GetReadlist") {
            let request = NSMutableURLRequest( URL: url)
            request.HTTPMethod = "POST"
            let postString : String = "uid=59"
            request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
            let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
                data, response, error in
                if let data = data,
                    jsonString = NSString(data: data, encoding: NSUTF8StringEncoding)
                    where error == nil {
                        completion(jsonString)
                } else {
                    print("error=\(error!.localizedDescription)")
                }
            }
            task.resume()
        }
    }
}


let pfd = PostFOrData()

// you call the method with a trailing closure
pfd.forData { jsonString in
    // and here you get the "returned" value from the asynchronous task
    print(jsonString)
}

That way, the completion is only called when the asynchronous task is completed. It is a way to "return" the data without actually using return.

Swift 3 version

class PostFOrData {
    // the completion closure signature is (String) -> ()
    func forData(completion:  @escaping (String) -> ()) {
        if let url = URL(string: "http://210.61.209.194:8088/SmarttvWebServiceTopmsoApi/GetReadlist") {
            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            let postString : String = "uid=59"
            request.httpBody = postString.data(using: String.Encoding.utf8)
            let task = URLSession.shared.dataTask(with: request) {
                data, response, error in
                if let data = data, let jsonString = String(data: data, encoding: String.Encoding.utf8), error == nil {
                    completion(jsonString)
                } else {
                    print("error=\(error!.localizedDescription)")
                }
            }
            task.resume()
        }
    }
}


let pfd = PostFOrData()

// you call the method with a trailing closure
pfd.forData { jsonString in
    // and here you get the "returned" value from the asynchronous task
    print(jsonString)
}


回答2:

There are some very generic requirements that would like every good API Manager to satisfy: will implement a protocol-oriented API Client.

APIClient Initial Interface

protocol APIClient {
   func send(_ request: APIRequest,
              completion: @escaping (APIResponse?, Error?) -> Void) 
}

protocol APIRequest: Encodable {
    var resourceName: String { get }
}

protocol APIResponse: Decodable {
}

Now Please check complete api structure

// ******* 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)
        }
    }

}