i have this struct import Foundation
import Foundation
// MARK: - Welcome
struct Say: Codable {
let response: [Rest]
init(response: [Rest]) {
self.response = response
}
}
// MARK: - Response
struct Rest: Codable {
let eventID: String
let data: ClassMe
enum CodingKeys: String, CodingKey {
case eventID = "event_id"
case data
}
init(eventID: String, data: ClassMe) {
self.eventID = eventID
self.data = data
}
}
// MARK: - DataClass
struct ClassMe: Codable {
let toTime, to, addInfo, maxWeight: String
let freeSpace, type, fromTime: String
let blocked: Blocked
let fromDate: String
let index: Int
let toDate, cost, from: String
let isDeleted: Bool
let userID: String
enum CodingKeys: String, CodingKey {
case toTime, to, addInfo, maxWeight, freeSpace, type, blocked, fromTime, fromDate, index
case toDate = "ToDate"
case cost, from
case isDeleted = "is_deleted"
case userID = "userId"
}
init(toTime: String, to: String, addInfo: String, maxWeight: String, freeSpace: String, type: String, blocked: Blocked, fromTime: String, fromDate: String, index: Int, toDate: String, cost: String, from: String, isDeleted: Bool, userID: String) {
self.toTime = toTime
self.to= to
self.addInfo = addInfo
self.maxWeight = maxWeight
self.freeSpace = freeSpace
self.type = type
self.blocked = blocked
self.fromTime = fromTime
self.fromDate = fromDate
self.index = index
self.toDate = toDate
self.cost = cost
self.from = from
self.isDeleted = isDeleted
self.userID = userID
}
}
enum Blocked: Codable {
case anythingArray([JSONAny])
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([JSONAny].self) {
self = .anythingArray(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
eturn
}
throw DecodingError.typeMismatch(Blocked.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Blocked"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .anythingArray(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
and if i want to save in core inside an http request i can’t for i
func httpfetchEventFly(){
// declare the parameter as a dictionary that contains string as key and value combination. considering inputs are valid
let dateFormatter = DateFormatter()
let t: [Int] = Array(0...100)
// var i: Int = 0
#State var i = 0
let parameters:Dictionary<String, Any> = [
"data" : [
"date":"07-03-2022",
"type":"airplane"
]
]
// create the url with URL
let url = URL(string: "myURL")! // change server url accordingly
// create the session object
let session = URLSession.shared
// now create the URLRequest object using the url object
var request = URLRequest(url: url)
request.httpMethod = "POST" //set http method as POST
request.httpBody = parameters.percentEncoded()
// add headers for the request
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") // change as per server requirements
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")
do {
// convert parameters to Data and assign dictionary to httpBody of request
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
return
}
// create dataTask using the session object to send data to the server
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
print("Post Request Error: \(error.localizedDescription)")
return
}
// ensure there is valid response code returned from this HTTP response
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode)
else {
print("Invalid Response received from the server")
return
}
// ensure there is data returned
guard let responseData = data else {
print("nil Data received from the server")
return
}
do {
// create json object from data or use JSONDecoder to convert to Model stuct
if let jsonResponse = try JSONSerialization.jsonObject(with: responseData, options: .mutableContainers) as? Dictionary<String,Any>{
print(jsonResponse)
let decoder = JSONDecoder()
do {
let loginResponse = try decoder.decode(Say.self, from: responseData)
for i in t.prefix(120) { ← ((((that doesn’t work i want a solution for the index i))))
print("fromdate:", loginResponse.response[i].data.fromDate)
let peerfly = Peerfly(context: self.persistentContainer.viewContext)
//peertraffic.fromDate = dateFormatter.date(from: loginResponse.response[i].data.fromDate)
peerfly.fromDate = loginResponse.response[i].data.fromDate
peerfly.toDate = loginResponse.response[i].data.toDate
peerfly.freeSpace = loginResponse.response[i].data.freeSpace
peerfly.from = loginResponse.response[i].data.from
peerfly.to = loginResponse.response[i].data.to
peerfly.fromTime = loginResponse.response[i].data.fromTime
peerfly.toTime = loginResponse.response[i].data.fromTime
peerfly.cost = loginResponse.response[i].data.cost
peerfly.maxWeight = loginResponse.response[i].data.maxWeight
peerfly.addInfo = loginResponse.response[i].data.addInfo
peerfly.eventID = loginResponse.response[i].eventID
}
do {
try self.persistentContainer.viewContext.save()
} catch {
print("Failed to save event \(error)")
}
DispatchQueue.main.async {
self.events = loginResponse
}
} catch {
print(error)
}
// handle json response
} else {
print("data maybe corrupted or in wrong format")
throw URLError(.badServerResponse)
}
} catch let error {
print(error.localizedDescription)
}
}
// perform the task
task.resume()
}
}
}
i want an index to loop but when it reaches a null value it doesn’t do fatal error ,and out of range , i just want to take the index that has data which is the response array and if is null it does nothing ( neither prints or throw fatal error ), but can't do it
knowing that i used persistent because i am storing in core data
thank you very much my friends
Related
I am a beginner in iOS development. I was trying to use an api URl: https://www.arbeitnow.com/api/job-board-api in my job search iOS app. But nothing shows on my app. I tested the URL in POSTMAN and it returns json(but HTML in description part?). I wrote the code:
func getResults(completed: #escaping (Result<[Results], ErrorMessage>) -> Void) {
let urlString = "https://www.arbeitnow.com/api/job-board-api"
guard let url = URL(string: urlString) else {return}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let _ = error {
completed(.failure(.invalidData))
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
completed(.failure(.invalidResponse))
return
}
guard let data = data else {
completed(.failure(.invalidData))
return
}
do {
let deconder = JSONDecoder()
deconder.keyDecodingStrategy = .convertFromSnakeCase
let results = try deconder.decode([Results].self, from: data)
completed(.success(results))
} catch {
completed(.failure(.invalidData))
}
}
task.resume()
}
struct Results: Codable {
let slug, companyName, title, resultsDescription: String
let remote: Bool
let url: String
let tags, jobTypes: [String]
let location: String
let createdAt: Int
enum CodingKeys: String, CodingKey {
case slug
case companyName = "company_name"
case title
case resultsDescription = "description"
case remote, url, tags
case jobTypes = "job_types"
case location
case createdAt = "created_at"
}
}
I used the code in HomeViewController:
override func viewDidLoad() {
super.viewDidLoad()
title = "Home"
collectionView.backgroundColor = UIColor(named: "backgroundMain")
collectionView.register(SearchViewCell.self, forCellWithReuseIdentifier: cellId)
setupSearchBar()
Service.shared.getResults() { [weak self] result in
switch result {
case .success(let results):
print(results)
self?.jobResults = results
DispatchQueue.main.async {
self?.collectionView.reloadData()
}
case .failure(let error):
print(error)
}
}
}
888
I don't know what is wrong with my code. Can anyone help? Thanks!
You are discarding all meaningful error information, which will make this hard to diagnose. If you get an Error object, you should return that:
enum WebServiceError: Error {
case httpError(Data, Int)
}
func getResults(completion: #escaping (Result<[Results], Error>) -> Void) {
let urlString = "https://www.arbeitnow.com/api/job-board-api"
guard let url = URL(string: urlString) else {
completion(.failure(URLError(.badURL)))
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
let data = data,
let response = response as? HTTPURLResponse,
error == nil
else {
completion(.failure(error ?? URLError(.badServerResponse)))
return
}
guard 200 ..< 300 ~= response.statusCode else {
completion(.failure(WebServiceError.httpError(data, response.statusCode)))
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let results = try decoder.decode([Results].self, from: data)
completion(.success(results.data))
} catch {
completion(.failure(error))
}
}
task.resume()
}
So, that will,
if there was a URLSession error, tell you what the error was;
if there was a non-2xx status code, tell you what the code was (and return the body of the response, too, in case you want to look at that); and
if there was a parsing error, tell you what the parsing error was.
Without something like this, that captures the salient error information, you are flying blind.
In this case, the error is that you are parsing for [Results], but the structure is a dictionary, whose key is data and whose value is a [Results]. You are missing an object for this dictionary that wraps the [Results].
struct ResponseObject: Decodable {
let data: [Posting]
let links: Links
let meta: Meta
}
struct Posting: Decodable {
let slug, companyName, title, description: String
let remote: Bool
let url: String
let tags, jobTypes: [String]
let location: String
let createdAt: Int
}
struct Links: Decodable {
let first: URL?
let last: URL?
let prev: URL?
let next: URL?
}
struct Meta: Decodable {
let currentPage: Int
let path: URL
let perPage: Int
let from: Int
let to: Int
let terms: String
let info: String
}
func getResults(completion: #escaping (Result<[Posting], Error>) -> Void) {
let urlString = "https://www.arbeitnow.com/api/job-board-api"
guard let url = URL(string: urlString) else {
completion(.failure(URLError(.badURL)))
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
let data = data,
let response = response as? HTTPURLResponse,
error == nil
else {
completion(.failure(error ?? URLError(.badServerResponse)))
return
}
guard 200 ..< 300 ~= response.statusCode else {
completion(.failure(WebServiceError.httpError(data, response.statusCode)))
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let results = try decoder.decode(ResponseObject.self, from: data)
completion(.success(results.data))
} catch {
completion(.failure(error))
}
}
task.resume()
}
Your model does not match the JSON you receive from the link you provided:
Using:
struct Root: Codable{
let data: [WorkData]
let links: Links
let meta: Meta
}
// MARK: - Links
struct Links: Codable {
let first: String
let last, prev: String?
let next: String
}
// MARK: - Meta
struct Meta: Codable {
let currentPage, from: Int
let path: String
let perPage, to: Int
let terms, info: String
enum CodingKeys: String, CodingKey {
case currentPage = "current_page"
case from, path
case perPage = "per_page"
case to, terms, info
}
}
struct WorkData: Codable {
let slug, companyName, title, payloadDescription: String
let remote: Bool
let url: String
let tags, jobTypes: [String]
let location: String
let createdAt: Int
enum CodingKeys: String, CodingKey {
case slug
case companyName = "company_name"
case title
case payloadDescription = "description"
case remote, url, tags
case jobTypes = "job_types"
case location
case createdAt = "created_at"
}
}
should solve the problem
Usage:
let root = JsonDecoder().decode(Root.self, from: data)
let firstCompany = root.data[0]
Edit to adress the comment:
This should work!
let results = try decoder.decode([Results].self, from: data)
is your code isn´t it?
instead use:
let root = JsonDecoder().decode(Root.self, from: data)
how could data be missing here?
After that you should either map the root object to your Result type to keep your Viewmodel and completion Handler the way they are now. Or change Viewmodel and completion Handler instead.
I have the following code that makes an API request to a flurry.com API endpoint and decodes the request.
let url = URL(string: bookStarRateDomainUrl)!
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error {
print("Error with fetching book star rates: \(error)")
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(response)")
return
}
if let data = data,
let flurryItems = try? JSONDecoder().decode(FlurryItem.self, from: data) {
completionHandler(flurryItems.results ?? [])
}
})
task.resume()
The problem is that I cannot use .decode(FlurryItem.self, because the values I get back from the API endpoint is this:
[{
dateTime:2020-06-05 00:00:00.000-07:00
event|name:BookStarRate
paramName|name:bookId
paramValue|name:why-buddism-is-true
count:3
}]
notice how the variable name is "paramName|name". The | makes it impossible to name a variable for that item. What can I do instead?
1- You need to use enum
struct Root: Codable {
var date: String
var name: String
var id: Int
var value: String
var count: Int
enum CodingKeys: String, CodingKey {
case date = "dateTime"
case name = "event|name"
case id = "paramName|name"
case value = "paramValue|name"
case count
}
}
2- You should use [FlurryItem].self
let flurryItems = try? JSONDecoder().decode([FlurryItem].self, from: data)
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)
}
}
}
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)
}
}
}
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)
}
}
}