Data fetched via postman and URLSession is different in here http://173.249.20.137:9000/apiapp/coupon - swift

http://173.249.20.137:9000/apiapp/coupon GET method .
when I request via URLSession and Postman I get two different results. Actually postman data is correct, but the URL session has always the same response whether I add or delete data it is not going to update. Can anybody please give a look. if it is happening with me only or something wrong at the backend server.
I have tested requesting data with URLSession.shared and postman.
I actually like to have the data I get via postman through URLSession request too.
func getAvailableCoupons(urlString:String, completion: #escaping (_
product: Any, _ error: Error?) -> Void){
guard let url = URL(string: urlString) else {return}
let task = URLSession.shared.dataTask(with: url) { (data, response,
error) in
let jsonDecoder = JSONDecoder()
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Response Error")
return }
let statusCode = (response as! HTTPURLResponse).statusCode
let responseJSON = try? JSONSerialization.jsonObject(with: dataResponse, options: [])
if let responseJSON = responseJSON as? [String: Any] {
if statusCode == 200 {
do {
let jsonData = try JSONSerialization.data(withJSONObject: responseJSON, options: [])
let responseData = try jsonDecoder.decode(CoupensResponseModel.self, from:jsonData)
completion(responseData, nil)
} catch let error {
print("error when parshing json response \(error)")
completion(error, nil )
}
} else if statusCode == 404{
completion(" 404 not found", nil )
} else {
print("fatal error \(error?.localizedDescription ?? "big errror")")
}
}
}
task.resume()
}
import Foundation
// MARK: - CoupensResponseModel
struct CoupensResponseModel: Codable {
let couponDetails: [CouponDetail]?
enum CodingKeys: String, CodingKey {
case couponDetails = "coupon_details"
}
}
// MARK: - CouponDetail
struct CouponDetail: Codable {
let id: Int?
let vouchersusageSet: [VouchersusageSet]?
let couponCode: String?
let minimumSpend: Int?
let expiryDate, createdDate: String?
let discountPrice, discountPercent: Int?
let discountBasis: DiscountBasis?
let couponImage: String?
let couponType: String?
enum CodingKeys: String, CodingKey {
case id
case vouchersusageSet = "vouchersusage_set"
case couponCode = "coupon_code"
case minimumSpend = "minimum_spend"
case expiryDate = "expiry_date"
case createdDate = "created_date"
case discountPrice = "discount_price"
case discountPercent = "discount_percent"
case discountBasis = "discount_basis"
case couponImage = "coupon_image"
case couponType = "coupon_type"
}
}
enum DiscountBasis: String, Codable {
case amount = "amount"
case percent = "percent"
}
// MARK: - VouchersusageSet
struct VouchersusageSet: Codable {
let id: Int?
let itemObj: ItemObj?
let voucherObj: Int?
let sectionObj, categoryObj: Int?
}
// MARK: - ItemObj
struct ItemObj: Codable {
let id: Int?
let code, productName: String?
let productPrice, discountedPrice: Int?
let productDescription, itemImage: String?
let categoryObj: CouponCategoryObj?
enum CodingKeys: String, CodingKey {
case id, code
case productName = "product_name"
case productPrice = "product_price"
case discountedPrice = "discounted_price"
case productDescription = "product_description"
case itemImage = "item_image"
case categoryObj
}
}
// MARK: - CouponCategoryObj
struct CouponCategoryObj: Codable {
let id: Int?
let categoryCode, categoryName: String?
let categoryImage: CouponJSONNull?
let sectionObj: Int?
enum CodingKeys: String, CodingKey {
case id
case categoryCode = "category_code"
case categoryName = "category_name"
case categoryImage = "category_image"
case sectionObj
}
}
// MARK: - Encode/decode helpers
class CouponJSONNull: Codable, Hashable {
public static func == (lhs: CouponJSONNull, rhs: CouponJSONNull) ->
Bool {
return true
}
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) {
// No-op
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(CouponJSONNull.self,
DecodingError.Context(codingPath: decoder.codingPath,
debugDescription:
"Wrong type for CouponJSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}

Try the following method
let headers = [
"Cache-Control": "no-cache",
]
let request = NSMutableURLRequest(url: NSURL(string: "http://173.249.20.137:9000/apiapp/coupon")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error)
} else {
let string = String(data: data!, encoding: .utf8) ?? ""
print(string)
}
})
dataTask.resume()

Please look on response both are same.
Postman response :
https://jsoneditoronline.org/?id=7b94ef69a3344164aa4a96423fdbf9db
Code response :
https://jsoneditoronline.org/?id=6e5a7d221d9c4c818f1d46fc893031fe

Related

Encoding string data back to JSON is giving an error

I've removed jsonCallback ( and ) from the URL https://www.apple.com/support/systemstatus/data/developer/system_status_en_US.js using the below.
var dataString = String(data: data, encoding: .utf8)
dataString = dataString?.replacingOccurrences(of: "jsonCallback(", with: "")
dataString = dataString?.replacingOccurrences(of: ");", with: "")
let json = dataString?.data(using: .utf8)
let jsonData = try JSONEncoder().encode(json)
The error I'm getting back
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found a string/data instead.", underlyingError: nil))
I can't find where the mismatch is happening at because when I look at dataString and piece it back together the JSON decoding model appears to match.
Here's the full code:
func fetchSystemStatus() async -> [SystemStatus] {
guard let url = URL(string: systemStatusURL) else {
return []
}
do {
let (data, response) = try await URLSession.shared.data(from: url)
// This is commented out data to try and gather developer system status
var dataString = String(data: data, encoding: .utf8)
dataString = dataString?.replacingOccurrences(of: "jsonCallback(", with: "")
dataString = dataString?.replacingOccurrences(of: ");", with: "")
let json = dataString?.data(using: .utf8)
let jsonData = try JSONEncoder().encode(json)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
print("\(#function) \(response)")
return []
}
let statusData = try JSONDecoder().decode(SystemStatus.self, from: jsonData)
return [statusData]
} catch {
print("\(#function) \(error)")
return []
}
}
Model:
// MARK: - SystemStatus
struct SystemStatus: Codable {
let services: [Service]
enum CodingKeys: String, CodingKey {
case services = "services"
}
}
// MARK: - Service
struct Service: Codable, Identifiable {
let id = UUID()
let redirectURL: String?
let events: [Event]
let serviceName: String
enum CodingKeys: String, CodingKey {
case redirectURL = "redirectUrl"
case events = "events"
case serviceName = "serviceName"
}
}
// MARK: - Event
struct Event: Codable {
let usersAffected: String
let epochStartDate: Int
let epochEndDate: Int
let messageID: String
let statusType: String
let datePosted: String
let startDate: String
let endDate: String
let affectedServices: [String]?
let eventStatus: String
let message: String
enum CodingKeys: String, CodingKey {
case usersAffected = "usersAffected"
case epochStartDate = "epochStartDate"
case epochEndDate = "epochEndDate"
case messageID = "messageId"
case statusType = "statusType"
case datePosted = "datePosted"
case startDate = "startDate"
case endDate = "endDate"
case affectedServices = "affectedServices"
case eventStatus = "eventStatus"
case message = "message"
}
}
This should work:
let statusData = try JSONDecoder().decode(SystemStatus.self, from: Data(json!))
or
let statusData = try JSONDecoder().decode(SystemStatus.self, from: Data(dataString!.utf8))
What's your issue:
let jsonData = try JSONEncoder().encode(json)
But json here is Data, so if you call JSONEncoder on it, by default, it will use Base64, so it won't be like the JSON you expect.
But json already is correct.

Why can't I change the instance variable inside this session.dataTask()?

I tried to directly get the data recieved from a URLsession updated into the instance variables. Tried the code below in playgroud, I can see until the self.cityName = weatherdecoded.name the code seems working fine, but the self.cityName which intended to be the instance's variable didn't got updated. The results are nils. Hope to understand the reason, what is the mistake i made. Thanks!
import UIKit
class WeatherManager {
var cityName: String?
var conditionID: Int?
var temp: Double?
func fetchData(cityName: String) {
let session = URLSession(configuration: .default)
let urlStr = "https://api.openweathermap.org/data/2.5/weather?units=metric&appid=8da179fa1c83749056ec6a5385cabb04&q=" + cityName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
print(urlStr)
let url = URL(string: urlStr)!
let weatherDataSession = session.dataTask(with: url, completionHandler: getdata(data:response:error:))
weatherDataSession.resume()
}
func getdata(data: Data?, response: URLResponse?, error: Error?) {
if error != nil {
print(error!)
return
}
if let safedata = data {
let decoder = JSONDecoder()
do {
let weatherdecoded = try decoder.decode(Weatherdata.self, from: safedata)
self.cityName = weatherdecoded.name
self.conditionID = weatherdecoded.weather[0].id
self.temp = weatherdecoded.main.temp
} catch {
print(error)
}
}
}
}
struct Weatherdata: Decodable {
let weather : [Weather]
let main: Main
let name: String
}
struct Weather: Decodable {
let id: Int
let description: String
}
struct Main: Decodable {
let temp: Double
}
let weathermanager = WeatherManager()
weathermanager.fetchData(cityName: "beijing")
print(weathermanager.cityName)
print(weathermanager.conditionID)
print(weathermanager.temp)
The simple reason is the fact that you are printing the values before the request actually completes. You have to use a completion handler:
class WeatherManager {
func fetchObject<T: Decodable>(urlPath: String, onCompletion: #escaping (Result<T, Error>) -> Void) {
let session = URLSession(configuration: .default)
let url = URL(string: urlPath)!
let task = session.dataTask(with: url) { data, _, error in
if let error = error {
onCompletion(.failure(error))
return
}
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(T.self, from: data ?? Data())
onCompletion(.success(decoded))
} catch {
onCompletion(.failure(error))
}
}
task.resume()
}
func fetchWeather(cityName: String, onCompletion: #escaping (Result<Weatherdata, Error>) -> Void) {
let urlPath = "https://api.openweathermap.org/data/2.5/weather?units=metric&appid=8da179fa1c83749056ec6a5385cabb04&q=" + cityName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
fetchObject(urlPath: urlPath, onCompletion: onCompletion)
}
}
struct Weatherdata: Decodable {
let weather : [Weather]
let main: Main
let name: String
}
struct Weather: Decodable {
let id: Int
let description: String
}
struct Main: Decodable {
let temp: Double
}
let weathermanager = WeatherManager()
weathermanager.fetchWeather(cityName: "beijing") { result in
switch result {
case .success(let weatherData):
print(weatherData.name)
print(weatherData.weather[0].id)
print(weatherData.main.temp)
case .failure(let error):
print(error)
}
}

Decoding dynamic JSON structure in swift 4

I have the following issue that I'm not sure how to handle.
My JSON response can look like this:
{
"data": {
"id": 7,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDY1MTU0NDMsImRhdGEiOiJ2bGFkVGVzdCIsImlhdCI6MTU0NjUwODI0M30.uwuPhlnchgBG4E8IvHvK4bB1Yj-TNDgmi7wUAiKmoVo"
},
"error": null
}
Or like this:
{
"data": [{
"id": 12
}, {
"id": 2
}, {
"id": 5
}, {
"id": 7
}],
"error": null
}
So in short the data can be either a single objet or an Array. What i have is this:
struct ApiData: Decodable {
var data: DataObject?
var error: String?
}
struct DataObject: Decodable {
var userId: Int?
enum CodingKeys: String, CodingKey {
case userId = "id"
}
}
This works fine for the first use case, but it will fail once data turns into
var data: [DataObject?]
How do I make that dynamic without duplicating code?
Edit: This is how i decode the object as well
func makeDataTaskWith(with urlRequest: URLRequest, completion: #escaping(_ apiData: ApiData?) -> ()) {
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
session.dataTask(with: urlRequest) {
(data, response, error) in
guard let _ = response, let data = data else {return}
if let responseCode = response as? HTTPURLResponse {
print("Response has status code: \(responseCode.statusCode)")
}
do {
let retreived = try NetworkManager.shared.decoder.decode(ApiData.self, from: data)
completion(retreived)
} catch let decodeError as NSError {
print("Decoder error: \(decodeError.localizedDescription)\n")
return
}
}.resume()
}
If data can be a single object or an array write a custom initializer which decodes first an array, if a type mismatch error occurs decode a single object. data is declared as an array anyway.
As token appears only in a single object the property is declared as optional.
struct ApiData: Decodable {
let data : [DataObject]
let error : String?
private enum CodingKeys : String, CodingKey { case data, error }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
data = try container.decode([DataObject].self, forKey: .data)
} catch DecodingError.typeMismatch {
data = [try container.decode(DataObject.self, forKey: .data)]
}
error = try container.decodeIfPresent(String.self, forKey: .error)
}
}
struct DataObject: Decodable {
let userId : Int
let token : String?
private enum CodingKeys: String, CodingKey { case userId = "id", token }
}
Edit: Your code to receive the data can be improved. You should add a better error handling to return also all possible errors:
func makeDataTaskWith(with urlRequest: URLRequest, completion: #escaping(ApiData?, Error?) -> Void) {
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
session.dataTask(with: urlRequest) {
(data, response, error) in
if let error = error { completion(nil, error); return }
if let responseCode = response as? HTTPURLResponse {
print("Response has status code: \(responseCode.statusCode)")
}
do {
let retreived = try NetworkManager.shared.decoder.decode(ApiData.self, from: data!)
completion(retreived, nil)
} catch {
print("Decoder error: ", error)
completion(nil, error)
}
}.resume()
}
Using power of generic, it simple like below:
struct ApiData<T: Decodable>: Decodable {
var data: T?
var error: String?
}
struct DataObject: Decodable {
private var id: Int?
var userId:Int? {
return id
}
}
Use
if let obj = try? NetworkManager.shared.decoder.decode(ApiData<DataObject>.self, from: data) {
//Do somthing
} else if let array = try NetworkManager.shared.decoder.decode(ApiData<[DataObject]>.self, from: data) {
// Do somthing
}
If you have only two possible outcomes for your data, an option would be to try and parse data to one of the expected types, if that fails you know that the data is of other type and you can then handle it accordingly.
See this
You can try
struct Root: Codable {
let data: DataUnion
let error: String?
}
enum DataUnion: Codable {
case dataClass(DataClass)
case datumArray([Datum])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([Datum].self) {
self = .datumArray(x)
return
}
if let x = try? container.decode(DataClass.self) {
self = .dataClass(x)
return
}
throw DecodingError.typeMismatch(DataUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for DataUnion"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .dataClass(let x):
try container.encode(x)
case .datumArray(let x):
try container.encode(x)
}
}
}
struct Datum: Codable {
let id: Int
}
struct DataClass: Codable {
let id: Int
let token: String
}
let res = try? JSONDecoder().decode(Root.self, from:data)

Swift 3: completionHandler in session.dataTask not being called [duplicate]

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?
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)
}
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)
}
}
}

How can I get the Data from NSURLSession.sharedSession().dataTaskWithRequest

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?
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)
}
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)
}
}
}