What would be the replacement for performSelector in Swift? Please suggest below is my code. I need to update the statement I commented in DFURLPrepare class method.
class Login {
func loginRequest(url:String, dictParams: Dictionary <String, String>)
{
let urlPrepare = DFURLPrepare()
urlPrepare.sendRequest(self, url: url, dictParams: dictParams, successMethod: "getDefaultItemsResponse", errorMethod: nil)
}
}
class DFURLPrepare {
func sendRequest (delegate:AnyObject, url : String, dictParams: Dictionary <String, String>,successMethod: String?, errorMethod:String?){
let networkObj = Network()
let requestResource = Resource(url:url,paramdict: dictParams)
networkObj.load(requestResource){ data, response, error in
if let httpResponse = response as? NSHTTPURLResponse {
let statusCode = httpResponse.statusCode
if statusCode == 200 && data != nil{
/// *************Here as in objeactive C **************//
/// [self.delegate performSelector:successMethod withObject:data]];
///// What would be code at place of above statement
}
}
}
}
}
You can use performSelector(_:withObject:) for NSObject-descendants:
class Login: NSObject { //<-Login needs to be a subclass of `NSObject`.
func loginRequest(url:String, dictParams: Dictionary <String, String>)
{
let urlPrepare = DFURLPrepare()
urlPrepare.sendRequest(self, url: url, dictParams: dictParams, successMethod: #selector(getDefaultItemsResponse), errorMethod: nil)
}
func getDefaultItemsResponse(data: NSData?) {
}
}
class DFURLPrepare {
func sendRequest(delegate: NSObject, url : String, dictParams: Dictionary <String, String>,successMethod: Selector, errorMethod: Selector){
//Type of `delegate` needs to be `NSObject`.
let networkObj = Network()
let requestResource = Resource(url:url,paramdict: dictParams)
networkObj.load(requestResource){ data, response, error in
if let httpResponse = response as? NSHTTPURLResponse {
let statusCode = httpResponse.statusCode
if statusCode == 200 && data != nil{
delegate.performSelector(successMethod, withObject: data)
}
}
}
}
}
But using closure would be a more preferred way by many Swift programmers:
class Login {
func loginRequest(url: String, dictParams: Dictionary <String, String>) {
let urlPrepare = DFURLPrepare()
urlPrepare.sendRequest(url, dictParams: dictParams, successHandler: getDefaultItemsResponse, errorHandler: nil)
}
func getDefaultItemsResponse(data: NSData?) {
}
}
class DFURLPrepare {
func sendRequest(url : String,
dictParams: [String: String],
successHandler: ((NSData?)->Void)?,
errorHandler: ((NSError?)->Void)?
) {
let networkObj = Network()
let requestResource = Resource(url:url,paramdict: dictParams)
networkObj.load(requestResource){ data, response, error in
if let httpResponse = response as? NSHTTPURLResponse {
let statusCode = httpResponse.statusCode
if statusCode == 200 && data != nil{
successHandler?(data)
}
}
}
}
}
Related
I'm having an issue decoding an API response.
So we have a NetworkManager class which we use to decode APIs. I have a simple GET endpoint that I need to retrieve a list of airports from. Here is the endpoint:
static let airports = Endpoint(url: "/test/airports")
Endpoint is defined as follows:
public struct Endpoint : Equatable {
public init(url: String? = nil, pattern: String? = nil, methods: [Test.HTTPMethod] = [.get], type: Test.EncodingType = .json)
}
Then in our network manager we have:
public func call<R: Decodable>(_ endpoint: Endpoint,
with args: [String: String]? = nil,
using method: HTTPMethod = .get,
expecting response: R.Type?,
completion: APIResponse<R>) {
call(endpoint, with: args, parameters: Nothing(),
using: method, posting: Nothing(), expecting: response, completion: completion)
}
My Airport model is as follows:
struct Airport: Codable {
let id: String
let name: String
let iata3: String
let icao4: String
let countryCode: String
}
And then I'm calling the endpoint like:
private func getAirportsList() {
API.client.call(.airports, expecting: [Airport].self) { (result, airports) in
print(airports)
}
}
Now I'm using Charles to proxy and I am getting the response I expect:
[{
"id": "5f92b0269c983567fc4b9683",
"name": "Amsterdam Schiphol",
"iata3": "AMS",
"icao4": "EHAM",
"countryCode": "NL"
}, {
"id": "5f92b0269c983567fc4b9685",
"name": "Bahrain International",
"iata3": "BAH",
"icao4": "OBBI",
"countryCode": "BH"
}, {
"id": "5f92b0269c983567fc4b968b",
"name": "Bankstown",
"iata3": "BWU",
"icao4": "YSBK",
"countryCode": "AU"
}]
But in my getAirports() method, airports is nil. I'm really struggling to see why. Clearly the endpoint is being hit correctly but my decoding is failing.
Edit:
Full method:
private func call<P: Encodable, B: Encodable, R: Decodable>(_ endpoint: Endpoint,
with args: [String: String]? = nil,
parameters params: P?,
using method: HTTPMethod = .get,
posting body: B?,
expecting responseType: R.Type?,
completion: APIResponse<R>) {
// Prepare our URL components
guard var urlComponents = URLComponents(string: baseURL.absoluteString) else {
completion?(.failure(nil, NetworkError(reason: .invalidURL)), nil)
return
}
guard let endpointPath = endpoint.url(with: args) else {
completion?(.failure(nil, NetworkError(reason: .invalidURL)), nil)
return
}
urlComponents.path = urlComponents.path.appending(endpointPath)
// Apply our parameters
applyParameters: if let parameters = try? params.asDictionary() {
if parameters.count == 0 {
break applyParameters
}
var queryItems = [URLQueryItem]()
for (key, value) in parameters {
if let value = value as? String {
let queryItem = URLQueryItem(name: key, value: value)
queryItems.append(queryItem)
}
}
urlComponents.queryItems = queryItems
}
// Try to build the URL, bad request if we can't
guard let urlString = urlComponents.url?.absoluteString.removingPercentEncoding,
var url = URL(string: urlString) else {
completion?(.failure(nil, NetworkError(reason: .invalidURL)), nil)
return
}
if let uuid = UIDevice.current.identifierForVendor?.uuidString, endpoint.pattern == "/logging/v1/device/<device_id>" {
let us = "http://192.168.6.128:3000/logging/v1/device/\(uuid)"
guard let u = URL(string: us) else { return }
url = u
}
// Can we call this method on this endpoint? If not, lets not try to continue
guard endpoint.httpMethods.contains(method) else {
completion?(.failure(nil, NetworkError(reason: .methodNotAllowed)), nil)
return
}
// Apply debug cookie
if let debugCookie = debugCookie {
HTTPCookieStorage.shared.setCookies(
HTTPCookie.cookies(
withResponseHeaderFields: ["Set-Cookie": debugCookie],
for:url
), for: url, mainDocumentURL: url)
}
// Build our request
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
if let headers = headers {
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
}
// If we are posting, safely retrieve the body and try to assign it to our request
if !(body is NothingProtocol) {
guard let body = body else {
completion?(.failure(nil, NetworkError(reason: .buildingPayload)), nil)
return
}
do {
let result = try encode(body: body, type: endpoint.encodingType)
request.httpBody = result.data
request.setValue(result.headerValue, forHTTPHeaderField: "Content-Type")
} catch {
completion?(.failure(nil, NetworkError(reason: .buildingPayload)), nil)
return
}
}
// Build our response handler
let task = session.dataTask(with: request as URLRequest) { (rawData, response, error) in
// Print some logs to help track requests
var debugOutput = "URL\n\(url)\n\n"
if !(params is Nothing.Type) {
debugOutput.append(contentsOf: "PARAMETERS\n\(params.asJSONString() ?? "No Parameters")\n\n")
}
if !(body is Nothing.Type) {
debugOutput.append(contentsOf: "BODY\n\(body.asJSONString() ?? "No Body")\n\n")
}
if let responseData = rawData {
debugOutput.append(contentsOf: "RESPONSE\n\(String(data: responseData, encoding: .utf8) ?? "No Response Content")")
}
Logging.client.record(debugOutput, domain: .network, level: .debug)
guard let httpResponse = response as? HTTPURLResponse else {
guard error == nil else {
completion?(.failure(nil, NetworkError(reason: .unwrappingResponse)), nil)
return
}
completion?(.failure(nil, NetworkError(reason: .invalidResponseType)), nil)
return
}
let statusCode = httpResponse.statusCode
// We have an error, return it
guard error == nil, NetworkManager.successStatusRange.contains(statusCode) else {
var output: Any?
if let data = rawData {
output = (try? JSONSerialization.jsonObject(with: data,
options: .allowFragments)) ?? "Unable to connect"
Logging.client.record("Response: \(String(data: data, encoding: .utf8) ?? "No error data")", domain: .network)
}
completion?(.failure(statusCode, NetworkError(reason: .requestFailed, json: output)), nil)
return
}
// Safely cast the responseType we are expecting
guard let responseType = responseType else {
completion?(.failure(statusCode, NetworkError(reason: .castingToExpectedType)), nil)
return
}
// If we are expecting nothing, return now (since we will have nothing!)
if responseType is Nothing.Type {
completion?(.success(statusCode), nil)
return
}
guard let data = rawData else {
assertionFailure("Could not cast data from payload when we passed pre-cast checks")
return
}
// Decode the JSON and cast to our expected response type
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let responseObject = try decoder.decode(responseType, from: data)
completion?(.success(statusCode), responseObject)
return
} catch let error {
let content = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
Logging.client.record("Failed to build codable from JSON: \(String(describing: content))\n\nError: \(error)", domain: .network, level: .error)
assertionFailure("Failed to build codable from JSON: \(error)")
completion?(.failure(statusCode, NetworkError(reason: .castingToExpectedType)), nil)
return
}
}
// Submit our request
task.resume()
}
I am using RxMoya for my networking calls and extending PremitiveSequence and Response so as to handle the error coming back. I declared a struct of Networking error which I could use to get all the error details and as such Pass the error message via the BaseResponse Model. Here is my NetwokingError struct
public struct NetworkingError: Error {
let httpResponse: HTTPURLResponse?
let networkData: Data?
let baseError: Error
}
For my coding, I have extended the primitive sequence as follows
public extension PrimitiveSequence where TraitType == SingleTrait,
ElementType == Response {
func mapObject<T: Codable>(_ type: T.Type, path: String? = nil) -> Single<T> {
return flatMap { response -> Single<T> in
return Single.just(try response.mapObject(type, path: path))
}
}
func mapArray<T: Codable>(_ type: T.Type, path: String? = nil) -> Single<[T]> {
return flatMap { response -> Single<[T]> in
return Single.just(try response.mapArray(type, path: path))
}
}
func filterSuccess() -> Single<E> {
return flatMap { (response) -> Single<E> in
if 200 ... 299 ~= response.statusCode {
return Single.just(response)
}
print("THIS ERROR JSON jsonObject2 xx mm \(response.data)")
do {
let jsonObject2 = try JSONSerialization.jsonObject(with: response.getJsonData(), options: .allowFragments)
print("THIS ERROR JSON jsonObject2 xx \(jsonObject2)")
let jsonObject = try JSONSerialization.jsonObject(with: response.getJsonData(), options: .allowFragments) as? NetworkingError
print("THIS ERROR JSON xx \(jsonObject)")
return Single.error(jsonObject ?? NetworkingError.self as! Error)
}
}
}
}
if I run this code here, The app crashes return Single.error(jsonObject ?? NetworkingError.self as! Error)
in my code, I am passing data like
func postVerifyApp(challenge: Int, identifier: String) -> Observable<AuthResponse> {
return provider.rx.request(.postVerifyApp(challenge: challenge, identifier: identifier))
.filterSuccess()
.mapObject(AuthResponse.self)
.asObservable()
.flatMap({ authResponse -> Observable<AuthResponse> in
return self.sendTokenToServer(authResponse)
})
}
then I am working with this in my presenter class like this
func postVerifyApp(challenge: Int, identifier: String) {
view?.setProgress(enabled: true)
source.postVerifyApp(challenge: challenge, identifier: identifier)
.retry(.delayed(maxCount: 2, time: 2.5), shouldRetry: networkRetryPredicate)
.asSingle()
.subscribe(onSuccess: { [weak self] response in
guard let presenter = self, let view = presenter.view else {return}
view.setProgress(enabled: false)
log(response, .json)
guard let data = response.data else {
return }
view.showVerifySuccess()
}, onError: { [weak self] error in
guard let presenter = self, let view = presenter.view else {return}
print("MESSAGE X \(error.localizedDescription)")
if let error = error as? NetworkingError {
print("MESSAGE X httpResponse \(error.httpResponse)")
}
view.setProgress(enabled: false)
}).disposed(by: disposeBag)
}
I want to be able to pass this Error and extract the error message and passing it to the console.
This is what my base Model looks like
struct ResponseBase<T: Codable>: Codable {
var error: Bool?
var message: String?
var data: T
var isSucessful: Bool {
return error == false
}
}
The expression used to construct the Single.error can not cast as Error. Firstly, you are trying to cast a jsonObject (a Dictionary) as Error. On the right hand, on the ifNull expression, you are trying to cast a metatype (Networking.Type) as an Error.
To solve your casting problem you can use this modified NetworkingError.
public struct NetworkingError: Error {
let httpResponse: HTTPURLResponse?
let networkData: Data?
let baseError: MoyaError
init(_ response:Response) {
self.baseError = MoyaError.statusCode(response)
self.httpResponse = response.response
self.networkData = response.data
}
func getLocalizedDescription() -> String {
return self.baseError.localizedDescription
}
}
Having this, modify the closure in the filterSuccess to create the NetworkingError object, passing it the Response, just like this:
func filterSuccess() -> Single<E> {
return flatMap {
(response) -> Single<E> in
if 200 ... 299 ~= response.statusCode {
return Single.just(response)
} else {
let netError = NetworkingError(response)
return Single.error(netError)
}
}
}
I encourage you to take a look at the MoyaError definition
I want to create custom framework for universal API request using URLSession. So I have used this link. I will be using this project as a custom framework. So to use that I have changed its access specifier by open .And using thislink I have imported it in my project. And I have done the following code to call post request
import iOSCoreFramework
func callBySpeedyNetworking2() {
let trylogin = login(username: "****", password: "***")
SpeedyNetworking.removeToken()
SpeedyNetworking.postUrl(url: URL(string: GlobalConstants.loginFullURL), model: trylogin) { (response) in
if !response.success {
// show a network error
print("network error ",response.error)
return
}
// successful
print("RESPONSE 1 ------------> ")
dump(response.result(model: ModelResponse.self))
dump(response.jsonResults(model: ModelResponse.self))
}
}
But it's giving me an error for 'success', 'error' and on the following lines:
dump(response.result(model: ModelResponse.self))
dump(response.jsonResults(model: ModelResponse.self))
From various links I have made changes in SpeedyResponse class by the following
public class SpeedyResponse {
public var success : Bool!
public var statusCode = 0
public var error: Error?
public var data: Data?
public init (success : Bool, statusCode : Int,error : Error, data : Data){
self.success = success
self.statusCode = statusCode
self.error = error
self.data = data
}
public init(data: Data?, response: URLResponse?, error: Error?) {
self.error = error
self.data = data
if let httpResponse = response as? HTTPURLResponse {
statusCode = httpResponse.statusCode
}
success = statusCode == 200 && error == nil && data != nil ? true : false
}
public func jsonResults<T>(model: T.Type) -> T? {
if !success { return nil }
guard let responseData = data else { return nil }
do {
return try JSONSerialization.jsonObject(with: responseData) as? T
} catch {
return nil
}
}
public func result<T: Decodable>(model: T.Type) -> T? {
if !success { return nil }
guard let responseData = data else { return nil }
do {
return try JSONDecoder().decode(model, from: responseData)
} catch {
return nil
}
}
}
But still it wasn't fixed.
The underlying problem here is inside your struct all variables and methods are declared automatically with its scope as internal. So when you create a type like this:
public class Human {
let foo: String
let bar: String
}
You will not be able to access both foo and bar because they are actually declared as:
public class Human {
internal let foo: String
internal let bar: String
}
To fix this just add the access modifier to public.
In that sense your new model should look like this
public class SpeedyResponse {
public var success: Bool!
public var statusCode = 0
public var error: Error?
public var data: Data?
public init(data: Data?, response: URLResponse?, error: Error?) {
self.error = error
self.data = data
if let httpResponse = response as? HTTPURLResponse {
statusCode = httpResponse.statusCode
}
success = statusCode == 200 && error == nil && data != nil ? true : false
}
public func jsonResults<T>(model: T.Type) -> T? {
if !success { return nil }
guard let responseData = data else { return nil }
do {
return try JSONSerialization.jsonObject(with: responseData) as? T
} catch {
return nil
}
}
public func result<T: Decodable>(model: T.Type) -> T? {
if !success { return nil }
guard let responseData = data else { return nil }
do {
return try JSONDecoder().decode(model, from: responseData)
} catch {
return nil
}
}
}
How can I write a generic wrapper for alamofire request ?
How can I convert the POST and GET function to Generic function in the following code?
I need to have generic request functions that show different behaviors depending on the type of data received.
Also, Can the response be generic?
My non-generic code is fully outlined below
import Foundation
import Alamofire
import SwiftyJSON
// for passing string body
extension String: ParameterEncoding {
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try urlRequest.asURLRequest()
request.httpBody = data(using: .utf8, allowLossyConversion: false)
return request
}
}
public class ConnectionManager {
func Post (FirstName: String, LastName: String, NationalID: String, NationalCode: String, BirthDate: Date,Image: String,isFemale: Bool,Age:Int64,Avg:Double, completion: #escaping CompletionHandler) {
let body: [String:Any] = [
"FirstName":FirstName,
"LastName": LastName,
"NationalID": NationalID,
"NationalCode": NationalCode,
"BirthDate":BirthDate,
"Image": Image,
"isFemale": isFemale,
"Age": Age,
"Avg": Avg
]
Alamofire.request(BASE_URL, method: .post, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseJSON { (response) in
if response.response?.statusCode == 200 {
guard let data = response.result.value else { return }
print(data)
completion(true)
} else {
print("error reg auth service")
guard let er = response.result.value else { return }
print(er)
completion(false)
debugPrint(response.result.error as Any)
}
}
}
func get(FirstName: String, LastName: String, NationalID: String, NationalCode: String, BirthDate: Date,Image: String,isFemale: Bool,Age:Int64,Avg:Double, completion: #escaping CompletionHandler) -> [Person] {
let body: [String:Any] = [
"FirstName":FirstName,
"LastName": LastName,
"NationalID": NationalID,
"NationalCode": NationalCode,
"BirthDate":BirthDate,
"Image": Image,
"isFemale": isFemale,
"Age": Age,
"Avg": Avg
]
Alamofire.request(BASE_URL, method: .get, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseJSON { (response) in
if response.response?.statusCode == 200 {
print("no error login in authservice")
guard let data = response.result.value else { return }
print(data)
completion(true)
}
else if response.response?.statusCode == 400 {
print("error 400 login in authservice")
guard let er = response.result.value else { return }
print(er)
debugPrint(response.result.error as Any)
completion(false)
} else {
print("error ## login in authservice")
guard let er = response.result.value else { return }
print(er)
debugPrint(response.result.error as Any)
completion(false)
}
}
return [Person]()
}
}
The best idea is to use the URLRequestConvertible Alamofires protocol and create your own protocol and simple structs for every API request:
import Foundation
import Alamofire
protocol APIRouteable: URLRequestConvertible {
var baseURL: String { get }
var path: String { get }
var method: HTTPMethod { get }
var parameters: Parameters? { get }
var encoding: ParameterEncoding { get }
}
extension APIRouteable {
var baseURL: String { return URLs.baseURL }
// MARK: - URLRequestConvertible
func asURLRequest() throws -> URLRequest {
let url = try baseURL.asURL().appendingPathComponent(path)
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = method.rawValue
return try encoding.encode(urlRequest, with: parameters)
}
}
and request can look like this:
struct GetBooks: APIRouteable {
var path: String
var method: HTTPMethod
var parameters: Parameters?
var encoding: ParameterEncoding
}
and inside the APIClient prepare the generic method:
func perform<T: Decodable>(_ apiRoute: APIRouteable,
completion: #escaping (Result<T>) -> Void) {
let dataRequest = session
.request(apiRoute)
dataRequest
.validate(statusCode: 200..<300)
.responseDecodable(completionHandler: { [weak dataRequest] (response: DataResponse<T>) in
dataRequest.map { debugPrint($0) }
let responseData = response.data ?? Data()
let string = String(data: responseData, encoding: .utf8)
print("Repsonse string: \(string ?? "")")
switch response.result {
case .success(let response):
completion(.success(response))
case .failure(let error):
completion(.failure(error))
}
})
}
and use it:
func getBooks(completion: #escaping (Result<[Book]>) -> Void) {
let getBooksRoute = APIRoute.GetBooks()
perform(getBooksRoute, completion: completion)
}
when I call this function the array 'data' is showing empty square brackets, it is not giving me any errors though
here is the code :-
import Foundation
import Alamofire
import SwiftyJSON
class Networking {
var data = [Item]()
let tVC = TableViewController()
let url = "https://api.coinmarketcap.com/v1/ticker/"
func getCoinData(url: String) {
Alamofire.request(url, method: .get)
.responseJSON { response in
if response.result.isSuccess {
let coinJSON : JSON = JSON(response.result.value!)
for i in 0..<coinJSON.count{
let coinName = Item(bitJSON: coinJSON[i])
self.data.append(coinName)
self.tVC.tableView.reloadData()
}
}
else {
print("Error: \(String(describing: response.result.error))")
}
}
}
}
Try this one.
Change your url, method is "Get" and your mapping model.
public static func getArrayInformation(completionHandler: #escaping (APIResponse) -> Void) {
let url = "your url"
let params: Parameters = ["username": "admin",
"password": "1234"]
Alamofire.request(url,
method: .post,
parameters: params,
encoding: JSONEncoding.default,
headers: ["Content-Type": "application/json"])
.responseDecodableObject(keyPath: nil, decoder: JSONDecoder(), completionHandler: { (handler: DataResponse<[Your Object Array]>) in
completionHandler(handler.result.value ?? [Your Object Array]())
})
}
why you reload Table every loop iteration , and instead Loop use Map
class Networking {
var data = [Item]()
let tVC = TableViewController()
let url = "https://api.coinmarketcap.com/v1/ticker/"
func getCoinData(url: String) {
Alamofire.request(url, method: .get)
.responseJSON { response in
if response.result.isSuccess {
let coinJSON : JSON = JSON(response.result.value!)
data = coinJSON.map({ (coinJson) -> Item in
return Item(bitJSON: coinJson)
})
DispatchQueue.main.async {
self.tVC.tableView.reloadData()
}
}
else {
print("Error: \(String(describing: response.result.error))")
}
}
}
}