Swift 5 Default Decododable implementation with only one exception - ios

Is there a way to keep Swift's default implementation for a Decodable class with only Decodable objects but one exception?
So for example if I have a struct/class like that:
struct MyDecodable: Decodable {
var int: Int
var string: String
var location: CLLocation
}
I would like to use default decoding for int and string but decode location myself.
So in init(from decoder:) i would like to have something like this:
required init(from decoder: Decoder) throws {
<# insert something that decodes all standard decodable properties #>
// only handle location separately
let container = try decoder.container(keyedBy: CodingKeys.self)
location = <# insert custom location decoding #>
}

Is there a way to keep Swift's default implementation for a Decodable class with only Decodable objects but one exception
Unfortunately no. To be Decodable all properties must be Decodable. And if you are going to write a custom init you must initialize (and therefore decode) all properties yourself.
Apple knows this is painful and has given some thought to the matter, but right now a custom init for a Decodable is all or nothing.
As has been suggested in a comment you might work around this by splitting your struct into two separate types. That way you could have a type with just one property, you initialize it manually, and you’re all done.

Related

Saving object to be accessible anywhere

I have a condition hereby let's say a user has logged in by calling an API. And the response contain's user's details. Is there anyway we can save the details of the user as an object and be accessible globally? Below is how I used ObjectMapper to call the api
For the model class:
import ObjectMapper
class User: Mappable {
var id: Int?
var userId: Int?
var orgId: Int?
var type: Int?
var email: String?
var lang: String?
var nickname: String?
var status: Int?
var token: String?
required init(map: Map) {
mapping(map: map)
}
func mapping(map: Map) {
id <- map["id"]
userId <- map["userId"]
orgId <- map["orgId"]
type <- map["type"]
failedAttempt <- map["failedAttempt"]
email <- map["email"]
lang <- map["lang"]
nickname <- map["nickname"]
status <- map["status"]
token <- map["token"]
}
}
And from my Networking file,
func postLogin(params: [String: Any], controller: UIViewController, completion: #escaping (User) -> Void) {
alamofire("/login", method: .post, token: false, params: params, controller: controller) { result in
if let userDetails = Mapper<User>().map(JSON: result as! [String: Any]) {
DispatchQueue.main.async {
completion(userDetails)
}
}
}
}
Some solutions may be using UserDefaults but it's just not practical to be using 9 UserDefaults to save the 9 keys that we got from this response. What are the suggested ways of approach we can go about this where user logged in, we save these details as an object globally and even when after closing the app, the details are not reseted? Thank you all
I agree that saving 9 individual keys in UserDefaults is not practical. But why not encode the whole thing as JSON and save the resulting Data?
extension User: Codable { }
// Assuming user is an instance of User
guard let userJSON = try? JSONEncoder().encode(user) else {
// handle encoding failure
}
let userDefaultsKeyForUser = "com.yourdomain.snazzyapp.userInfo"
UserDefaults.standard.set(userJSON, forKey: userDefaultsKeyForUser)
I think Swift will automatically synthesize Codable conformance for your User class, but you might need to make it final. Or implement Codable explicitly.
To retrieve it
guard let userJSON = UserDefaults.standard.data(forKey: userDefaultsKeyForUser) else {
// Handle missing data (first run maybe?)
}
guard let user = try? JSONDecoder().decode(User.self, from: userJSON) else {
// Handle decoding failure
}
Alternative encoding/decoding methods
Although conforming to Codable would be the preferred way to do this, that can sometimes be a problem for classes, particularly when inheritance is involved. In particular Codable is a combination of Encodable and Decodable. Decodable is the one that is sometimes a problem for classes. The issue has to do with the required init(from decoder: Decoder) throws having to be declared directly in the class, not in an extension, and then you might have trouble with encoding the super. If your class is the base class that shouldn't be a problem. This is mainly a problem when you inherit from AppKit/UIKit classes.
If for some reason User can't conform to Codable, you can use NSCoding instead. It works a little differently.
In the worst case you could implement methods where you manually encode/decode each property explicitly to data. You could even store them as binary, but you'll probably need to do something like store byte counts for the strings so you know where each one starts and ends, and you'll need to come up with a scheme to indicate when they are nil in the encoding. It's not as flexible as JSON, which is why it's a last resort... although I will note that it is much faster. That wouldn't matter much for your User class, but I wrote a neural network library where I wanted to save the model at checkpoints during training, which meant encoding many layers of very large matrices. Millions of parameters. Reading and writing the model was about 20x faster with my own binary encoding than letting Codable handle it, even when I had Codable saving it as a binary plist.
A third option would be to re-work your User class. Either make it a struct, or use a struct internally to store its properties and use computed properties to get and set them in your class. All of your class's properties already conform to Codable, so Swift can definitely synthesize Codable conformance for a struct with those properties. If you still wrap the struct inside of a class, then you just have an initializer that takes a Data, and do the same decoding I showed above, to set the wrapped struct, and an encode method (or even a computed var) that encodes the struct as above and returns the Data.
I don't think you'll need these alternate solutions, but I mention them just in case I'm wrong, and because they're useful to know about for future situations.
You have to ask yourself how you would like to access this object with compilator autocompletion from different places in your app. Secondly, what is the life span of the object? The object should be accessible during login / logout, app session or app life time?
Let's start with the object life span.
login / logout, the object can be stored in memory
app session, the object can be stored in memory
app install / delete, the object should be stored in UserDefaults, Database or a file.
Autocompletion Driven Development aka ADD:
Deciding where to store userDetails object and how to access it
Let's say that you have a logic in a view controller which hide or unhide a view in a initial configuration defined in viewDidLoad. Write some prototype code to decide how you would like to access the 'userDetails' object from different places of the app.
func viewDidLoad() {
super.viewDidLoad()
if userDetails.status {
//userDetails is a global instance of a user object
}
if session.userDetails.status {
//session is a global instance of a Session object which has a property which stores a User instance in userDetails
}
if Session.current.userDetails.status {
// Session is a type and 'current' is a singleton of that type which has a userDetails property which stores an instance of User type
}
if User.userDetails.status {
// User is your type which can have a static property 'userDetails' which stores a User instance
}
}

how to encode an Int in swift using the protocol Encoder

I was surfing in swift STD particularly in the "Int structure", and there is a function named
func encode(to encoder: Encoder) throws
https://developer.apple.com/documentation/swift/int/2894461-encode.
I saw many examples using this function, however all the examples contained structs and classes I was wondering how can I use this function to encode a variable of type Int.
Thanks
As you may be aware, Int conforms to Encodable which defines the encode(to:) method that you have discovered. You can use an encoder such as a JSONEncoder or PropertyListEncoder to encode any type that conforms to the Encodable protocol. For example:
let encoder = JSONEncoder()
let jsonData = try encoder.encode(5)
You don't need to worry about calling encode(to:) on Int directly. Your encoder should handle all of that under the hood.

What does it mean when no implementation is provided by the class/struct that adopts a protocol?

As far as I understand, a protocol is a blueprint for properties, methods, and other requirements without the actual implementation. The conformance to a protocol means providing the actual implementation of the blueprint. But, I frequently see classes and struct's adopting a protocol without actually providing the implementation.
For example:
class Item: Codable {
var title: String = ""
var done: Bool = false
}
var itemArray = [Item]()
let encoder = PropertyListEncoder()
do {
let data = try encoder.encode(itemArray)
try data.write(to: dataFilePath!)
} catch {
print("Error encoding item array, \(error)")
}
The Item class here adopts the Codable protocol and the instance of the class is used as an argument to the instance method of PropertyListEncoder(). However, no implementation for the Codable protocol was provided or used in this process.
There are protocols that offer what is called automatic synthesis. In the case of Codable that means that as long as the properties of Item conform to Codable, methods such as init(from:) or encode(to:) are automatically added to that type.
As long as all of its properties are Codable, any custom type can also be Codable.
Encoding and Decoding Custom Types
Another great example is Equatable, see Conforming to the Equatable Protocol
Automatic synthesis is done via protocol extensions.
The Codable protocol is one of a few protocols (Equatable, Hashable) that can be synthesized by the compiler so long as all properties of a type also conform to that protocol. This means that the compiler is generating the implementation for the protocol that you noticed was missing

iOS - Why the apple using structure for Notification name instead of String Constants? [duplicate]

Why UIImagePickerController.InfoKey type is struct not a string ,what is the benefit to use struct as a dictionary key instead of string?
public struct InfoKey : Hashable, Equatable, RawRepresentable {
public init(rawValue: String)
}
}
extension UIImagePickerController.InfoKey {
public static let mediaType: UIImagePickerController.InfoKey
public static let originalImage: UIImagePickerController.InfoKey // a UIImage
public static let editedImage: UIImagePickerController.InfoKey // a UIImage
public static let cropRect: UIImagePickerController.InfoKey // an NSValue (CGRect)
It's simple, the benefit is the ability to check key values during compilation. This way, you won't be able to pass a String directly and if you do, you have to manually wrap it into a InfoKey structure, making your intention clear.
Most of the times you should be using one of the predefined constants.
An enum would be a better solution but probably that would break some existing code (and cannot be really enforced in Objective-C).
If Apple engineers were creating a new API today, they wouldn't probably even use a dictionary but they would use a custom object to pass the values to the delegate method.

Swift Decodable: how to transform one of values during decoding?

Be default, Decodable protocol makes translation of JSON values to object values with no change. But sometimes you need to transform value during json decoding, for example, in JSON you get {id = "id10"} but in your class instance you need to put number 10 into property id (or into even property with different name).
You can implement method init(from:) where you can do what you want with any of the values, for example:
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
latitude = try container.decode(Double.self, forKey:.latitude)
longitude = try container.decode(Double.self, forKey: .longitude)
// and for example there i can transform string "id10" to number 10
// and put it into desired field
}
Thats sounds great for me, but what if i want to change value just for one of the JSON fields and left all my other 20 fields with no change? In case of init(from:) i should manually get and put values for every of 20 fields of my class! After years of objC coding it's intuitive for me to first call super's implementation of init(from:) and then make changes just to some fields, but how i can achieve such effect with Swift and Decodable protocol?
You can use a lazy var. The downside being that you still have to provide a list of keys and you can't declare your model a constant:
struct MyModel: Decodable {
lazy var id: Int = {
return Int(_id.replacingOccurrences(of: "id", with: ""))!
}()
private var _id: String
var latitude: CGFloat
var longitude: CGFloat
enum CodingKeys: String, CodingKey {
case latitude, longitude
case _id = "id"
}
}
Example:
let json = """
{
"id": "id10",
"latitude": 1,
"longitude": 1
}
""".data(using: .utf8)!
// Can't use a `let` here
var m = try JSONDecoder().decode(MyModel.self, from: json)
print(m.id)
Currently you are forced to fully implement the encode and decode methods if you want to change the parsing of even a single property.
Some future version of Swift Codable will likely allow case-by-case handling of each property's encoding and decoding. But that Swift feature work is non-trivial and hasn't been prioritized yet:
Regardless, the goal is to likely offer a strongly-typed solution that allows you to do this on a case-by-case basis with out falling off the "cliff" into having to implement all of encode(to: and init(from: for the benefit of one property; the solution is likely nontrivial and would require a lot of API discussion to figure out how to do well, hence why we haven't been able to do this yet.
- Itai Ferber, lead developer on Swift 4 Codable
https://bugs.swift.org/browse/SR-5249?focusedCommentId=32638

Resources