Reputation: 21
I'm searching for the way to make my code more clean and efficient. I Have an "BaseInfo" it has got some properties and also it is a parent to the "SomeInfo", and "AnotherInfo" classes, which have own additional properties. I've made a generic function that saves and gets those objects from UserDefaults. And I have ViewController that's saves and loads info using those functions, considering the type of info it should use. I want to know is there any way to make my code cleaner and get rid of those type casting in my ViewContoller. Here is my info classes:
public class BaseInfo: Codable{
var name: String?
init(_ dict: [String: Any]){
name = dict["name"] as? String
}
}
class AnotherInfo: BaseInfo {
var secondName: String?
override init(_ dict: [String : Any]) {
super.init(dict)
secondName = dict["secondName"] as? String
}
required init(from decoder: Decoder) throws {
fatalError("init(from:) has not been implemented")
}
}
class SomeInfo: BaseInfo {
var middleName: String?
override init(_ dict: [String : Any]) {
super.init(dict)
middleName = dict["middleName"]
}
required init(from decoder: Decoder) throws {
fatalError("init(from:) has not been implemented")
}
}
Here is my class that manages info
protocol InfoManagerProtocol {
static func getInfo(with type: InfoType) -> BaseInfo?
static func saveInfo <T: BaseInfo>(with type: InfoType, info: T)
}
class InfoManager: InfoManagerProtocol {
static func getInfo<T: BaseInfo>(with type: InfoType) -> T? {
if let data = UserDefaults.standard.object(forKey: type.toString()) as? Data, let info = try? JSONDecoder().decode(type.typeOfInfo() as! T.Type, from: data){
return info
}
return nil
}
static func saveInfo<T>(with type: InfoType, info: T) where T : BaseInfo {
if let encodedData = try? JSONEncoder().encode(info){
UserDefaults.standard.setValue(encodedData, forKey: type.toString())
}
}
}
InfoType enum:
enum InfoType: Int{
case base = 1
case another = 2
case some = 3
}
extension InfoType{
func toString() -> String{
switch self{
case .base:
return "base"
case .another:
return "another"
case .some:
return "some"
}
}
func typeOfInfo() -> BaseInfo.Type{
switch self{
case .base:
return BaseInfo.self
case .another:
return AnotherInfo.self
case .some:
return SomeInfo.self
}
}
}
and some controller
class ViewCV: UIViewController{
......
// some outlets
var infoType: InfoType? // info
// some func that updates UI
func updateUI(){
let genericInfo = InfoManager.getInfo(info: .infoType)
self.someNameOutletLabel.text = genericInfo.name
self.secondNameOutletLabel?.text = (genericInfo as? AnotherInfo).secondName // here I don't like it
if let someInfo = genericInfo as? SomeInfo{
self.secondNameOutletLabel?.text = someInfo.thirdName // or like here I also don't like
}
}
}
Looking forward for other critics and advices
Upvotes: 1
Views: 118
Reputation: 52108
You can simplify your code by skipping the InfoType
type and instead define what type being used from the given parameter or return value.
So the protocol becomes
protocol InfoManagerProtocol {
static func getInfo<T: BaseInfo>() -> T?
static func saveInfo <T: BaseInfo>(info: T)
}
and then the implementation. Note that instead of the toString
in the enum I now use the classname itself as the key
class InfoManager: InfoManagerProtocol {
static func getInfo<T: BaseInfo>() -> T? {
if let data = UserDefaults.standard.object(forKey: "\(T.self)") as? Data, let info = try? JSONDecoder().decode(T.self, from: data){
return info
}
return nil
}
static func saveInfo<T>(info: T) where T : BaseInfo {
if let encodedData = try? JSONEncoder().encode(info){
print(encodedData)
UserDefaults.standard.set(encodedData, forKey: "\(T.self)")
}
}
}
Here is an example of how to use it (assuming that init(from:)
has been properly implemented)
let dict: [String: String] = ["name": "Joe", "secondName": "Doe", "middleName": "Jr"]
let another = AnotherInfo(dict)
let some = SomeInfo(dict)
//Here the declaration of the argument tells the generic function what T is
InfoManager.saveInfo(info: another)
InfoManager.saveInfo(info: some)
//And here the declaration of the return value tells the generic function what T is
if let stored:AnotherInfo = InfoManager.getInfo() {
print(stored, type(of: stored))
}
if let stored:SomeInfo = InfoManager.getInfo() {
print(stored, type(of: stored))
}
Upvotes: 1