diff --git a/.travis.yml b/.travis.yml index ae21af5..1d3b8ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode9 +osx_image: xcode10 env: global: diff --git a/AlamofireObjectMapper.podspec b/AlamofireObjectMapper.podspec index b58efb3..9e49629 100644 --- a/AlamofireObjectMapper.podspec +++ b/AlamofireObjectMapper.podspec @@ -1,15 +1,15 @@ Pod::Spec.new do |s| s.name = "AlamofireObjectMapper" - s.version = "5.2.0" + s.version = "6.0" s.license = { :type => "MIT", :file => "LICENSE" } s.summary = "An extension to Alamofire which automatically converts JSON response data into swift objects using ObjectMapper" s.homepage = "https://github.com/tristanhimmelman/AlamofireObjectMapper" s.author = { "Tristan Himmelman" => "tristanhimmelman@gmail.com" } s.source = { :git => 'https://github.com/tristanhimmelman/AlamofireObjectMapper.git', :tag => s.version.to_s } - s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.10' + s.ios.deployment_target = '10.0' + s.osx.deployment_target = '10.0' s.watchos.deployment_target = '2.0' s.tvos.deployment_target = '9.0' @@ -17,6 +17,6 @@ Pod::Spec.new do |s| s.requires_arc = true s.source_files = 'AlamofireObjectMapper/**/*.swift' - s.dependency 'Alamofire', '~> 4.7' + s.dependency 'Alamofire', '~> 5.0.0.beta.1' s.dependency 'ObjectMapper', '~> 3.4' end diff --git a/AlamofireObjectMapper.xcodeproj/project.pbxproj b/AlamofireObjectMapper.xcodeproj/project.pbxproj index 1d1b23d..758c204 100644 --- a/AlamofireObjectMapper.xcodeproj/project.pbxproj +++ b/AlamofireObjectMapper.xcodeproj/project.pbxproj @@ -323,7 +323,7 @@ E89A901B1D6F5F520023C364 /* ObjectMapper.framework */, E89A901D1D6F5F520023C364 /* ObjectMapper.framework */, E89A901F1D6F5F520023C364 /* ObjectMapper-iOSTests.xctest */, - E89A90211D6F5F520023C364 /* ObjectMapper-MacTests.xctest */, + E89A90211D6F5F520023C364 /* ObjectMapper-macOSTests.xctest */, E89A90231D6F5F520023C364 /* ObjectMapper-tvOSTests.xctest */, 6A20983C219CDEA5009AB4DF /* libObjectMapper.a */, ); @@ -654,10 +654,10 @@ remoteRef = E89A901E1D6F5F520023C364 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - E89A90211D6F5F520023C364 /* ObjectMapper-MacTests.xctest */ = { + E89A90211D6F5F520023C364 /* ObjectMapper-macOSTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; - path = "ObjectMapper-MacTests.xctest"; + path = "ObjectMapper-macOSTests.xctest"; remoteRef = E89A90201D6F5F520023C364 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -835,7 +835,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -889,7 +889,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_VERSION = 4.2; @@ -914,7 +914,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AlamofireObjectMapper/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.tristanhimmelman.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AlamofireObjectMapper; @@ -938,7 +938,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AlamofireObjectMapper/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.tristanhimmelman.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AlamofireObjectMapper; @@ -1005,7 +1005,9 @@ ); INFOPLIST_FILE = AlamofireObjectMapper/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = "com.tristanhimmelman.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AlamofireObjectMapper; SDKROOT = macosx; @@ -1027,7 +1029,9 @@ FRAMEWORK_VERSION = A; INFOPLIST_FILE = AlamofireObjectMapper/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = "com.tristanhimmelman.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AlamofireObjectMapper; SDKROOT = macosx; @@ -1093,7 +1097,9 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AlamofireObjectMapper/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = "com.tristanhimmelman.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AlamofireObjectMapper; SDKROOT = watchos; @@ -1117,7 +1123,9 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AlamofireObjectMapper/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = "com.tristanhimmelman.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AlamofireObjectMapper; SDKROOT = watchos; @@ -1143,14 +1151,16 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AlamofireObjectMapper/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = "com.tristanhimmelman.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AlamofireObjectMapper; SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 10.0; }; name = Debug; }; @@ -1167,7 +1177,9 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AlamofireObjectMapper/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = "com.tristanhimmelman.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AlamofireObjectMapper; SDKROOT = appletvos; @@ -1175,7 +1187,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 10.0; }; name = Release; }; diff --git a/AlamofireObjectMapper.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/AlamofireObjectMapper.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/AlamofireObjectMapper.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/AlamofireObjectMapper/AlamofireObjectMapper.swift b/AlamofireObjectMapper/AlamofireObjectMapper.swift index b181e84..bc78327 100644 --- a/AlamofireObjectMapper/AlamofireObjectMapper.swift +++ b/AlamofireObjectMapper/AlamofireObjectMapper.swift @@ -37,6 +37,25 @@ extension DataRequest { 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" @@ -59,61 +78,44 @@ extension DataRequest { return nil } - /// Utility function for extracting JSON from response - internal static func processResponse(request: URLRequest?, response: HTTPURLResponse?, data: Data?, keyPath: String?) -> Any? { - let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments) - let result = jsonResponseSerializer.serializeResponse(request, response, data, nil) - - let JSON: Any? - if let keyPath = keyPath , keyPath.isEmpty == false { - JSON = (result.value as AnyObject?)?.value(forKeyPath: keyPath) - } else { - JSON = result.value - } - - return JSON - } /// BaseMappable Object Serializer - public static func ObjectMapperSerializer(_ keyPath: String?, mapToObject object: T? = nil, context: MapContext? = nil) -> DataResponseSerializer { - return DataResponseSerializer { request, response, data, error in - if let error = checkResponseForError(request: request, response: response, data: data, error: error){ - return .failure(error) - } - + public static func ObjectMapperSerializer(_ keyPath: String?, mapToObject object: T? = nil, context: MapContext? = nil) -> MappableResponseSerializer { + + 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(context: context, shouldIncludeNilValues: false).map(JSONObject: JSONObject, toObject: object) - return .success(object) + return object } else if let parsedObject = Mapper(context: context, shouldIncludeNilValues: false).map(JSONObject: JSONObject){ - return .success(parsedObject) + return parsedObject } let failureReason = "ObjectMapper failed to serialize response." - let error = newError(.dataSerializationFailed, failureReason: failureReason) - return .failure(error) - } + throw AFError.responseSerializationFailed(reason: .decodingFailed(error: newError(.dataSerializationFailed, failureReason: failureReason))) + + }) } /// ImmutableMappable Array Serializer - public static func ObjectMapperImmutableSerializer(_ keyPath: String?, context: MapContext? = nil) -> DataResponseSerializer { - return DataResponseSerializer { request, response, data, error in - if let error = checkResponseForError(request: request, response: response, data: data, error: error){ - return .failure(error) - } + public static func ObjectMapperImmutableSerializer(_ keyPath: String?, context: MapContext? = nil) -> MappableResponseSerializer { + + 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(context: context, shouldIncludeNilValues: false).map(JSONObject: JSONObject)){ - return .success(parsedObject) + return parsedObject + } else { + let failureReason = "ObjectMapper failed to serialize response." + throw AFError.responseSerializationFailed(reason: .decodingFailed(error: newError(.dataSerializationFailed, failureReason: failureReason))) } - - let failureReason = "ObjectMapper failed to serialize response." - let error = newError(.dataSerializationFailed, failureReason: failureReason) - return .failure(error) - } + }) } /** @@ -137,42 +139,40 @@ extension DataRequest { } /// BaseMappable Array Serializer - public static func ObjectMapperArraySerializer(_ keyPath: String?, context: MapContext? = nil) -> DataResponseSerializer<[T]> { - return DataResponseSerializer { request, response, data, error in - if let error = checkResponseForError(request: request, response: response, data: data, error: error){ - return .failure(error) - } + public static func ObjectMapperArraySerializer(_ keyPath: String?, context: MapContext? = nil) -> MappableArrayResponseSerializer { + + + + 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(context: context, shouldIncludeNilValues: false).mapArray(JSONObject: JSONObject){ - return .success(parsedObject) + return parsedObject } let failureReason = "ObjectMapper failed to serialize response." - let error = newError(.dataSerializationFailed, failureReason: failureReason) - return .failure(error) - } + throw AFError.responseSerializationFailed(reason: .decodingFailed(error: newError(.dataSerializationFailed, failureReason: failureReason))) + }) } + /// ImmutableMappable Array Serializer - public static func ObjectMapperImmutableArraySerializer(_ keyPath: String?, context: MapContext? = nil) -> DataResponseSerializer<[T]> { - return DataResponseSerializer { request, response, data, error in - if let error = checkResponseForError(request: request, response: response, data: data, error: error){ - return .failure(error) - } + public static func ObjectMapperImmutableArraySerializer(_ keyPath: String?, context: MapContext? = nil) -> MappableArrayResponseSerializer { + 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(context: context, shouldIncludeNilValues: false).mapArray(JSONObject: JSONObject){ - return .success(parsedObject) + return parsedObject } } let failureReason = "ObjectMapper failed to serialize response." - let error = newError(.dataSerializationFailed, failureReason: failureReason) - return .failure(error) - } + throw AFError.responseSerializationFailed(reason: .decodingFailed(error: newError(.dataSerializationFailed, failureReason: failureReason))) + }) } /** @@ -203,3 +203,109 @@ extension DataRequest { return response(queue: queue, responseSerializer: DataRequest.ObjectMapperImmutableArraySerializer(keyPath, context: context), completionHandler: completionHandler) } } + +public final class MappableResponseSerializer: 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 + /// HTTP request methods for which empty responses are allowed. + public let emptyRequestMethods: Set + + 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 = MappableResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = 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: 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 + /// HTTP request methods for which empty responses are allowed. + public let emptyRequestMethods: Set + + 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 = MappableArrayResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = 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) + } +} diff --git a/Cartfile b/Cartfile index 662080e..0e23659 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ -github "Alamofire/Alamofire" ~> 4.7 +github "Alamofire/Alamofire" "5.0.0.beta.1" github "tristanhimmelman/ObjectMapper" ~> 3.4 diff --git a/Cartfile.resolved b/Cartfile.resolved index 1462415..1b98deb 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "Alamofire/Alamofire" "4.7.3" -github "tristanhimmelman/ObjectMapper" "3.4.1" +github "Alamofire/Alamofire" "5.0.0.beta.1" +github "tristanhimmelman/ObjectMapper" "3.4.2" diff --git a/Carthage/Checkouts/Alamofire b/Carthage/Checkouts/Alamofire index 61a780f..3e488dc 160000 --- a/Carthage/Checkouts/Alamofire +++ b/Carthage/Checkouts/Alamofire @@ -1 +1 @@ -Subproject commit 61a780f3b58ee0d2bb76fdb7b89dbc9751d521d7 +Subproject commit 3e488dcd5caf8d76f3da79c7b6d7f4d784ca3296 diff --git a/Carthage/Checkouts/ObjectMapper b/Carthage/Checkouts/ObjectMapper index feb763a..83848fe 160000 --- a/Carthage/Checkouts/ObjectMapper +++ b/Carthage/Checkouts/ObjectMapper @@ -1 +1 @@ -Subproject commit feb763a93755b5b576e09a9db2cb8d380fd65ba2 +Subproject commit 83848fe8baa6fab47c7c325514956d4c9b2b88e6