Project Setup

This commit is contained in:
manaknightdev
2023-03-13 23:20:27 +05:30
commit d4da2b5e02
178 changed files with 29139 additions and 0 deletions
@@ -0,0 +1,311 @@
//
// Request.swift
// AlamofireObjectMapper
//
// Created by Tristan Himmelman on 2015-04-30.
//
// The MIT License (MIT)
//
// Copyright (c) 2014-2015 Tristan Himmelman
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import Alamofire
import ObjectMapper
extension DataRequest {
enum ErrorCode: Int {
case noData = 1
case dataSerializationFailed = 2
}
/// Utility function for extracting JSON from response
internal static func processResponse(request: URLRequest?, response: HTTPURLResponse?, data: Data?, keyPath: String?) -> Any? {
let jsonResponseSerializer = JSONResponseSerializer(options: .allowFragments)
if let result = try? jsonResponseSerializer.serialize(request: request, response: response, data: data, error: nil) {
let JSON: Any?
if let keyPath = keyPath , keyPath.isEmpty == false {
JSON = (result as AnyObject?)?.value(forKeyPath: keyPath)
} else {
JSON = result
}
return JSON
}
return nil
}
internal static func newError(_ code: ErrorCode, failureReason: String) -> NSError {
let errorDomain = "com.alamofireobjectmapper.error"
let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
let returnError = NSError(domain: errorDomain, code: code.rawValue, userInfo: userInfo)
return returnError
}
/// Utility function for checking for errors in response
internal static func checkResponseForError(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) -> Error? {
if let error = error {
return error
}
guard let _ = data else {
let failureReason = "Data could not be serialized. Input data was nil."
let error = newError(.noData, failureReason: failureReason)
return error
}
return nil
}
/// BaseMappable Object Serializer
public static func ObjectMapperSerializer<T: BaseMappable>(_ keyPath: String?, mapToObject object: T? = nil, context: MapContext? = nil) -> MappableResponseSerializer<T> {
return MappableResponseSerializer(keyPath, mapToObject: object, context: context, serializeCallback: {
request, response, data, error in
let JSONObject = processResponse(request: request, response: response, data: data, keyPath: keyPath)
if let object = object {
_ = Mapper<T>(context: context, shouldIncludeNilValues: false).map(JSONObject: JSONObject, toObject: object)
return object
} else if let parsedObject = Mapper<T>(context: context, shouldIncludeNilValues: false).map(JSONObject: JSONObject){
return parsedObject
}
let failureReason = "ObjectMapper failed to serialize response."
throw AFError.responseSerializationFailed(reason: .decodingFailed(error: newError(.dataSerializationFailed, failureReason: failureReason)))
})
}
/// ImmutableMappable Array Serializer
public static func ObjectMapperImmutableSerializer<T: ImmutableMappable>(_ keyPath: String?, context: MapContext? = nil) -> MappableResponseSerializer<T> {
return MappableResponseSerializer(keyPath, context: context, serializeCallback: {
request, response, data, error in
let JSONObject = processResponse(request: request, response: response, data: data, keyPath: keyPath)
if let JSONObject = JSONObject,
let parsedObject = (try? Mapper<T>(context: context, shouldIncludeNilValues: false).map(JSONObject: JSONObject) as T) {
return parsedObject
} else {
let failureReason = "ObjectMapper failed to serialize response."
throw AFError.responseSerializationFailed(reason: .decodingFailed(error: newError(.dataSerializationFailed, failureReason: failureReason)))
}
})
}
/**
Adds a handler to be called once the request has finished.
- parameter queue: The queue on which the completion handler is dispatched.
- parameter keyPath: The key path where object mapping should be performed
- parameter object: An object to perform the mapping on to
- parameter completionHandler: A closure to be executed once the request has finished and the data has been mapped by ObjectMapper.
- returns: The request.
*/
@discardableResult
public func responseObject<T: BaseMappable>(queue: DispatchQueue = .main, keyPath: String? = nil, mapToObject object: T? = nil, context: MapContext? = nil, completionHandler: @escaping (AFDataResponse<T>) -> Void) -> Self {
return response(queue: queue, responseSerializer: DataRequest.ObjectMapperSerializer(keyPath, mapToObject: object, context: context), completionHandler: completionHandler)
}
@discardableResult
public func responseObject<T: ImmutableMappable>(queue: DispatchQueue = .main, keyPath: String? = nil, mapToObject object: T? = nil, context: MapContext? = nil, completionHandler: @escaping (AFDataResponse<T>) -> Void) -> Self {
return response(queue: queue, responseSerializer: DataRequest.ObjectMapperImmutableSerializer(keyPath, context: context), completionHandler: completionHandler)
}
/// BaseMappable Array Serializer
public static func ObjectMapperArraySerializer<T: BaseMappable>(_ keyPath: String?, context: MapContext? = nil) -> MappableArrayResponseSerializer<T> {
return MappableArrayResponseSerializer(keyPath, context: context, serializeCallback: {
request, response, data, error in
let JSONObject = processResponse(request: request, response: response, data: data, keyPath: keyPath)
if let parsedObject = Mapper<T>(context: context, shouldIncludeNilValues: false).mapArray(JSONObject: JSONObject){
return parsedObject
}
let failureReason = "ObjectMapper failed to serialize response."
throw AFError.responseSerializationFailed(reason: .decodingFailed(error: newError(.dataSerializationFailed, failureReason: failureReason)))
})
}
/// ImmutableMappable Array Serializer
public static func ObjectMapperImmutableArraySerializer<T: ImmutableMappable>(_ keyPath: String?, context: MapContext? = nil) -> MappableArrayResponseSerializer<T> {
return MappableArrayResponseSerializer(keyPath, context: context, serializeCallback: {
request, response, data, error in
if let JSONObject = processResponse(request: request, response: response, data: data, keyPath: keyPath){
if let parsedObject = try? Mapper<T>(context: context, shouldIncludeNilValues: false).mapArray(JSONObject: JSONObject) as [T] {
return parsedObject
}
}
let failureReason = "ObjectMapper failed to serialize response."
throw AFError.responseSerializationFailed(reason: .decodingFailed(error: newError(.dataSerializationFailed, failureReason: failureReason)))
})
}
/**
Adds a handler to be called once the request has finished. T: BaseMappable
- parameter queue: The queue on which the completion handler is dispatched.
- parameter keyPath: The key path where object mapping should be performed
- parameter completionHandler: A closure to be executed once the request has finished and the data has been mapped by ObjectMapper.
- returns: The request.
*/
@discardableResult
public func responseArray<T: BaseMappable>(queue: DispatchQueue = .main, keyPath: String? = nil, context: MapContext? = nil, completionHandler: @escaping (AFDataResponse<[T]>) -> Void) -> Self {
return response(queue: queue, responseSerializer: DataRequest.ObjectMapperArraySerializer(keyPath, context: context), completionHandler: completionHandler)
}
/**
Adds a handler to be called once the request has finished. T: ImmutableMappable
- parameter queue: The queue on which the completion handler is dispatched.
- parameter keyPath: The key path where object mapping should be performed
- parameter completionHandler: A closure to be executed once the request has finished and the data has been mapped by ObjectMapper.
- returns: The request.
*/
@discardableResult
public func responseArray<T: ImmutableMappable>(queue: DispatchQueue = .main, keyPath: String? = nil, context: MapContext? = nil, completionHandler: @escaping (AFDataResponse<[T]>) -> Void) -> Self {
return response(queue: queue, responseSerializer: DataRequest.ObjectMapperImmutableArraySerializer(keyPath, context: context), completionHandler: completionHandler)
}
}
public final class MappableResponseSerializer<T: BaseMappable>: ResponseSerializer {
/// The `JSONDecoder` instance used to decode responses.
public let decoder: DataDecoder = JSONDecoder()
/// HTTP response codes for which empty responses are allowed.
public let emptyResponseCodes: Set<Int>
/// HTTP request methods for which empty responses are allowed.
public let emptyRequestMethods: Set<HTTPMethod>
public let keyPath: String?
public let context: MapContext?
public let object: T?
public let serializeCallback: (URLRequest?,HTTPURLResponse?, Data?,Error?) throws -> T
/// Creates an instance using the values provided.
///
/// - Parameters:
/// - keyPath:
/// - object:
/// - context:
/// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. Defaults to
/// `[204, 205]`.
/// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. Defaults to `[.head]`.
/// - serializeCallback:
public init(_ keyPath: String?, mapToObject object: T? = nil, context: MapContext? = nil,
emptyResponseCodes: Set<Int> = MappableResponseSerializer.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = MappableResponseSerializer.defaultEmptyRequestMethods, serializeCallback: @escaping (URLRequest?,HTTPURLResponse?, Data?,Error?) throws -> T) {
self.emptyResponseCodes = emptyResponseCodes
self.emptyRequestMethods = emptyRequestMethods
self.keyPath = keyPath
self.context = context
self.object = object
self.serializeCallback = serializeCallback
}
public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> T {
guard error == nil else { throw error! }
guard let data = data, !data.isEmpty else {
guard emptyResponseAllowed(forRequest: request, response: response) else {
throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
}
guard let emptyValue = Empty.value as? T else {
throw AFError.responseSerializationFailed(reason: .invalidEmptyResponse(type: "\(T.self)"))
}
return emptyValue
}
return try self.serializeCallback(request, response, data, error)
}
}
public final class MappableArrayResponseSerializer<T: BaseMappable>: ResponseSerializer {
/// The `JSONDecoder` instance used to decode responses.
public let decoder: DataDecoder = JSONDecoder()
/// HTTP response codes for which empty responses are allowed.
public let emptyResponseCodes: Set<Int>
/// HTTP request methods for which empty responses are allowed.
public let emptyRequestMethods: Set<HTTPMethod>
public let keyPath: String?
public let context: MapContext?
public let serializeCallback: (URLRequest?,HTTPURLResponse?, Data?,Error?) throws -> [T]
/// Creates an instance using the values provided.
///
/// - Parameters:
/// - keyPath:
/// - context:
/// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. Defaults to
/// `[204, 205]`.
/// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. Defaults to `[.head]`.
/// - serializeCallback:
public init(_ keyPath: String?, context: MapContext? = nil, serializeCallback: @escaping (URLRequest?,HTTPURLResponse?, Data?,Error?) throws -> [T],
emptyResponseCodes: Set<Int> = MappableArrayResponseSerializer.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = MappableArrayResponseSerializer.defaultEmptyRequestMethods) {
self.emptyResponseCodes = emptyResponseCodes
self.emptyRequestMethods = emptyRequestMethods
self.keyPath = keyPath
self.context = context
self.serializeCallback = serializeCallback
}
public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> [T] {
guard error == nil else { throw error! }
guard let data = data, !data.isEmpty else {
guard emptyResponseAllowed(forRequest: request, response: response) else {
throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
}
// TODO / FIX - Empty Response JSON Decodable Array Fix - "Cast from empty always fails..."
guard let emptyValue = Empty.value as? [T] else {
throw AFError.responseSerializationFailed(reason: .invalidEmptyResponse(type: "\(T.self)"))
}
return emptyValue
}
return try self.serializeCallback(request, response, data, error)
}
}
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Tristan Himmelman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+193
View File
@@ -0,0 +1,193 @@
AlamofireObjectMapper
============
[![Build Status](https://travis-ci.org/tristanhimmelman/AlamofireObjectMapper.svg?branch=master)](https://travis-ci.org/tristanhimmelman/AlamofireObjectMapper)
[![CocoaPods](https://img.shields.io/cocoapods/v/AlamofireObjectMapper.svg)](https://github.com/tristanhimmelman/AlamofireObjectMapper)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
An extension to [Alamofire](https://github.com/Alamofire/Alamofire) which automatically converts JSON response data into swift objects using [ObjectMapper](https://github.com/Hearst-DD/ObjectMapper/).
# Usage
Given a URL which returns weather data in the following form:
```
{
"location": "Toronto, Canada",
"three_day_forecast": [
{
"conditions": "Partly cloudy",
"day" : "Monday",
"temperature": 20
},
{
"conditions": "Showers",
"day" : "Tuesday",
"temperature": 22
},
{
"conditions": "Sunny",
"day" : "Wednesday",
"temperature": 28
}
]
}
```
You can use the extension as the follows:
```swift
import AlamofireObjectMapper
let URL = "https://raw.githubusercontent.com/tristanhimmelman/AlamofireObjectMapper/d8bb95982be8a11a2308e779bb9a9707ebe42ede/sample_json"
Alamofire.request(URL).responseObject { (response: DataResponse<WeatherResponse>) in
let weatherResponse = response.result.value
print(weatherResponse?.location)
if let threeDayForecast = weatherResponse?.threeDayForecast {
for forecast in threeDayForecast {
print(forecast.day)
print(forecast.temperature)
}
}
}
```
The `WeatherResponse` object in the completion handler is a custom object which you define. The only requirement is that the object must conform to [ObjectMapper's](https://github.com/Hearst-DD/ObjectMapper/) `Mappable` protocol. In the above example, the `WeatherResponse` object looks like the following:
```swift
import ObjectMapper
class WeatherResponse: Mappable {
var location: String?
var threeDayForecast: [Forecast]?
required init?(map: Map){
}
func mapping(map: Map) {
location <- map["location"]
threeDayForecast <- map["three_day_forecast"]
}
}
class Forecast: Mappable {
var day: String?
var temperature: Int?
var conditions: String?
required init?(map: Map){
}
func mapping(map: Map) {
day <- map["day"]
temperature <- map["temperature"]
conditions <- map["conditions"]
}
}
```
The extension uses Generics to allow you to create your own custom response objects. Below is the `responseObject` function definition. Just replace `T` in the completionHandler with your custom response object and the extension handles the rest:
```swift
public func responseObject<T: BaseMappable>(queue: DispatchQueue? = nil, keyPath: String? = nil, mapToObject object: T? = nil, context: MapContext? = nil, completionHandler: @escaping (DataResponse<T>) -> Void) -> Self
```
The `responseObject` function has 4 optional parameters and a required completionHandler:
- `queue`: The queue on which the completion handler is dispatched.
- `keyPath`: The key path of the JSON where object mapping should be performed.
- `mapToObject`: An object to perform the mapping on to.
- `context`: A [context object](https://github.com/Hearst-DD/ObjectMapper/#mapping-context) that is passed to the mapping function.
- `completionHandler`: A closure to be executed once the request has finished and the data has been mapped by ObjectMapper.
### Easy Mapping of Nested Objects
AlamofireObjectMapper supports dot notation within keys for easy mapping of nested objects. Given the following JSON String:
```json
"distance" : {
"text" : "102 ft",
"value" : 31
}
```
You can access the nested objects as follows:
```swift
func mapping(map: Map) {
distance <- map["distance.value"]
}
```
[See complete documentation](https://github.com/Hearst-DD/ObjectMapper#easy-mapping-of-nested-objects)
### KeyPath
The `keyPath` variable is used to drill down into a JSON response and only map the data found at that `keyPath`. It supports nested values such as `data.weather` to drill down several levels in a JSON response.
```swift
let URL = "https://raw.githubusercontent.com/tristanhimmelman/AlamofireObjectMapper/2ee8f34d21e8febfdefb2b3a403f18a43818d70a/sample_keypath_json"
let expectation = expectationWithDescription("\(URL)")
Alamofire.request(URL).responseObject(keyPath: "data") { (response: DataResponse<WeatherResponse>) in
expectation.fulfill()
let weatherResponse = response.result.value
print(weatherResponse?.location)
if let threeDayForecast = weatherResponse?.threeDayForecast {
for forecast in threeDayForecast {
print(forecast.day)
print(forecast.temperature)
}
}
}
```
# Array Responses
If you have an endpoint that returns data in `Array` form you can map it with the following function:
```swift
public func responseArray<T: Mappable>(queue queue: dispatch_queue_t? = nil, keyPath: String? = nil, completionHandler: DataResponse<[T]> -> Void) -> Self
```
For example, if your endpoint returns the following:
```
[
{
"conditions": "Partly cloudy",
"day" : "Monday",
"temperature": 20
},
{
"conditions": "Showers",
"day" : "Tuesday",
"temperature": 22
},
{
"conditions": "Sunny",
"day" : "Wednesday",
"temperature": 28
}
]
```
You can request and map it as follows:
```swift
let URL = "https://raw.githubusercontent.com/tristanhimmelman/AlamofireObjectMapper/f583be1121dbc5e9b0381b3017718a70c31054f7/sample_array_json"
Alamofire.request(URL).responseArray { (response: DataResponse<[Forecast]>) in
let forecastArray = response.result.value
if let forecastArray = forecastArray {
for forecast in forecastArray {
print(forecast.day)
print(forecast.temperature)
}
}
}
```
# Installation
AlamofireObjectMapper can be added to your project using [CocoaPods](https://cocoapods.org/) by adding the following line to your Podfile:
```
pod 'AlamofireObjectMapper', '~> 5.2'
```
If you're using [Carthage](https://github.com/Carthage/Carthage) you can add a dependency on AlamofireObjectMapper by adding it to your Cartfile:
```
github "tristanhimmelman/AlamofireObjectMapper" ~> 5.2
```