diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index edfc67b..f396c8e 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -158,6 +158,7 @@ D2DEDCB923C6400600C44CC4 /* UnitInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DEDCB823C6400600C44CC4 /* UnitInterval.swift */; }; D2DEDCBB23C65BC300C44CC4 /* Percent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DEDCBA23C65BC300C44CC4 /* Percent.swift */; }; D2E1FAD92260C3E400AEFD8C /* DelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FAD82260C3E400AEFD8C /* DelegateObject.swift */; }; + EA09CD62282ACDDB00A7835F /* Decoder+UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CD61282ACDDB00A7835F /* Decoder+UserInfo.swift */; }; EA3B264C25FC0B7600008074 /* ModelHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3B264B25FC0B7600008074 /* ModelHandlerProtocol.swift */; }; /* End PBXBuildFile section */ @@ -307,6 +308,7 @@ D2DEDCB823C6400600C44CC4 /* UnitInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitInterval.swift; sourceTree = ""; }; D2DEDCBA23C65BC300C44CC4 /* Percent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Percent.swift; sourceTree = ""; }; D2E1FAD82260C3E400AEFD8C /* DelegateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegateObject.swift; sourceTree = ""; }; + EA09CD61282ACDDB00A7835F /* Decoder+UserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decoder+UserInfo.swift"; sourceTree = ""; }; EA3B264B25FC0B7600008074 /* ModelHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelHandlerProtocol.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -435,6 +437,7 @@ isa = PBXGroup; children = ( 946EE1AA237B5C940036751F /* Decoder.swift */, + EA09CD61282ACDDB00A7835F /* Decoder+UserInfo.swift */, 946EE1B3237B619D0036751F /* Encoder.swift */, 0A42538E23F3414800554656 /* Codable+Helpers.swift */, ); @@ -911,6 +914,7 @@ AFEA17A9209B6A1C00BC6740 /* MVMCoreBlockOperation.m in Sources */, AF43A70A1FC4F415008E9347 /* MVMCoreCache.m in Sources */, AF43A6FF1FBE3252008E9347 /* Reachability.m in Sources */, + EA09CD62282ACDDB00A7835F /* Decoder+UserInfo.swift in Sources */, 01C851D123CF97FE0021F976 /* ActionBackModel.swift in Sources */, D27073D125BB844B001C7246 /* MVMCoreActionDelegateProtocol+Extension.swift in Sources */, AFBB96921FBA3A9A0008D868 /* MVMCoreNavigationOperation.m in Sources */, diff --git a/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift b/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift new file mode 100644 index 0000000..d9f4e04 --- /dev/null +++ b/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift @@ -0,0 +1,109 @@ +// +// Decoder+UserInfo.swift +// MVMCore +// +// Created by Matt Bruce on 5/10/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +extension CodingUserInfoKey { + internal static let delegateObjectKey = CodingUserInfoKey(rawValue: "delegateObject")! + internal static let contextKey = CodingUserInfoKey(rawValue: "context")! +} + +// MARK: - DelegateObject +public extension JSONDecoder { + /// Adds a delegate object to the decoder for use in the model initializers. + func add(value: Any, for key: CodingUserInfoKey) { + userInfo.updateValue(value, forKey: key) + } +} + +public extension Decoder { + /// Gets a delegate object from the decoder for use in the model initializers. Had to be added before decode. + func get() throws -> T? { + return userInfo[.delegateObjectKey] as? T + } +} + +// MARK: - DecodingContext +/// Class used to be added into the Decoder's userInfo object to store +/// key/value pairs to be used within the Decoding process. +public class DecodingContext { + + /// Key/Value store + fileprivate var userInfo: [String: Any] = [:] + + public init(){ } + + /// Used to access value + /// - Parameter key: key for value + /// - Returns: value returns if the key exists + public func value(forKey key: String) -> Any? { + return userInfo[key] + } +} + +extension Decoder { + + ///wrapping getter to call internal userInfo for this object + public var context: DecodingContext? { + userInfo[.contextKey] as? DecodingContext + } + + /// Gets Typed value for key in the context + /// - Parameter key: Identifier + /// - Returns: Typed value + public func getContext(key: String) throws -> T? { + context?.userInfo[key] as? T + } + + /// Sets value for key in the context + /// - Parameters: + /// - value: Object + /// - key: Identifier + public func setContext(value: Any, for key: String) { + context?.userInfo[key] = value + } + + /// Removes value for key in the context + /// - Parameter key: Identifier + public func removeContext(for key: String) { + context?.userInfo.removeValue(forKey: key) + } + + /// This is a wrapping function that is meant to be used along with a "decode" method. It will save a key/value pair locally + /// and then this key/value pair is removed immediately after the completion handler is executed so that no other classes + /// will mistakenly use this value. + /// - Parameters: + /// - value: Object that you are wanting to save + /// - key: Identifier to get the Value + /// - completion: Code that will run after the value is set + public func setContext(value: Any, for key: String, completion: (() throws -> Void)) throws { + setContext(value: value, for: key) + try completion() + removeContext(for: key) + } +} + +extension JSONDecoder { + + /// Helper method to initialize a JSONDecoder + /// - Parameter delegateObject: Delegate Object + /// - Returns: JSONDecoder + public class func create(delegateObject: DelegateObject? = nil) throws -> JSONDecoder { + let decoder = JSONDecoder() + decoder.add(value: DecodingContext(), for: .contextKey) + if let delegateObject = delegateObject { + decoder.add(value: delegateObject, for: .delegateObjectKey) + } + return decoder + } + + ///wrapping getter to call internal userInfo for this object + public var context: DecodingContext? { + get { return userInfo[.contextKey] as? DecodingContext } + } +} diff --git a/MVMCore/MVMCore/Models/Extensions/Decoder.swift b/MVMCore/MVMCore/Models/Extensions/Decoder.swift index fc54a0d..9a4dd94 100644 --- a/MVMCore/MVMCore/Models/Extensions/Decoder.swift +++ b/MVMCore/MVMCore/Models/Extensions/Decoder.swift @@ -25,6 +25,16 @@ extension Data { } } +extension JSONDecoder { + /// Decodes a top-level value of the given type from the given JSON representation, and adds the delegate object if provided. + func decode(_ type: T.Type, from data: Data, delegateObject: DelegateObject?) throws -> T where T : Decodable { + if let delegateObject = delegateObject { + add(value: delegateObject, for: .delegateObjectKey) + } + return try decode(T.self, from: data) + } +} + extension KeyedDecodingContainerProtocol { public func decode(forKey key: Key) throws -> T { return try decode(T.self, forKey: key) @@ -42,7 +52,8 @@ extension Decodable { public static func decode(jsonDict: [String: Any], delegateObject: DelegateObject? = nil) throws -> Self { let jsonData = try JSONSerialization.data(withJSONObject: jsonDict) do { - return try jsonData.decode(delegateObject: delegateObject) + let decoder = try JSONDecoder.create(delegateObject: delegateObject) + return try jsonData.decode(using: decoder) } catch { throw JSONError.other(error: error) } @@ -64,35 +75,3 @@ extension Decodable { } } } - -public enum DecoderKeyError: Error { - case createKey -} - -public extension JSONDecoder { - /// Adds a delegate object to the decoder for use in the model initializers. - func add(delegateObject: T) throws { - guard let key = CodingUserInfoKey(rawValue: "delegateObject") else { - throw DecoderKeyError.createKey - } - userInfo.updateValue(delegateObject, forKey: key) - } - - /// Decodes a top-level value of the given type from the given JSON representation, and adds the delegate object if provided. - func decode(_ type: T.Type, from data: Data, delegateObject: DelegateObject?) throws -> T where T : Decodable { - if let delegateObject = delegateObject { - try add(delegateObject: delegateObject) - } - return try decode(T.self, from: data) - } -} - -public extension Decoder { - /// Gets a delegate object from the decoder for use in the model initializers. Had to be added before decode. - func get() throws -> T? { - guard let key = CodingUserInfoKey(rawValue: "delegateObject") else { - throw DecoderKeyError.createKey - } - return userInfo[key] as? T - } -}