using DidSet or WillSet on decodable object - swift

I currently have a Codable object which I am initializing using:
let decoder = JSONDecoder()
let objTest: TestOBJClass = try! decoder.decode(TestOBJClass.self, from: data)
TestOBJClass:
public class TestOBJClass: Codable {
var name: String
var lastname: String?
var age: Int? {
DidSet {
//Do Something
}
}
}
The object gets initialized with no issues. However, the DidSet function on the age variable is not getting called. Are we able to use these functions when working with Decodable or Encodable objects? Thank you for your help!

As #Larme pointed out in comments, property observers like didSet aren't called in initializers, including the one for Codable conformance. If you want that behavior, refactor the code from your didSet into a separate named function, and then at the end of your initializer call that function.
struct MyStruct: Codable
{
...
var isEnabled: Bool {
didSet { didSetIsEnabled() }
}
func didSetIsEnabled() {
// Whatever you used to do in `didSet`
}
init(from decoder: Decoder) throws
{
defer { didSetIsEnabled() }
...
}
}
Of course, that does mean you'll need to explicitly implement Codable conformance rather than relying on the compiler to synthesize it.

Related

How to make `ObservableObject` with `#Published` properties `Codable`?

My codable observable Thing compiles:
class Thing: Codable, ObservableObject {
var feature: String
}
Wrapping feature in #Published though doesn’t:
class Thing: Codable, ObservableObject {
#Published var feature: String
}
🛑 Class 'Thing' has no initializers
🛑 Type 'Thing' does not conform to protocol 'Decodable'
🛑 Type 'Thing' does not conform to protocol 'Encodable'
Apparently Codable conformance can’t be synthesized anymore because #Published doesn’t know how to encode/decode its wrappedValue (because it doesn’t conform to Codable even if its wrapped value does)?
Okay, so I’ll let it know how to do it!
extension Published: Codable where Value: Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue) // 🛑 'wrappedValue' is unavailable: #Published is only available on properties of classes
}
public init(from decoder: Decoder) throws {
var container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Value.self) // 🛑 'wrappedValue' is unavailable: #Published is only available on properties of classes
}
}
So sorrow, much misery, many sadness! 😭
How can I easily add back Codable synthesis (or similar) without defining encode(to:) and init(from:)?
I don't know why you would want to do it, I think you should code the struct, not the published class. Something like this:
struct CodableStruct: Codable {
var feature1: String = ""
var feature2: Int = 0
}
class Thing: ObservableObject {
#Published var features: CodableStruct = .init()
}

Codable conformance at #Published array of a codable-conformed Struct

I am tinkering around with URLSession and was essentially building a super simple app to load a JSON file into ContentView sort of like a friends list from Facebook and wanted some clarity on not any errors I'm having, but rather, the internal workings of Swift's Codable protocol. Here are some code and explanations:
struct User: Identifiable, Codable {
struct Friend : Identifiable, Codable {
var name : String
var id : String
}
var id : String
var isActive: Bool
var name : String
var age: Int
var company : String
var address : String
var about : String
var registered : String
var friends : [Friend]
var checkIsActive: String {
return self.isActive ? "🟢" :"🔴"
}
}
So to summarize above, I have a User struct which contains a bunch of properties that conform to Codable.
class UsersArrayClass: ObservableObject {
#Published var userArray = [User]()
}
However, I have another class, UsersArrayClass which creates a #Published var userArray of User struct objects. This class conforms to the #ObservableObject protocol, but of course, when I try to make it conform to Codable, it does not like that likely because of the #Published property wrapper being applied on the array itself... This is what is essentially confusing me though if the User struct has Codable conformance, why doesn't userArray which contains User objects automatically conform to Codable as well?
I was thinking that maybe loading all of this up into a Core Data model would solve my problems, but I still can't move on unless I understand what I'm missing out on here, so thanks in advance for any input.
It is hacky, but we can via extension add Codable conformance to Published despite lacking access to Published internals.
extension Published: Codable where Value: Codable {
public func encode(to encoder: Encoder) throws {
guard
let storageValue =
Mirror(reflecting: self).descendant("storage")
.map(Mirror.init)?.children.first?.value,
let value =
storageValue as? Value
??
(storageValue as? Publisher).map(Mirror.init)?
.descendant("subject", "currentValue")
as? Value
else { fatalError("Failed to encode") }
try value.encode(to: encoder)
}
public init(from decoder: Decoder) throws {
self.init(initialValue: try .init(from: decoder))
}
}
Quick check:
class User: ObservableObject, Codable {
#Published var name = "Paul"
}
struct ContentView: View {
#ObservedObject var user = User()
var body: some View {
let data = try? JSONEncoder().encode(user)
let dataFromStr = """
{
"name": "Smith"
}
"""
.data(using: .utf8)
let decoded = try! JSONDecoder().decode(User.self, from: dataFromStr!)
return
VStack{
Text(verbatim: String(data: data!, encoding: .utf8) ?? "encoding failed")
Text(decoded.name)
}
}
}
/*
Cannot automatically synthesize 'Encodable' because 'Published<[User]>'
does not conform to 'Encodable' #Published var userArray = [User]()
*/
// Published declaration
#propertyWrapper struct Published<Value> { ... }
Published don't conform to Codable or any common protocol in Foundation currently
Trying to make Published conform to Codeable resulting error below:
/*
Implementation of 'Decodable' cannot be
automatically synthesized in an extension in a different file to the type
*/
extension Published: Codable where Value: Codable {}

Codable variable in Codable Class

Given this class:
class MyClass: Codable {
var variable : Codable? = nil
}
I get error:
Type 'MyClass' does not conform to protocol 'Decodable'
Type 'MyClass' does not conform to protocol 'Encodable'
How can I have a generic variable that conforms to Codable as a property in a Codable class?
May be like this if i got your question correctly
class MyClass<T: Codable>: Codable {
var variable : T? = nil
}
If you try to implement your decoder you will have something like:
class MyClass: Codable {
var variable: Codable?
enum MyClassKeys: String, CodingKey {
case variable = "variable"
}
required init(from decoder: Decoder) {
let container = try! decoder.container(keyedBy: MyClassKeys.self)
variable = try! container.decode(XXXX, forKey: .variable)
}
}
But instead of XXXX, you should put a class type (for example String.self). What can you put here? Its not possible for the compiler to guess what you want. Instead of Codable type maybe you can put a class which conform to Codable protocol. So you can have something like this:
class MyClass: Codable {
var variable: MyClass?
enum MyClassKeys: String, CodingKey {
case variable = "variable"
}
required init(from decoder: Decoder) {
let container = try! decoder.container(keyedBy: MyClassKeys.self)
variable = try! container.decode(MyClass.self, forKey: .variable)
}
}
You can't.
But as a workaround, you can create an enum conforming to Codable.
enum SubType: Codable {
case foo
case bar
}

A codable structure contains a protocol property

I have a protocol which is inherited from codable
protocol OrderItem:Codable {
var amount:Int{get set}
var isPaid:Bool{get set}
}
And a struct conform this protocol
struct ProductItem:OrderItem {
var amount = 0
var isPaid = false
var price = 0.0
}
However when I put this structure into a codable structure, I got errors
struct Order:Codable {
var id:String
var sn:String = ""
var items:[OrderItem] = []
var createdAt:Int64 = 0
var updatedAt:Int64 = 0
}
The errors are
Type 'Order' does not conform to protocol 'Encodable'
Type 'Order' does not conform to protocol 'Decodable'
But if I change items:[OrderItem] to items:[ProductItem] , everything works!
How can I fix this problem?
You cannot do that because a protocol only states what you must do. So when you conform your protocol X to Codable, it only means that any type that conforms to X must also conform to Codable but it won't provide the required implementation. You probably got confused because Codable does not require you to implement anything when all your types already are Codable. If Codable asked you to, say, implement a function called myFunction, your OrderItem would then lack the implementation of that function and the compiler would make you add it.
Here is what you can do instead:
struct Order<T: OrderItem>: Codable {
var id:String
var sn:String = ""
var items: [T] = []
var createdAt:Int64 = 0
var updatedAt:Int64 = 0
}
You now say that items is a generic type that conforms to OrderItem.
It's worth mentioning that if you have a property of an array and the type is a protocol: let arrayProtocol: [MyProtocol] and the array contains multiple types that all conform to MyProtocol, you will have to implement your own init(from decoder: Decoder) throws to get the values and func encode(to encoder: Encoder) throws to encode them.
So for example:
protocol MyProtocol {}
struct FirstType: MyProtocol {}
struct SecondType: MyProtocol {}
struct CustomObject: Codable {
let arrayProtocol: [MyProtocol]
enum CodingKeys: String, CodingKey {
case firstTypeKey
case secondTypeKey
}
}
so our decode will look like this:
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
// FirstType conforms to MyProtocol
let firstTypeArray = try values.decode([FirstType].self, forKey: .firstTypeKey)
// SecondType conforms to MyProtocol
let secondTypeArray = try values.decode([SecondType].self, forKey: .secondTypeKey)
// Our array is finally decoded
self.arrayProtocol: [MyProtocol] = firstTypeArray + secondTypeArray
}
and the same for encoded, we need to cast to the actual type before encoding:
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let firstActualTypeArray = arrayProtocol.compactMap{$0 as? FirstType}
let secondActualTypeArray = arrayProtocol.compactMap{$0 as? SecondType}
try container.encode(firstActualTypeArray, forKey: .firstTypeKey)
try container.encode(secondActualTypeArray, forKey: .secondTypeKey)
}
Codable is a type alias for Encodable and Decodable.
Therefore if you're implementing it you need to implement the following two functions.
func encode(to: Encoder)
init(from: Decoder)

Different process between Struct and Class in mutating asynchronously in Swift3

In struct type, mutating self in async process makes error as below.
closure cannot implicitly captured a mutating self
If I change the struct to class type, the error disappear.
What is difference between struct and class when mutate self in asynchronously?
struct Media {
static let loadedDataNoti = "loadedDataNotification"
let imagePath: String
let originalPath: String
let description: String
var imageData: Data?
let tag: String
var likeCount: Int?
var commentCount: Int?
var username: String?
var delegate: MediaDelegate?
public init(imagePath: String, originalPath: String, description: String, tag: String, imageData: Data? = nil) {
self.imagePath = imagePath
self.originalPath = originalPath
self.description = description
self.tag = tag
if imageData != nil {
self.imageData = imageData
} else {
loadImageData()
}
}
mutating func loadImageData() {
if let url = URL(string: imagePath) {
Data.getDataFromUrl(url: url, completion: { (data, response, error) in
if (error != nil) {
print(error.debugDescription)
return
}
if data != nil {
self.imageData = data! // Error: closure cannot implicitly captured a mutating self
NotificationCenter.default.post(name: NSNotification.Name(rawValue: Media.loadedDataNoti), object: data)
}
})
}
}
A struct is a value type. How does struct mutating work? It works by making a completely new struct and substituting it for the original. Even in a simple case like this:
struct S {
var name = "matt"
}
var s = S()
s.name = "me"
... you are actually replacing one S instance by another — that is exactly why s must be declared as var in order to do this.
Thus, when you capture a struct's self into an asynchronously executed closure and ask to mutate it, you are threatening to appear at some future time and suddenly rip away the existing struct and replace it by another one in the middle of executing this very code. That is an incoherent concept and the compiler rightly stops you. It is incoherent especially because how do you even know that this same self will even exist at that time? An intervening mutation may have destroyed and replaced it.
Thus, this is legal:
struct S {
var name = "matt"
mutating func change() {self.name = "me"}
}
But this is not:
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
struct S {
var name = "matt"
mutating func change() {delay(1) {self.name = "me"}} // error
}
When you mutate an instance of a value type -- such as a struct -- you're conceptually replacing it with a new instance of the same type, i.e. doing this:
myMedia.mutatingFuncToLoadImageData()
...can be thought of as doing something like this:
myMedia = Media(withLoadedData: theDownloadedData)
...except you don't see the assignment in code.
You're effectively replacing the instance that you call a mutating function on. In this case myMedia. As you may realize, the mutation has to have finished at the end of the mutating function for this to work, or your instance would keep changing after calling the mutating function.
You're handing off a reference to self to an asynchronous function that will try to mutate your instance after your mutating function has ended.
You could compile your code by doing something like
var myself = self // making a copy of self
let closure = {
myself.myThing = "thing"
}
but that would only change the value of the variable myself, and not affect anything outside of your function.