How to call REST full API by providing headers in Swift - ios

I am quite new to Swift,
we have a API server where we need to pass HEADERS like this:
Content-Type:application/json
Session:fb4e7f9b-0f31-4709-
I was looking in Google, the most basic example uses HTTP url call (i.e GET,POST)
Do I need to use some third-party product to call REST API by providing headers?
I would appreciate if you provide some guideline and possibly some example articles where it's showing how do to REST API call by providing different type HEADERs.

By using Alamofire it's pretty simple.
let headers: HTTPHeaders = [
"Content-Type": "application/json",
"Session": "fb4e7f9b-0f31-4709-"
]
Alamofire.request("https://httpbin.org/headers", headers: headers).responseJSON { response in
//Parse or print your response.
}
By using urlRequest
let header: HTTPHeaders = [
"Content-Type": "application/json",
"Session": "fb4e7f9b-0f31-4709-"
]
var urlRequest = URLRequest(url: URL(string: "your request url goes here."), cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10)
urlRequest.allHTTPHeaderFields = header
urlRequest.httpMethod = //.get, .post, .put
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
print(error)
} else if let data = data ,let responseCode = response as? HTTPURLResponse {
do {
// Parse your response here.
}
}
catch let parseJSONError {
print("error on parsing request to JSON : \(parseJSONError)")
}
}
}.resume()

import Foundation
let url = URL(string: "")!
var request = URLRequest(url: url)
request.allHTTPHeaderFields = [
"Content-Type": "application/json",
"Session": "fb4e7f9b-0f31-4709-"
]
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else { return }
guard let data = data, let response = response else { return }
// handle data
}.resume()

The URLRequest type documentation outlines all of the different properties you can set. You need to create a URLRequest instance and then use URLSession to perform the request.

with header fields
static func getFeed(Completion:#escaping(FeedElement)->Void)
{
let urlString = NSString(format: "https://api-nhdev.india.com/api/v1/feeds/1/10/Kondapur") as String
guard let url = URL(string:urlString)
else {
return
}
let header: HTTPHeaders = [
"Authorization": YOUR_AUTH_ID
]
let request = NSMutableURLRequest(url: url as URL)
request.allHTTPHeaderFields = header
request.httpMethod = "GET"
let session = URLSession.shared
let mData = session.dataTask(with: request as URLRequest) { (data, response, error) -> Void in
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Response Error")
return }
do{
//here dataResponse received from a network request
let decoder = JSONDecoder()
let codabledata = try decoder.decode(FeedElement.self, from: dataResponse)
print(codabledata)
//Response result
Completion(codabledata)
} catch let parsingError {
print("Error", parsingError)
}
}
mData.resume()
}
}

extension URL
{
init(baseUrl: String, path: String, method: RequestMethod, params: [String:Any])
{
var components = URLComponents(string: baseUrl)!
components.path += path
switch method {
case .get,.delete:
var queryItems = components.queryItems ?? []
queryItems.append(contentsOf: params.map {
URLQueryItem(name: $0.key, value: String(describing: $0.value))
})
//print(queryItems)
components.queryItems = queryItems
default:
break
}
//print(components.url!)
self = components.url!
}
}
extension URLRequest
{
init(baseUrl: String, path: String, method: RequestMethod, params: [String:Any]) {
let url = URL(baseUrl: baseUrl, path: path, method: method, params: params)
//////////////My Code For Remove Percentages In URL//////////////////////////
let urlWithPath = url.absoluteString.removingPercentEncoding
let absoluteUrl = URL(string: urlWithPath!)!
self.init(url: absoluteUrl)
//self.init(url: url)
let util = AuthToken.shared()
httpMethod = method.rawValue
setValue("application/json", forHTTPHeaderField: "Content-Type")
if util.getToken() != "" {
setValue(util.getToken(), forHTTPHeaderField: "Token")
}
switch method {
case .post, .put:
httpBody = try! JSONSerialization.data(withJSONObject: params, options: [])
default:
break
}
}
}
struct NetworkManager {
static let environment : NetworkEnvironment = .qa
private init(){}
static let shared = NetworkManager();
private var baseURL = APIEndPoint.baseURL
func load(path:String, method:RequestMethod, params:[String:Any], completion:#escaping(_ responseObject:Data?, _ error:Error?, _ isSuccess: Bool?) -> Void) -> Void{
let reachability = Reachability()
if(reachability?.connection == .wifi || reachability?.connection == .cellular)
{
// Creating the URLRequest object
let request = URLRequest(baseUrl: baseURL, path: path, method: method, params: params)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
if error != nil
{
completion(nil, nil, false)
}
else
{
completion(data, nil, true)
}
}
task.resume()
}
else
{
completion(nil, nil, false)
}
}
}

// MARK: - get user list
func getUserList(param : [String:Any]) {
NetworkManager.shared.load(path: APIEndPoint.homeURL, method: .get, params: param) { (data, error, response) in
if response!
{
guard let responseData = data else {
return
}
do {
print(responseData)
let jsonData = try JSONSerialization.jsonObject(with: responseData, options: .mutableContainers)
print(jsonData)
let apiResponse = try JSONDecoder().decode(User.self, from: responseData)
self.userList = apiResponse
self.delegate?.getUserListCompleted()
}catch {
print(error)
}
}
}
}

// MARK : Declare base URL
enum NetworkEnvironment {
case qa
case production
}
class APIEndPoint {
// MARK : Returning base URL
static var environmentBaseURL : String {
switch NetworkManager.environment {
case .production: return ""
case .qa: return "https://www.jsonkeeper.com/"
}
}
// MARK : Finally seted base URL for the end point
static var baseURL: String {
return environmentBaseURL
}
// MARK : List of sub URLs
static let homeURL = "b/12D8"
}

import SystemConfiguration
import Foundation
public enum ReachabilityError: Error {
case FailedToCreateWithAddress(sockaddr_in)
case FailedToCreateWithHostname(String)
case UnableToSetCallback
case UnableToSetDispatchQueue
case UnableToGetInitialFlags
}
#available(*, unavailable, renamed: "Notification.Name.reachabilityChanged")
public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification")
public extension Notification.Name {
static let reachabilityChanged = Notification.Name("reachabilityChanged")
}
public class Reachability {
public typealias NetworkReachable = (Reachability) -> ()
public typealias NetworkUnreachable = (Reachability) -> ()
#available(*, unavailable, renamed: "Connection")
public enum NetworkStatus: CustomStringConvertible {
case notReachable, reachableViaWiFi, reachableViaWWAN
public var description: String {
switch self {
case .reachableViaWWAN: return "Cellular"
case .reachableViaWiFi: return "WiFi"
case .notReachable: return "No Connection"
}
}
}
public enum Connection: CustomStringConvertible {
case none, wifi, cellular
public var description: String {
switch self {
case .cellular: return "Cellular"
case .wifi: return "WiFi"
case .none: return "No Connection"
}
}
}
public var whenReachable: NetworkReachable?
public var whenUnreachable: NetworkUnreachable?
#available(*, deprecated, renamed: "allowsCellularConnection")
public let reachableOnWWAN: Bool = true
/// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`)
public var allowsCellularConnection: Bool
// The notification center on which "reachability changed" events are being posted
public var notificationCenter: NotificationCenter = NotificationCenter.default
#available(*, deprecated, renamed: "connection.description")
public var currentReachabilityString: String {
return "\(connection)"
}
#available(*, unavailable, renamed: "connection")
public var currentReachabilityStatus: Connection {
return connection
}
public var connection: Connection {
if flags == nil {
try? setReachabilityFlags()
}
switch flags?.connection {
case .none?, nil: return .none
case .cellular?: return allowsCellularConnection ? .cellular : .none
case .wifi?: return .wifi
}
}
fileprivate var isRunningOnDevice: Bool = {
#if targetEnvironment(simulator)
return false
#else
return true
#endif
}()
fileprivate var notifierRunning = false
fileprivate let reachabilityRef: SCNetworkReachability
fileprivate let reachabilitySerialQueue: DispatchQueue
fileprivate(set) var flags: SCNetworkReachabilityFlags? {
didSet {
guard flags != oldValue else { return }
reachabilityChanged()
}
}
required public init(reachabilityRef: SCNetworkReachability, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
self.allowsCellularConnection = true
self.reachabilityRef = reachabilityRef
self.reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", qos: queueQoS, target: targetQueue)
}
public convenience init?(hostname: String, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil }
self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue)
}
public convenience init?(queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
var zeroAddress = sockaddr()
zeroAddress.sa_len = UInt8(MemoryLayout<sockaddr>.size)
zeroAddress.sa_family = sa_family_t(AF_INET)
guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { return nil }
self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue)
}
deinit {
stopNotifier()
}
}
public extension Reachability {
// MARK: - *** Notifier methods ***
func startNotifier() throws {
guard !notifierRunning else { return }
let callback: SCNetworkReachabilityCallBack = { (reachability, flags, info) in
guard let info = info else { return }
let reachability = Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue()
reachability.flags = flags
}
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = UnsafeMutableRawPointer(Unmanaged<Reachability>.passUnretained(self).toOpaque())
if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) {
stopNotifier()
throw ReachabilityError.UnableToSetCallback
}
if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) {
stopNotifier()
throw ReachabilityError.UnableToSetDispatchQueue
}
// Perform an initial check
try setReachabilityFlags()
notifierRunning = true
}
func stopNotifier() {
defer { notifierRunning = false }
SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
}
// MARK: - *** Connection test methods ***
#available(*, deprecated, message: "Please use `connection != .none`")
var isReachable: Bool {
return connection != .none
}
#available(*, deprecated, message: "Please use `connection == .cellular`")
var isReachableViaWWAN: Bool {
// Check we're not on the simulator, we're REACHABLE and check we're on WWAN
return connection == .cellular
}
#available(*, deprecated, message: "Please use `connection == .wifi`")
var isReachableViaWiFi: Bool {
return connection == .wifi
}
var description: String {
guard let flags = flags else { return "unavailable flags" }
let W = isRunningOnDevice ? (flags.isOnWWANFlagSet ? "W" : "-") : "X"
let R = flags.isReachableFlagSet ? "R" : "-"
let c = flags.isConnectionRequiredFlagSet ? "c" : "-"
let t = flags.isTransientConnectionFlagSet ? "t" : "-"
let i = flags.isInterventionRequiredFlagSet ? "i" : "-"
let C = flags.isConnectionOnTrafficFlagSet ? "C" : "-"
let D = flags.isConnectionOnDemandFlagSet ? "D" : "-"
let l = flags.isLocalAddressFlagSet ? "l" : "-"
let d = flags.isDirectFlagSet ? "d" : "-"
return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
}
}
fileprivate extension Reachability {
func setReachabilityFlags() throws {
try reachabilitySerialQueue.sync { [unowned self] in
var flags = SCNetworkReachabilityFlags()
if !SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags) {
self.stopNotifier()
throw ReachabilityError.UnableToGetInitialFlags
}
self.flags = flags
}
}
func reachabilityChanged() {
let block = connection != .none ? whenReachable : whenUnreachable
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
block?(self)
self.notificationCenter.post(name: .reachabilityChanged, object: self)
}
}
}
extension SCNetworkReachabilityFlags {
typealias Connection = Reachability.Connection
var connection: Connection {
guard isReachableFlagSet else { return .none }
// If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
#if targetEnvironment(simulator)
return .wifi
#else
var connection = Connection.none
if !isConnectionRequiredFlagSet {
connection = .wifi
}
if isConnectionOnTrafficOrDemandFlagSet {
if !isInterventionRequiredFlagSet {
connection = .wifi
}
}
if isOnWWANFlagSet {
connection = .cellular
}
return connection
#endif
}
var isOnWWANFlagSet: Bool {
#if os(iOS)
return contains(.isWWAN)
#else
return false
#endif
}
var isReachableFlagSet: Bool {
return contains(.reachable)
}
var isConnectionRequiredFlagSet: Bool {
return contains(.connectionRequired)
}
var isInterventionRequiredFlagSet: Bool {
return contains(.interventionRequired)
}
var isConnectionOnTrafficFlagSet: Bool {
return contains(.connectionOnTraffic)
}
var isConnectionOnDemandFlagSet: Bool {
return contains(.connectionOnDemand)
}
var isConnectionOnTrafficOrDemandFlagSet: Bool {
return !intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty
}
var isTransientConnectionFlagSet: Bool {
return contains(.transientConnection)
}
var isLocalAddressFlagSet: Bool {
return contains(.isLocalAddress)
}
var isDirectFlagSet: Bool {
return contains(.isDirect)
}
var isConnectionRequiredAndTransientFlagSet: Bool {
return intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]
}
}

import Foundation
// MARK : Declare API request type
enum RequestMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
}

Related

How to show and hide loader on Network layer in swiftUI?

I have a Global Singleton network class which handles the response of the API. I want to handle the loader from this class I don't want to manage from each ViewModel individually. I just want to toggle variabe "isLoading" in the ContentView from my global Network class "APIService" But no idea how would I do that?
class APIService {
static let shared = APIService()
private let timeInterval = 60
func makeApiTypeRequest<T: Codable>(
url: String,
param: [String: Any]? = nil,
methodType: HttpMethodType,
expecting: T.Type,
passToken: Bool = true,
completion: #escaping (Result<T, Error>)->Void) {
guard let url = URL(string: url) else {
return
}
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: TimeInterval(timeInterval))
if let param = param {
let finalData = try? JSONSerialization.data(withJSONObject: param)
request.httpBody = finalData
}
request.httpMethod = methodType.rawValue //"post"
request.addValue("application/json", forHTTPHeaderField: "content-type")
if passToken {
let data = KeychainHelper.standard.read(service: Constant.tokenKey)
if let data = data {
let accessToken = String(data: data, encoding: .utf8)
if let accessToken, accessToken != "" {
let headers = ["Content-Type": "application/json", "Authorization": "Bearer " + accessToken]
request.allHTTPHeaderFields = headers
}
}
}
URLSession.shared.dataTask(with: request) { (data,response,error) in
do {
if let error = error {
completion(.failure(error))
return
}
if let data = data {
if let httpStatus = response as? HTTPURLResponse {
switch httpStatus.statusCode {
case 400:
completion(.failure(CustomError.forbidden))
case 200:
let respObj = try JSONDecoder().decode(T.self, from: data)
completion(.success(respObj))
case 500:
// token expired
DispatchQueue.main.async {
NotificationCenter.default.post(name: Notification.Name("test.token.expired"), object: nil)
}
completion(.failure(CustomError.tokenExpired))
default:
completion(.failure(CustomError.unknown))
}
}
} else {
completion(.failure(CustomError.unknown))
print("no data found")
}
} catch(let error) {
completion(.failure(CustomError.unknown))
print("Error A123 \(error.localizedDescription)")
}
}.resume()
}
}
ContenView
struct ContentView: View {
#EnvironmentObject var session: SessionManager
#State var showMenu: Bool = false
#State var isLoading: Bool = false
#State var isAppInstalled: Bool = false
let pub = NotificationCenter.default
.publisher(for: NSNotification.Name("test.token.expired"))
var body: some View {
return NavigationView {
ZStack {
if isAppInstalled {
if session.currentUserState == .loggedIn {
GeometryReader { geometry in
TabContainer(showMenu: $showMenu)
.frame(width: geometry.size.width, height: geometry.size.height)
if self.showMenu {
MenuView()
.frame(width: geometry.size.width/2)
}
}
} else {
LoginView(showMenu: $showMenu)
}
if isLoading {
Loading()
.ignoresSafeArea()
}
} else {
GettingStartedView(isInstalled: $isAppInstalled)
}
}
.onAppear {
if GettingStartedPersistance.shared.isAppAlreadyInstalled() {
isAppInstalled = true
}
}
.onReceive(pub) { (output) in
self.session.signout()
}
}//.allowsHitTesting(!self.isLoading)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Sample ViewModel
class LoginViewModel: ObservableObject {
#Published var loginResponse:LoginResponseModel?
#Published var isNavigatedToDashBaord = false
#Published var errorMessage = ""
#Published var isErrorPresented = false
private func validateInputFields(email: String, password: String)->Bool {
if email.isEmpty {
errorOccured(err: "Please enter email")
return false
}
if password.isEmpty {
errorOccured(err: "Please enter password")
return false
}
if !Utils.shared.isValidEmail(strToValidate: email) {
errorOccured(err: "Please enter a valid email")
return false
}
if password.count < 5 {
errorOccured(err: "Password must be atleast 5 characters long")
return false
}
return true
}
func login(email: String, password: String, completion: #escaping (()->())) {
if !validateInputFields(email: email, password: password) {
completion()
return
}
var param = [String: Any]()
param["username"] = email
param["password"] = password
APIService.shared.makeApiTypeRequest(url: APIURLConstant.loginUrl, param: param, methodType: .post, expecting: LoginResponseModel.self, passToken: false) { result in
switch result {
case .success(let loginData):
if loginData.authToken != "" && loginData.authToken != nil {
DispatchQueue.main.async {
self.saveUserToken(token: loginData.authToken ?? "")
self.loginResponse = loginData
self.successlogIn()
completion()
}
} else {
self.errorOccured(err: "Login failled")
completion()
}
case .failure( _):
self.errorOccured(err: "Login failled")
completion()
}
}
}
private func errorOccured(err: String) {
DispatchQueue.main.async {
self.errorMessage = err
self.isErrorPresented = true
}
}
private func successlogIn() {
DispatchQueue.main.async {
self.errorMessage = ""
self.isErrorPresented = false
self.isNavigatedToDashBaord = true
}
}
private func saveUserToken(token: String) {
let accessToken = Data(token.utf8)
KeychainHelper.standard.save(accessToken, service: Constant.tokenKey)
}
}

How to use dependency injection networking with MVVM iOS?

I'm currently trying to find out what's the best networking architecture for MVVM applications. I couldn't find many resources and decided to go with dependency injection based architecture as per the very less reading resources that I have found.
I'm not using any 3rd party for web service testing and whatever the networking architecture that I use should be supported to mock the web services as well.
I have found out the DI based networking architecture which was build intended to achieve unit testing according to the Test Pyramid concept at Apple WWDC 2018.
So I have build my networking layer according to that. Following is my APIHandler class.
public enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
}
protocol RequestHandler {
associatedtype RequestDataType
func makeRequest(from data:RequestDataType) -> URLRequest?
}
protocol ResponseHandler {
associatedtype ResponseDataType
func parseResponse(data: Data, response: HTTPURLResponse) throws -> ResponseDataType
}
typealias APIHandler = RequestHandler & ResponseHandler
Followings are my extensions for request handler and response handler.
extension RequestHandler {
func setQueryParams(parameters:[String: Any], url: URL) -> URL {
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
components?.queryItems = parameters.map { element in URLQueryItem(name: element.key, value: String(describing: element.value) ) }
return components?.url ?? url
}
func setDefaultHeaders(request: inout URLRequest) {
request.setValue(APIHeaders.contentTypeValue, forHTTPHeaderField: APIHeaders.kContentType)
}
}
struct ServiceError: Error,Decodable {
let httpStatus: Int
let message: String
}
extension ResponseHandler {
func defaultParseResponse<T: Decodable>(data: Data, response: HTTPURLResponse) throws -> T {
let jsonDecoder = JSONDecoder()
if response.statusCode == 200 {
do {
let body = try jsonDecoder.decode(T.self, from: data)
return body
} catch {
throw ServiceError(httpStatus: response.statusCode, message: error.localizedDescription)
}
} else {
var message = "Generel.Message.Error".localized()
do {
let body = try jsonDecoder.decode(APIError.self, from: data)
if let err = body.fault?.faultstring {
message = err
}
} catch {
throw ServiceError(httpStatus: response.statusCode, message: error.localizedDescription)
}
throw ServiceError(httpStatus: response.statusCode, message:message)
}
}
}
Then I loaded my request using APILoader as follows.
struct APILoader<T: APIHandler> {
var apiHandler: T
var urlSession: URLSession
init(apiHandler: T, urlSession: URLSession = .shared) {
self.apiHandler = apiHandler
self.urlSession = urlSession
}
func loadAPIRequest(requestData: T.RequestDataType, completionHandler: #escaping (Int, T.ResponseDataType?, ServiceError?) -> ()) {
if let urlRequest = apiHandler.makeRequest(from: requestData) {
urlSession.dataTask(with: urlRequest) { (data, response, error) in
if let httpResponse = response as? HTTPURLResponse {
guard error == nil else {
completionHandler(httpResponse.statusCode, nil, ServiceError(httpStatus: httpResponse.statusCode, message: error?.localizedDescription ?? "General.Error.Unknown".localized()))
return
}
guard let responseData = data else {
completionHandler(httpResponse.statusCode,nil, ServiceError(httpStatus: httpResponse.statusCode, message: error?.localizedDescription ?? "General.Error.Unknown".localized()))
return
}
do {
let parsedResponse = try self.apiHandler.parseResponse(data: responseData, response: httpResponse)
completionHandler(httpResponse.statusCode, parsedResponse, nil)
} catch {
completionHandler(httpResponse.statusCode, nil, ServiceError(httpStatus: httpResponse.statusCode, message: CommonUtil.shared.decodeError(err: error)))
}
} else {
guard error == nil else {
completionHandler(-1, nil, ServiceError(httpStatus: -1, message: error?.localizedDescription ?? "General.Error.Unknown".localized()))
return
}
completionHandler(-1, nil, ServiceError(httpStatus: -1, message: "General.Error.Unknown".localized()))
}
}.resume()
}
}
}
To call my API request. I have created a separate service class and call the web service as follows.
struct TopStoriesAPI: APIHandler {
func makeRequest(from param: [String: Any]) -> URLRequest? {
let urlString = APIPath().topStories
if var url = URL(string: urlString) {
if param.count > 0 {
url = setQueryParams(parameters: param, url: url)
}
var urlRequest = URLRequest(url: url)
setDefaultHeaders(request: &urlRequest)
urlRequest.httpMethod = HTTPMethod.get.rawValue
return urlRequest
}
return nil
}
func parseResponse(data: Data, response: HTTPURLResponse) throws -> StoriesResponse {
return try defaultParseResponse(data: data,response: response)
}
}
For syncing both my actual web service methods and mock services, I have created an API Client protocol like follows.
protocol APIClientProtocol {
func fetchTopStories(completion: #escaping (StoriesResponse?, ServiceError?) -> ())
}
Then I have derived APIServices class using my APIClient protocol and implemented my all the APIs there by passing requests and responses. My dependency injection was getting over at this point.
public class APIServices: APIClientProtocol {
func fetchTopStories(completion: #escaping (StoriesResponse?, ServiceError?) -> ()) {
let request = TopStoriesAPI()
let params = [Params.kApiKey.rawValue : CommonUtil.shared.NytApiKey()]
let apiLoader = APILoader(apiHandler: request)
apiLoader.loadAPIRequest(requestData: params) { (status, model, error) in
if let _ = error {
completion(nil, error)
} else {
completion(model, nil)
}
}
}
}
Then I have called this API request on my viewModel class like this.
func fetchTopStories(completion: #escaping (Bool) -> ()) {
APIServices().fetchTopStories { response, error in
if let _ = error {
self.errorMsg = error?.message ?? "Generel.Message.Error".localized()
completion(false)
} else {
if let data = response?.results {
if data.count > 0 {
self.stories.removeAll()
self.stories = data
completion(true)
} else {
self.errorMsg = "Generel.NoData.Error".localized()
completion(false)
}
} else {
self.errorMsg = "Generel.NoData.Error".localized()
completion(false)
}
}
}
}
Finally call the viewModel's API call from my viewController (View).
func fetchData() {
showActivityIndicator()
self.viewModel.fetchTopStories { success in
self.hideActivityIndicator()
DispatchQueue.main.async {
if self.pullToRefresh {
self.pullToRefresh = false
self.refreshControl.endRefreshing()
}
if success {
if self.imgNoData != nil {
self.imgNoData?.isHidden = true
}
self.tableView.reloadData()
} else {
CommonUtil.shared.showToast(message: self.viewModel.errorMsg, success: false)
self.imgNoData = {
let viewWidth = self.tableView.frame.size.width
let imageWidth = viewWidth - 50
let iv = UIImageView()
iv.frame = CGRect(x: 25, y: 100, width: imageWidth, height: imageWidth)
iv.image = UIImage(named:"no-data")
iv.contentMode = .scaleAspectFit
return iv
}()
self.imgNoData?.isHidden = false
self.tableView.addSubview(self.imgNoData!)
}
}
}
}
So I have following questions regarding this approach.
I have ended the dependency injection from my APIServices class.
Should I bring this all the way up to my viewController class API Call and
pass request and params variables from there ?
Are there any performance issues in this approach and any
improvement to be done?
My personal preference is to end all the data related stuffs from the viewModel level and just call the API without passing any parameters from the viewController. Does it wrong? If we pass parameters from the view controller class as per the pure dependency injection way, does it harm to the MVVM architecture?

Getting statusCode other than 200...299 in HTTPURLResponse of URLSession

the following is my APIManager code, I'm using it in all my apps. But sometimes, the guard statement fails in connectToServer function, which means the statusCode of HTTPURLResponse other than 200...299 and the thing here is even after getting statusCode other than 200...299 my record got inserted into DB. I don't know what happens.
I thought that the cause of this behavior is from ServerURL, because I'm using a dev server with IP address http://00.000.0.000/ without security. Once I moved it to domain as https://XXX.XXXXXXXXXX.XXXXX/ it is working fine. Can you help me to figure out this?
And also will it supports for asynchronous calls?
import UIKit
struct APIResponse : Decodable {
let status : Bool
let message : String
let extra: String?
}
internal let BASE_URL = "http://00.000.0.00/app/v0_1/api/" // Example server URL
enum APIPath: String {
case registration = "registration"
case login = "login"
case getProfile = "get_profile"
func directURL() -> URL? {
let urlPath = BASE_URL + self.rawValue
return URL(string: urlPath)
}
func extendedURL(using parameters: [String: Any]) -> URL? {
let extendedPath = parameters.map { $0.key + "=" + "\($0.value)" }.joined(separator: "&")
let urlPath = BASE_URL + self.rawValue + "?" + extendedPath
return URL(string: urlPath)
}
}
enum APIMethod: String {
case get = "GET"
case put = "PUT"
case post = "POST"
case patch = "PATCH"
case delete = "DELETE"
}
enum APIHeaders {
case user
case app
var authorization: [String:String] {
let acceptLanguage = UserDefaults.standard.value(forKey: UDKeys.appleLanguage) as? String ?? ""
if self == .user {
let token = UserDefaults.standard.value(forKey: UDKeys.userToken) as? String ?? ""
return ["Content-Type": "application/json", "Accept": "application/json", "Accept-Language": acceptLanguage, "Token" : token]
}
return ["Content-Type": "application/json", "Accept": "application/json", "Accept-Language": acceptLanguage]
}
}
struct APIRequest {
var url: URL?
var method: String
var parameters: Data?
var headers: [String:String]
init(path: APIPath, method: APIMethod, headers: APIHeaders) {
self.url = path.directURL()
self.method = method.rawValue
self.headers = headers.authorization
}
init(path: APIPath, parameters: [String: Any], method: APIMethod, headers: APIHeaders) {
self.url = path.extendedURL(using: parameters)
self.method = method.rawValue
self.headers = headers.authorization
}
init(path: APIPath, method: APIMethod, body: [String:Any], headers: APIHeaders) {
self.url = path.directURL()
self.method = method.rawValue
self.parameters = try? JSONSerialization.data(withJSONObject: body, options: .sortedKeys)
self.headers = headers.authorization
}
init<Encode: Encodable>(path: APIPath, method: APIMethod, body: Encode, headers: APIHeaders) {
self.url = path.directURL()
self.method = method.rawValue
self.parameters = try? JSONEncoder().encode(body)
self.headers = headers.authorization
}
}
struct APIError: Error {
let reason: String
let code: String?
init(reason: String, code: String? = nil) {
self.reason = reason
self.code = code
}
}
struct APIDispatcher {
static let instance = APIDispatcher()
private init() {}
func dispatch<Decode: Decodable>(request: APIRequest, response: Decode.Type, result: #escaping (Result<Decode, APIError>) -> ()) {
DispatchQueue(label: "queue", attributes: .concurrent).async {
self.connectToServer(with: request) { (resultant) in
switch resultant {
case .success(let data):
do {
let decoded = try JSONDecoder().decode(response, from: data)
DispatchQueue.main.async {
result(.success(decoded))
}
} catch let decodedError {
print("[Decoded Error]: ", decodedError)
do {
let apiResponse = try JSONDecoder().decode(APIResponse.self, from: data)
let apiError = APIError(reason: apiResponse.message, code: apiResponse.extra)
DispatchQueue.main.async {
result(.failure(apiError))
}
} catch {
let apiError = APIError(reason: decodedError.localizedDescription)
DispatchQueue.main.async {
result(.failure(apiError))
}
}
}
case .failure(let error):
DispatchQueue.main.async {
result(.failure(error))
}
}
}
}
}
func dispatch(request: APIRequest, result: #escaping (Result<Dictionary<String,Any>, APIError>) -> ()) {
DispatchQueue(label: "queue", attributes: .concurrent).async {
self.connectToServer(with: request) { (resultant) in
switch resultant {
case .success(let data):
do {
let serialized = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! Dictionary<String,Any>
DispatchQueue.main.async {
result(.success(serialized))
}
} catch {
let error = APIError(reason: error.localizedDescription)
DispatchQueue.main.async {
result(.failure(error))
}
}
case .failure(let error):
DispatchQueue.main.async {
result(.failure(error))
}
}
}
}
}
private func connectToServer(with request: APIRequest, result: #escaping (Result<Data, APIError>) -> ()) {
guard let url = request.url else {
let error = APIError(reason: "Invalid URL")
result(.failure(error))
return
}
var urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30)
urlRequest.httpMethod = request.method
urlRequest.httpBody = request.parameters
urlRequest.allHTTPHeaderFields = request.headers
print(urlRequest)
let urlSessionConfiguration = URLSessionConfiguration.default
urlSessionConfiguration.waitsForConnectivity = false
urlSessionConfiguration.timeoutIntervalForRequest = 30
urlSessionConfiguration.timeoutIntervalForResource = 60
let urlSession = URLSession(configuration: urlSessionConfiguration)
urlSession.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
let error = APIError(reason: error.localizedDescription)
result(.failure(error))
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
let error = APIError(reason: "Server Error")
result(.failure(error))
return
}
if let data = data {
result(.success(data))
}
}.resume()
}
}
Note: BASE_URL and APIResponse might be vary according to project.
I'm using it as
func login() {
self.startLoading()
let body = ["mobile_number": phoneNumberTF.text!, "password" : passwordTF.text!, "uuid" : UIDevice.current.identifierForVendor!.uuidString]
let apiRequest = APIRequest(path: .login, method: .post, body: body, headers: .app)
APIDispatcher.instance.dispatch(request: apiRequest) { result in
self.stopLoading()
switch result {
case .success(let response):
break
case .failure(let error):
break
}
}
}
EDIT: My bad I asked completely reverse on statsCode now I modified it.

Alamofire is getting slow on HTTPS but fine in HTTP

In my App multiple Apis . i have 2 differnet server one is for HTTP and one for HTTPS
When i Run my App on Http : it works fine 1st time for each Api and 2 second time for each Api same response time .
But when i run App on https : for each APi first time taking extra time for each Api , then if i hit same Api again it is fast . Problem is why for first time for each APi is slow or taking extra time . But thing is not happened with Android App .
here is My Request builder Class : URLRequestBuilder.swift
import Foundation
import Alamofire
protocol URLRequestBuilder: URLRequestConvertible, APIRequestHandler {
var mainURL: URL { get }
var requestURL: URL { get }
var path: String { get }
var parameters: Parameters? { get }
var method: HTTPMethod { get }
var encoding: ParameterEncoding { get }
var urlRequest: URLRequest { get }
}
extension URLRequestBuilder {
var encoding: ParameterEncoding {
switch method {
case .get:
return URLEncoding.default
default:
return JSONEncoding.default
}
}
var mainURL: URL {
return URL(string: SERVER_URL)!
}
var requestURL: URL {
var fullURL = mainURL.absoluteString + path
if L102Language.currentAppleLanguage() == "ar" && path.contains("?") {
fullURL = fullURL + "&blLocaleCode=ar"
} else if L102Language.currentAppleLanguage() == "ar" {
fullURL = fullURL + "?blLocaleCode=ar"
}
let urlComponents = URLComponents(string: fullURL)!
return urlComponents.url!
}
var urlRequest: URLRequest {
var request = URLRequest(url: requestURL)
request.httpMethod = method.rawValue
if UserDefaults.standard.isUserLoggedIn() {
request.setValue(UserDefaults.standard.getAccessToken(), forHTTPHeaderField: NETWORK_ACCESS_TOKEN)
}
request.setValue(NETWORK_REQUEST_TYPE, forHTTPHeaderField: NETWORK_ACCEPT)
request.setValue(NETWORK_REQUEST_TYPE, forHTTPHeaderField: NETWORK_CONTENT_TYPE)
//request.cachePolicy = .useProtocolCachePolicy
return request
}
func asURLRequest() throws -> URLRequest {
return try encoding.encode(urlRequest, with: parameters)
}
}
final class NetworkClient {
let evaluators = [
"somehttpsURL.com": ServerTrustPolicy.pinCertificates(
certificates: [Certificates.stackExchange],
validateCertificateChain: true,
validateHost: true)
]
let session: SessionManager
// 2
private init() {
session = SessionManager(
serverTrustPolicyManager: ServerTrustPolicyManager(policies: evaluators))
}
// MARK: - Static Definitions
private static let shared = NetworkClient()
static func request(_ convertible: URLRequestConvertible) -> DataRequest {
return shared.session.request(convertible)
}
}
struct Certificates {
static let stackExchange =
Certificates.certificate(filename: "certificate")
private static func certificate(filename: String) -> SecCertificate {
let filePath = Bundle.main.path(forResource: filename, ofType: "der")!
let data = try? Data(contentsOf: URL(fileURLWithPath: filePath))
let certificate = SecCertificateCreateWithData(nil, data! as CFData)!
return certificate
}
}
And my Request Handler Class is :
extension APIRequestHandler where Self : URLRequestBuilder {
// For Response Object
func send<T: AnyObject>(modelType: T.Type, data: [UIImage]? = nil, success: #escaping ( _ servicResponse: AnyObject) -> Void, fail: #escaping ( _ error: NSError) -> Void, showHUD: Bool) where T: Mappable {
if let data = data {
uploadToServerWith(modelType: modelType, images: data, request: self, parameters: self.parameters, success: success, fail: fail)
} else {
//print(requestURL.absoluteString)
// NetworkClient.
request(self).authenticate(user: APIAuthencationUserName, password: APIAuthencationPassword).validate().responseObject { (response: DataResponse<T>) in
switch response.result {
case .success(let objectData):
success(objectData)
break
case .failure(let error):
print(error.localizedDescription)
if error.localizedDescription == RefreshTokenFailed {
self.getAccessTokenAPI(completion: { (value) in
if value == TOKEN_SAVED {
self.send(modelType: modelType, success: success, fail: fail, showHUD: showHUD)
return
}else {
fail(error as NSError)
}
})
} else {
fail(error as NSError)
}
}
}
}
}
}

Get full URL from short URL in Swift on iOS

Given a short URL https://itun.es/us/JB7h_, How do you expand it into the full URL? e.g. https://music.apple.com/us/album/blackstar/1059043043
Extension
extension URL {
func getExpandedURL() async throws -> Result<URL, Error> {
var request = URLRequest(url: self)
request.httpMethod = "HEAD"
let (_, response) = try await URLSession.shared.data(for: request)
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
throw URLError.unableToExpand
}
if let expandedURL = response.url {
return .success(expandedURL)
} else {
throw URLError.unableToExpand
}
}
enum URLError: Error {
case unableToExpand
}
}
Crude Demo
struct ContentView: View {
let shortURL = URL(string: "https://itun.es/us/JB7h_")
#State var expandedURLResult: Result<URL, Error>?
var body: some View {
Form {
Section("Short URL") {
Text(shortURL?.description ?? "")
}
Section("Long URL") {
switch expandedURLResult {
case .some(.success(let expandedURL)):
Text(expandedURL.description)
case .none:
Text("Waiting")
case .some(.failure(let error)):
Text(error.localizedDescription)
}
}
}
.task {
do {
expandedURLResult = try await shortURL?.getExpandedURL()
} catch {
expandedURLResult = .failure(error)
}
}
}
}
The final resolved URL will be returned to you in the NSURLResponse: response.URL.
You should also make sure to use the HTTP HEAD method to avoid downloading unnecessary data (since you don't care about the resource body).
Swift 4.2 Updated :
extension URL {
func resolveWithCompletionHandler(completion: #escaping (URL) -> Void) {
let originalURL = self
var req = URLRequest(url: originalURL)
req.httpMethod = "HEAD"
URLSession.shared.dataTask(with: req) { body, response, error in
completion(response?.url ?? originalURL)
}.resume()
}
Older Swift Versions:
extension NSURL
{
func resolveWithCompletionHandler(completion: NSURL -> Void)
{
let originalURL = self
let req = NSMutableURLRequest(URL: originalURL)
req.HTTPMethod = "HEAD"
NSURLSession.sharedSession().dataTaskWithRequest(req) { body, response, error in
completion(response?.URL ?? originalURL)
}.resume()
}
}
// Example:
NSURL(string: "https://itun.es/us/JB7h_")!.resolveWithCompletionHandler {
print("resolved to \($0)") // prints https://itunes.apple.com/us/album/blackstar/id1059043043
}

Resources