From 53e05ca42f19ce089021ea79fc3de9345cdfb554 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 12 Feb 2020 11:25:03 -0500 Subject: [PATCH] integrating changes to make fan out instance models into categories due to name conflicts --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 4 + .../ActionType/ActionModelProtocol.swift | 19 +++ .../ActionType/ActionOpenAppModel.swift | 2 +- .../Models/ActionType/ActionPopupModel.swift | 1 + .../ActionType/ActionTopAlertModel.swift | 1 + .../Models/Extensions/Codable+Helpers.swift | 45 ++++++ MVMCore/MVMCore/Models/Model/Model.swift | 11 ++ .../MVMCore/Models/Model/ModelRegistry.swift | 145 ++++++++++++------ MVMCore/MVMCore/Models/ModelMapping.swift | 14 +- .../MFHardCodedServerResponse.h | 2 +- 10 files changed, 187 insertions(+), 57 deletions(-) create mode 100644 MVMCore/MVMCore/Models/Extensions/Codable+Helpers.swift diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index a90b168..2b86370 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 01F2A04E23A82CF500D954D8 /* ActionPopupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2A04D23A82CF500D954D8 /* ActionPopupModel.swift */; }; 01F2A05023A82D0800D954D8 /* ActionTopAlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2A04F23A82D0800D954D8 /* ActionTopAlertModel.swift */; }; 01F2A05223A8325100D954D8 /* ModelMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2A05123A8325100D954D8 /* ModelMapping.swift */; }; + 0A42538F23F3414800554656 /* Codable+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A42538E23F3414800554656 /* Codable+Helpers.swift */; }; 30349BF11FCCA78A00546A1E /* MVMCoreSessionTimeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 30349BEF1FCCA78A00546A1E /* MVMCoreSessionTimeHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 30349BF21FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 30349BF01FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m */; }; 881D26931FCC9D180079C521 /* MVMCoreErrorObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */; }; @@ -177,6 +178,7 @@ 01F2A05123A8325100D954D8 /* ModelMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelMapping.swift; sourceTree = ""; }; 0A11030B20864F94008ADD90 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 0A11030C20864F9A008ADD90 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Localizable.strings"; sourceTree = ""; }; + 0A42538E23F3414800554656 /* Codable+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codable+Helpers.swift"; sourceTree = ""; }; 30349BEF1FCCA78A00546A1E /* MVMCoreSessionTimeHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreSessionTimeHandler.h; sourceTree = ""; }; 30349BF01FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreSessionTimeHandler.m; sourceTree = ""; }; 881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreErrorObject.m; sourceTree = ""; }; @@ -429,6 +431,7 @@ children = ( 946EE1AA237B5C940036751F /* Decoder.swift */, 946EE1B3237B619D0036751F /* Encoder.swift */, + 0A42538E23F3414800554656 /* Codable+Helpers.swift */, ); path = Extensions; sourceTree = ""; @@ -894,6 +897,7 @@ 946EE1AB237B5C940036751F /* Decoder.swift in Sources */, 946EE1BC237B691A0036751F /* ActionOpenPageModel.swift in Sources */, AFBB96A61FBA3A9A0008D868 /* MVMCoreTopAlertOperation.m in Sources */, + 0A42538F23F3414800554656 /* Codable+Helpers.swift in Sources */, 881D26931FCC9D180079C521 /* MVMCoreErrorObject.m in Sources */, 946EE1B0237B5EF70036751F /* JSONHelper.swift in Sources */, 30349BF21FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m in Sources */, diff --git a/MVMCore/MVMCore/Models/ActionType/ActionModelProtocol.swift b/MVMCore/MVMCore/Models/ActionType/ActionModelProtocol.swift index 5a01d35..09e4673 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionModelProtocol.swift +++ b/MVMCore/MVMCore/Models/ActionType/ActionModelProtocol.swift @@ -13,10 +13,29 @@ public enum ActionCodingKey: String, CodingKey { } public protocol ActionModelProtocol: Model { + var actionType: String? { get set } var extraParameters: JSONValueDictionary? { get set } var analyticsData: JSONValueDictionary? { get set } + + static var categoryName: String { get } + static var categoryCodingKey: String { get } // Temporary fix till server changes var title: String? { get set } } + +extension ActionModelProtocol { + + public var actionType: String? { + get { return Self.identifier } + } + + public static var categoryCodingKey: String { + return "actionType" + } + + public static var categoryName: String { + return "\(ActionModelProtocol.self)" + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionOpenAppModel.swift b/MVMCore/MVMCore/Models/ActionType/ActionOpenAppModel.swift index 8cc9c58..cd774fa 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionOpenAppModel.swift +++ b/MVMCore/MVMCore/Models/ActionType/ActionOpenAppModel.swift @@ -8,7 +8,7 @@ import Foundation -@objcMembers public class ActionOpenAppModel: ActionModelProtocol { +@objcMembers public class ActionOpenAppModel: ActionModelProtocol { public static var identifier: String = "openApp" public var actionType: String? public var appURL: String diff --git a/MVMCore/MVMCore/Models/ActionType/ActionPopupModel.swift b/MVMCore/MVMCore/Models/ActionType/ActionPopupModel.swift index 899f782..68366fe 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionPopupModel.swift +++ b/MVMCore/MVMCore/Models/ActionType/ActionPopupModel.swift @@ -9,6 +9,7 @@ import Foundation @objcMembers public class ActionPopupModel: ActionModelProtocol { + public static var identifier: String = "popup" public var actionType: String? public var title: String? diff --git a/MVMCore/MVMCore/Models/ActionType/ActionTopAlertModel.swift b/MVMCore/MVMCore/Models/ActionType/ActionTopAlertModel.swift index 6da6491..ac0d568 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionTopAlertModel.swift +++ b/MVMCore/MVMCore/Models/ActionType/ActionTopAlertModel.swift @@ -9,6 +9,7 @@ import Foundation @objcMembers public class ActionTopAlertModel: ActionModelProtocol { + public static var identifier: String = "topAlert" public var actionType: String? public var pageType: String diff --git a/MVMCore/MVMCore/Models/Extensions/Codable+Helpers.swift b/MVMCore/MVMCore/Models/Extensions/Codable+Helpers.swift new file mode 100644 index 0000000..db289ca --- /dev/null +++ b/MVMCore/MVMCore/Models/Extensions/Codable+Helpers.swift @@ -0,0 +1,45 @@ +// +// Codable+Helpers.swift +// MVMCore +// +// Created by Kevin Christiano on 2/11/20. +// Copyright © 2020 myverizon. All rights reserved. +// + +import Foundation + +// MARK: - Date formatters +/// Protocol acting as a common API for all types of date formatters, +/// such as `DateFormatter` and `ISO8601DateFormatter`. +public protocol AnyDateFormatter { + /// Format a string into a date + func date(from string: String) -> Date? + /// Format a date into a string + func string(from date: Date) -> String +} + +extension DateFormatter: AnyDateFormatter {} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +extension ISO8601DateFormatter: AnyDateFormatter {} + + +// MARK: supporting types +public struct AnyCodingKey: CodingKey { + + public var stringValue: String + public var intValue: Int? + + public init(_ string: String) { + stringValue = string + } + + public init?(stringValue: String) { + self.stringValue = stringValue + } + + public init?(intValue: Int) { + self.intValue = intValue + self.stringValue = String(intValue) + } +} diff --git a/MVMCore/MVMCore/Models/Model/Model.swift b/MVMCore/MVMCore/Models/Model/Model.swift index 936c548..dc17f7e 100644 --- a/MVMCore/MVMCore/Models/Model/Model.swift +++ b/MVMCore/MVMCore/Models/Model/Model.swift @@ -10,19 +10,30 @@ import Foundation public protocol Model: Codable { + /// The key name of the molecule static var identifier: String { get } + /// Name of the defining object. + static var categoryName: String { get } + + /// The JSON key name of the + static var categoryCodingKey: String { get } + /// Convenience function to decode to model using a keyed container. static func decode(keyedContainer: KeyedDecodingContainer, codingKey: K) throws -> Self? + /// Convenience function to decode to model using an unkeyed container. static func decode(unkeyedContainer: inout UnkeyedDecodingContainer) throws -> Self? + /// Convenience function to encode model using a keyed container. func encode(keyedContainer: inout KeyedEncodingContainer, codingKey: K) throws + /// Convenience function to encode model using an unkeyed container. func encode(unkeyedContainer: inout UnkeyedEncodingContainer) throws } extension Model { + static public func decode(keyedContainer: KeyedDecodingContainer, codingKey: K) throws -> Self? { return try keyedContainer.decodeIfPresent(self, forKey: codingKey) } diff --git a/MVMCore/MVMCore/Models/Model/ModelRegistry.swift b/MVMCore/MVMCore/Models/Model/ModelRegistry.swift index c92727f..026bf7b 100644 --- a/MVMCore/MVMCore/Models/Model/ModelRegistry.swift +++ b/MVMCore/MVMCore/Models/Model/ModelRegistry.swift @@ -10,36 +10,78 @@ import Foundation public struct ModelRegistry { + public enum Error: Swift.Error { case keyNotFound case encoderError case decoderError + case decoderOther(message: String) case decoderErrorObjectNotPresent case decoderErrorModelNotMapped + case other(message: String) } - - fileprivate static var types = [String: Model.Type]() - - /// Register the model. - public static func register(_ type: M.Type) { - types[M.identifier] = type + + private struct Category { + var name: String + var codingKey: String + var instanceTypes: [String: Model.Type] = [:] } - - public static func getType(for name: String) -> Model.Type? { - return types[name] + + private static var categories: [String: Category] = [:] + + /// Registers models for Atomic use. + public static func register(_ type: M.Type) throws { + + var category: Category + print("\n\n") + print("HI= \(M.identifier)") + print("HI= \(M.categoryName)") + print("HI= \(M.categoryCodingKey)") + print("\n\n") + if let c = categories[M.categoryName] { + category = c + } else { + category = Category(name: M.categoryName, codingKey: M.categoryCodingKey) + } + + // Check to ensure the Category/Type combination doesn't exist. + if category.instanceTypes[M.identifier] != nil { + throw ModelRegistry.Error.other(message: "Model: \(M.identifier) already exists in Category: \(M.categoryName)") + } + + category.instanceTypes[M.identifier] = type + categories[M.categoryName] = category + } + + private static func getCategory(for type: T.Type) -> Category? { + return categories["\(T.self)"] + } + + public static func getType(for name: String, with type: T.Type) -> Model.Type? { + return getCategory(for: type)?.instanceTypes[name] + } + + public static func getCodingKey(for type: T.Type) throws -> AnyCodingKey{ + guard let category = getCategory(for: type) else { + throw ModelRegistry.Error.decoderOther(message: "decodeModelsIfPresent only works for objects implementing the Model protocol") + } + + return AnyCodingKey(category.codingKey) } } extension KeyedDecodingContainer where Key: CodingKey { - - //MARK: - Decode + + // MARK: - Decode + /// Decodes to a registered model based on the identifier public func decodeModel(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeKey) throws -> M { + guard let model: Model = try decodeModelIfPresent(codingKey: codingKey, typeCodingKey: typeCodingKey) else { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey)") throw ModelRegistry.Error.decoderErrorObjectNotPresent } - + if let model = model as? M { return model } else { @@ -48,25 +90,24 @@ extension KeyedDecodingContainer where Key: CodingKey { } } - //MARK: - DecodeIfPresent + // MARK: - DecodeIfPresent + /// Decodes to a registered model based on the identifier, optional. public func decodeModelIfPresent(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeKey) throws -> M? { //get the identifier string guard contains(codingKey), let container = try? nestedContainer(keyedBy: TypeKey.self, forKey: codingKey), - let identifier = try? container.decodeIfPresent(String.self, forKey: typeCodingKey) else { - return nil - } - - //get the type - guard let type = ModelRegistry.getType(for: identifier) else { + let identifier = try? container.decodeIfPresent(String.self, forKey: typeCodingKey) + else { return nil } + + guard let type = ModelRegistry.getType(for: identifier, with: M.self) else { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "Model not mapped: \(identifier)") throw ModelRegistry.Error.decoderErrorModelNotMapped } - + //decode the type using the decoder let model = try type.decode(keyedContainer: self, codingKey: codingKey) - + if let model = model as? M { return model } else { @@ -78,12 +119,12 @@ extension KeyedDecodingContainer where Key: CodingKey { /// Decodes an array of registered model based on the identifiers, optional. public func decodeModelsIfPresent(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeKey) throws -> [Model]? { guard contains(codingKey), - var container = try? nestedUnkeyedContainer(forKey: codingKey) else { - return nil - } + var container = try? nestedUnkeyedContainer(forKey: codingKey) + else { return nil } + return try container.decodeModelsIfPresent(typeCodingKey: typeCodingKey) } - + /// Decodes an array of registered model based on the identifiers. public func decodeModels(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeKey) throws -> [Model] { guard let models: [Model] = try decodeModelsIfPresent(codingKey: codingKey, typeCodingKey: typeCodingKey) else { @@ -96,9 +137,9 @@ extension KeyedDecodingContainer where Key: CodingKey { /// Decodes an array with arrays of models based on the identifiers, optional. public func decodeModels2DIfPresent(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeKey) throws -> [[Model]]? { guard contains(codingKey), - var container = try? nestedUnkeyedContainer(forKey: codingKey) else { - return nil - } + var container = try? nestedUnkeyedContainer(forKey: codingKey) + else { return nil } + return try container.decodeModels2DIfPresent(typeCodingKey: typeCodingKey) } @@ -112,40 +153,44 @@ extension KeyedDecodingContainer where Key: CodingKey { } } -extension KeyedEncodingContainer where Key: CodingKey { - +public extension KeyedEncodingContainer where Key: CodingKey { + /// Encodes a model, optional. - public mutating func encodeModelIfPresent(_ value: Model?, forKey key: KeyedEncodingContainer.Key) throws { + mutating func encodeModelIfPresent(_ value: Model?, forKey key: KeyedEncodingContainer.Key) throws { if let value = value { - let encoder = self.superEncoder(forKey: key) + let encoder = superEncoder(forKey: key) try value.encode(to: encoder) } } - + /// Encodes a model. - public mutating func encodeModel(_ value: Model, forKey key: KeyedEncodingContainer.Key) throws { - let encoder = self.superEncoder(forKey: key) + mutating func encodeModel(_ value: Model, forKey key: KeyedEncodingContainer.Key) throws { + let encoder = superEncoder(forKey: key) try value.encode(to: encoder) } - + /// Encodes an array of models - public mutating func encodeModels(_ list: [Model], forKey key:KeyedEncodingContainer.Key) throws { + mutating func encodeModels(_ list: [Model], forKey key:KeyedEncodingContainer.Key) throws { try encodeModelsIfPresent(list, forKey: key) } - + /// Encodes an array of models, optional, need instance type as input paramaeter list - public mutating func encodeModelsIfPresent(_ list: [Model]?, forKey key:KeyedEncodingContainer.Key) throws { + mutating func encodeModelsIfPresent(_ list: [Model]?, forKey key:KeyedEncodingContainer.Key) throws { + guard let list = list else { return } - var unkeyedContainer = self.nestedUnkeyedContainer(forKey: key) + var unkeyedContainer = nestedUnkeyedContainer(forKey: key) + for model in list { try model.encode(unkeyedContainer: &unkeyedContainer) } } /// Convenience function for encoding the container into a list of lists of Models. needs instance type as input paramaeter list - public mutating func encodeModels2D(_ list: [[Model]]?, forKey key:KeyedEncodingContainer.Key) throws { + mutating func encodeModels2D(_ list: [[Model]]?, forKey key:KeyedEncodingContainer.Key) throws { + guard let list = list else { return } - var unkeyedContainer = self.nestedUnkeyedContainer(forKey: key) + var unkeyedContainer = nestedUnkeyedContainer(forKey: key) + for models in list { var arrayunkeyedContainer = unkeyedContainer.nestedUnkeyedContainer() for model in models { @@ -155,20 +200,22 @@ extension KeyedEncodingContainer where Key: CodingKey { } } -extension UnkeyedDecodingContainer { +public extension UnkeyedDecodingContainer { + /// Decodes the container into a list of Models. - public mutating func decodeModelsIfPresent(typeCodingKey: TypeKey) throws -> [Model]? { + mutating func decodeModelsIfPresent(typeCodingKey: TypeKey) throws -> [Model]? { + var models = [Model]() var containerCopy = self + while !containerCopy.isAtEnd { let nestedContainer = try containerCopy.nestedContainer(keyedBy: TypeKey.self) if let identifier = try nestedContainer.decodeIfPresent(String.self, forKey: typeCodingKey) { - //get the type - guard let type = ModelRegistry.getType(for: identifier) else { + guard let type = ModelRegistry.getType(for: identifier, with: TypeKey.self) else { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorModelNotMapped: \(identifier)") throw ModelRegistry.Error.decoderErrorModelNotMapped } - //now get the decoder to use for the type + // Now get the decoder to use for the type let decoder = try self.superDecoder() let model = try type.init(from: decoder) models.append(model) @@ -178,9 +225,11 @@ extension UnkeyedDecodingContainer { } /// Convenience function for decoding the container into a list of lists of Models. - public mutating func decodeModels2DIfPresent(typeCodingKey: TypeKey) throws -> [[Model]]? { + mutating func decodeModels2DIfPresent(typeCodingKey: TypeKey) throws -> [[Model]]? { + var modelLists = [[Model]]() var containerCopy = self + while !containerCopy.isAtEnd { var arraycontainerCopy = try containerCopy.nestedUnkeyedContainer() guard let models = try arraycontainerCopy.decodeModelsIfPresent(typeCodingKey: typeCodingKey) else { diff --git a/MVMCore/MVMCore/Models/ModelMapping.swift b/MVMCore/MVMCore/Models/ModelMapping.swift index 383e98e..bb9b0df 100644 --- a/MVMCore/MVMCore/Models/ModelMapping.swift +++ b/MVMCore/MVMCore/Models/ModelMapping.swift @@ -10,12 +10,12 @@ import Foundation @objcMembers public class ModelMapping: NSObject { public static func registerObjects() { - ModelRegistry.register(ActionOpenPageModel.self) - ModelRegistry.register(ActionOpenUrlModel.self) - ModelRegistry.register(ActionOpenAppModel.self) - ModelRegistry.register(ActionCallModel.self) - ModelRegistry.register(ActionPopupModel.self) - ModelRegistry.register(ActionTopAlertModel.self) - ModelRegistry.register(ActionBackModel.self) + try? ModelRegistry.register(ActionOpenPageModel.self) + try? ModelRegistry.register(ActionOpenUrlModel.self) + try? ModelRegistry.register(ActionOpenAppModel.self) + try? ModelRegistry.register(ActionCallModel.self) + try? ModelRegistry.register(ActionPopupModel.self) + try? ModelRegistry.register(ActionTopAlertModel.self) + try? ModelRegistry.register(ActionBackModel.self) } } diff --git a/MVMCore/MVMCore/Utility/HardCodedServerResponse/MFHardCodedServerResponse.h b/MVMCore/MVMCore/Utility/HardCodedServerResponse/MFHardCodedServerResponse.h index eb9fba9..efa33b6 100644 --- a/MVMCore/MVMCore/Utility/HardCodedServerResponse/MFHardCodedServerResponse.h +++ b/MVMCore/MVMCore/Utility/HardCodedServerResponse/MFHardCodedServerResponse.h @@ -9,7 +9,7 @@ #import #import "MVMCoreRequestParameters.h" -#define ENABLE_HARD_CODED_RESPONSE 0 && DEBUG +#define ENABLE_HARD_CODED_RESPONSE 1 && DEBUG #if ENABLE_HARD_CODED_RESPONSE