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)
}
}
}
Related
I am working with the TMDB api and I'm building a networking layer to do this. I'm following a tutorial as well which I'm pretty sure I followed correctlly. I'm calling this API Endpoint:
{"page":1,
"results":[{"adult":false,"backdrop_path":"/fev8UFNFFYsD5q7AcYS8LyTzqwl.jpg","genre_ids":[16,28,35,10751],
"id":587807,
"original_language":"en","original_title":"Tom & Jerry",
"overview":"Jerry moves into New York City's finest hotel on the eve of the wedding of the century, forcing the desperate event planner to hire Tom to get rid of him. As mayhem ensues, the escalating cat-and-mouse battle soon threatens to destroy her career, the wedding, and possibly the hotel itself.",
"popularity":4136.493,
"poster_path":"/6KErczPBROQty7QoIsaa6wJYXZi.jpg",
"release_date":"2021-02-12",
"title":"Tom & Jerry",
"video":false,
"vote_average":8,
"vote_count":541}]
}
Unfortunately I'm getting this error:
keyNotFound(CodingKeys(stringValue: "backdrop_path", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "results", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"backdrop_path\", intValue: nil) (\"backdrop_path\").", underlyingError: nil))
This applies to every key in my Resulting struct.
Here is the method in my ViewController:
func fetchUpComing() {
let api = MovieDB.api
api.send(request: .upcomingMovies(completion: { result in
print("result", result)
switch result {
case .success(let page):
print(page.results)
self.upcomingMovies = page.results
var basicSection = MovieSection()
basicSection.numberOfItems = self.upcomingMovies.count
basicSection.upcomingItems = page.results
self.sections = [TitleSection(title: "Upcoming Movies"), basicSection]
self.setupCollectionView()
case .failure(let error): print(error)
}
}))
}
MovieDB:
struct MovieDB { // logic specific to the TMDB API
public static let baseURL = URL(string: "https://api.themoviedb.org/3/")!
public static var api: APIClient = {
let configuration = URLSessionConfiguration.default
let apiKey = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJmMzM2Yzk4MmY0MzdhMGQ1OTQ1MDFlY2U1YTA0ZmI3NiIsInN1YiI6IjYwM2ZjMTg5OTdlYWI0MDA3NzViYWIyMCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.J5hW42SMtm7RBeVGy9Z_Ex"
configuration.httpAdditionalHeaders = [
"Authorization": "Bearer"
]
return APIClient(configuration: configuration)
}()
public static let baseImageURL = URL(string: "https://image.tmdb.org/t/p/w500")!
}
public extension Result where Success == Data, Failure == Error {
func decoding<M: Model>(_ model: M.Type, completion: #escaping (Result<M, Error>) -> Void) {
// decode the JSON in the background and call the completion block on the main thread
DispatchQueue.global().async {
//Result’s flatMap() method takes the successful case (if it was successful) and applies your block. You can return a new Result that contains a successful value or an error.
let result = self.flatMap { data -> Result<M, Error> in
do {
let decoder = M.decoder
let model = try decoder.decode(M.self, from: data)
return .success(model)
} catch {
print("Error: \(error) with :\(String(data: data, encoding: .utf8))")
return .failure(error)
}
}
DispatchQueue.main.async {
completion(result)
}
}
}
API Client:
private let session: URLSession
init(configuration: URLSessionConfiguration) {
session = URLSession(configuration: configuration)
}
public func send(request: Request) {
let urlRequest = request.builder.toURLRequest()
let task = session.dataTask(with: urlRequest) { data, response, error in
let result: Result<Data, Error>
if let error = error {
result = .failure(error)
} else {
result = .success(data ?? Data())
}
DispatchQueue.main.async {
request.completion(result)
}
}
task.resume()
}
}
Request:
public struct Request {
let builder: RequestBuilder
let completion: (Result<Data, Error>) -> Void
init(builder: RequestBuilder, completion: #escaping (Result<Data, Error>) -> Void) {
self.builder = builder
self.completion = completion
}
public static func basic(method: HTTPMethod = .get, baseURL: URL, path: String, params: [URLQueryItem]? = nil, completion: #escaping (Result<Data, Error>) -> Void) -> Request {
let builder = BasicRequestBuilder(method: method, baseURL: baseURL, path: path, params: params)
return Request(builder: builder, completion: completion)
}
}
extension Request {
static func popularMovies(completion: #escaping (Result<PagedResults<Movie>, Error>) -> Void) -> Request {
Request.basic(baseURL: MovieDB.baseURL, path: "discover/movie", params: [
URLQueryItem(name: "sort_by", value: "popularity.desc")
]) { result in
result.decoding(PagedResults<Movie>.self, completion: completion) }
}
static func upcomingMovies(completion: #escaping (Result<Upcoming<Resulting>, Error>) -> Void) -> Request {
Request.basic(baseURL: MovieDB.baseURL, path: "movie/upcoming", params: [
URLQueryItem(name: "sort_by", value: "popularity.desc")
]) { result in
result.decoding(Upcoming<Resulting>.self, completion: completion) }
}
}
RequestBuilder:
public enum HTTPMethod: String {
case get
case post
case put
case delete
}
public protocol RequestBuilder {
var method: HTTPMethod { get }
var baseURL: URL { get }
var path: String { get }
var params: [URLQueryItem]? { get }
var headers: [String: String] { get }
func toURLRequest() -> URLRequest
}
public extension RequestBuilder {
func toURLRequest() -> URLRequest {
var components = URLComponents(url: baseURL.appendingPathComponent(path), resolvingAgainstBaseURL: false)!
components.queryItems = params
let url = components.url!
var request = URLRequest(url: url)
request.allHTTPHeaderFields = headers
request.httpMethod = method.rawValue.uppercased()
return request
}
}
struct BasicRequestBuilder: RequestBuilder {
var method: HTTPMethod
var baseURL: URL
var path: String
var params: [URLQueryItem]?
var headers: [String: String] = [:]
}
Here is my struct used in decoding:
import Foundation
struct Movie: Model, Hashable {
let id: Int
let title: String
let posterPath: String
let releaseDate: String
}
struct PagedResults<T: Model>: Model {
let page: Int
let totalPages: Int
let results: [T]
}
extension PagedResults {
static var decoder: JSONDecoder { T.decoder }
}
struct Upcoming<T: Model>: Model {
let dates: Dates
let page: Int
let results: [Resulting]
let totalPages, totalResults: Int
enum CodingKeys: String, CodingKey {
case dates, page, results
case totalPages = "total_pages"
case totalResults = "total_results"
}
}
extension Upcoming {
static var decoder: JSONDecoder { T.decoder }
}
// MARK: - Dates
struct Dates: Codable {
let maximum, minimum: String
}
// MARK: - Result
struct Resulting: Model, Hashable{
let adult: Bool
let backdropPath: String
let genreIDS: [Int]
let id: Int
let originalLanguage: OriginalLanguage
let originalTitle, overview: String
let popularity: Double
let posterPath, releaseDate, title: String
let video: Bool
let voteAverage: Double
let voteCount: Int
enum CodingKeys: String, CodingKey {
case adult
case backdropPath = "backdrop_path"
case genreIDS = "genre_ids"
case id
case originalLanguage = "original_language"
case originalTitle = "original_title"
case overview, popularity
case posterPath = "poster_path"
case releaseDate = "release_date"
case title, video
case voteAverage = "vote_average"
case voteCount = "vote_count"
}
}
enum OriginalLanguage: String, Codable {
case en = "en"
case es = "es"
case ja = "ja"
}
I am trying to plug a memory leak. I have the following class that fetches API requests:
public struct Service {
public let baseURL: URL
public let session: URLSession
public init (baseURL: URL, session: URLSession) {
self.baseURL = baseURL
self.session = session
}
public struct Response {
public let data: Data
public let response: URLResponse
}
public enum ServiceError: Error {
case api(title: String, messages: [String])
case other(Error)
}
struct ServiceErrorResponse: Decodable {
let response: ErrorResponse
enum CodingKeys: String, CodingKey {
case response = "error"
}
}
struct ErrorResponse: Decodable {
let title: String
let messages: [String]
}
public enum HTTPMethod: String {
case get = "GET"
case put = "PUT"
case post = "POST"
case patch = "PATCH"
case delete = "DELETE"
}
public func run(_ request: URLRequest) -> AnyPublisher<Response, ServiceError> {
return session
.dataTaskPublisher(for: request)
.tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
let error = try JSONDecoder().decode(ServiceErrorResponse.self, from: data)
let title = error.response.title
let messages = error.response.messages
print(error.response)
throw ServiceError.api(title: title, messages: messages)
}
return Response(data: data, response: response)
}
.mapError { err in
let error = err is ServiceError ? err : ServiceError.other(err)
return error as! Service.ServiceError
}
.eraseToAnyPublisher()
}
public func fetch(
_ path: String,
method: HTTPMethod = .get,
params: Data? = nil
) -> AnyPublisher<Response, ServiceError> {
let url: URL
if let params = params, method == .get {
url = buildGetURLWithParams(path: path, params: params)!
}
else {
url = baseURL.appendingPathComponent(path)
}
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
if let params = params, method != .get {
request.httpBody = params
}
return run(request)
}
private func buildGetURLWithParams(path: String, params: Data) -> URL? {
if let json = try? JSONSerialization.jsonObject(with: params, options: []) as? [String: String] {
var urlComponents = URLComponents(
url: baseURL.appendingPathComponent(path),
resolvingAgainstBaseURL: false
)
urlComponents?.queryItems = json.map { URLQueryItem(name: $0, value: $1) }
return urlComponents?.url
}
else { return nil }
}
}
I then make requests from the app using the following:
typealias ServiceResponse = Service.Response
typealias ServiceError = Service.ServiceError
typealias ServiceMethod = Service.HTTPMethod
enum MyAPI {
static let service = Service(
baseURL: URL(string: "http://127.0.0.1:3000/api")!,
session: URLSession(configuration: URLSessionConfiguration.default)
)
static func login(email: String, password: String) -> AnyPublisher<ServiceResponse, ServiceError> {
let params = ["email": email, "password": password]
let json = try! JSONEncoder().encode(params)
return service.fetch("/login", method: .post, params: json)
}
}
The login function fetches a response and returns an AnyPublisher which is consumed as follows:
enum UserAction {
case login
case loginSuccess(UserResponse)
case loginFailure
case logout
static func login(email: String, password: String) -> Dispatch<AppAction> {
return { dispatch in
dispatch(.userAction(action: .login))
return MyAPI.login(email: email, password: password)
.map(\.data)
.decode(type: UserResponse.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { completion in
if case .failure(let err) = completion {
print("--------------------------")
print("Retrieving data failed with error \(err)")
}
},
receiveValue: { result in
dispatch(.userAction(action: .loginSuccess(result))) // Here I have a memory leak
}
)
}
}
}
I am mimicking something like Redux where a dispatch actions to change state, so I return an 'effect' from the UserAction login that gets the dispatch function. Everything works nicely but at the receiveValue line I get a memory leak with the following description:
Any ideas what could be causing it or how I can find out? Im fairly new to Xcode and Swift.
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)
}
}
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
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)
}
}
}