4

let me start by saying that I have already implemented Decodable which decodes JSON into several objects with these two Integer values:

public class ARBufferData: DecoderUpdatable {

    private var previousStation: Int
    private var numberOfElements: Int

    func update(from decoder: Decoder) throws {
    //Still needs work
    }
}

What I am now trying to achieve is making the created objects updatable so that when a value in the JSON changes (e.g. numberOfElements) only the value is changed in the corresponding object. I believe this guide can enable me to do it, but I am having trouble implementing it: Understanding and Extending Swift 4’s Codable

This is the extension of KeyedDecodingContainer:

extension KeyedDecodingContainer {
    func update<T: DecoderUpdatable>(_ value: inout T, forKey key: Key, userInfo: Any) throws {
        let nestedDecoder = NestedDecoder(from: self, key: key)
        try value.update(from: nestedDecoder)
    }
}

The reason this would be helpful is that I can then set a property observer on that value and trigger a redraw of the visualisation.

I would be very grateful, if anyone can help or point me in the right direction.

Thank you!

Cheers

Sam
  • 51
  • 1
  • 7

1 Answers1

2

There are two ways to update the class. One, you decode each int by itself and compare. Two, you implement DecoderUpdatable for Int and call container.update with them as argument.

public class ARBufferData: NSObject, Decodable, DecoderUpdatable {
    init(previousStation: Int, numberOfElements: Int) {
        self.previousStation = previousStation
        self.numberOfElements = numberOfElements
    }

    @objc dynamic var previousStation: Int
    @objc dynamic var numberOfElements: Int

    private enum CodingKeys: String, CodingKey {
        case previousStation, numberOfElements
    }

    public func update(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        try container.update(&previousStation, forKey: .previousStation)
        try container.update(&numberOfElements, forKey: .numberOfElements)
    }
}

extension Int: DecoderUpdatable {
    public mutating func update(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let result = try container.decode(Int.self)
        guard result != self else { return }
        self = result
    }
}

I do not know whether the blogpost-writer intended it this way though. If he did, then generating the DecoderUpdatable conformances for the basic types could be a use case for Sourcery, but that's off topic here.

In Swift4 there is an interesting way to observe which you may also be interested in:

let token = buffer.observe(\.numberOfElements, options: [.new, .old]) {
    object, change in
    if change.oldValue != change.newValue {
        // Act on change of buffer.numberOfElements.
    }
}

Source

Fabian
  • 5,040
  • 2
  • 23
  • 35
  • Ok I have implemented Int:DecoderUpdateable, how do I now call update(from decoder: Decoder) periodically? I know how the normal decoder gets called ( let urlData = try JSONDecoder().decode([ARBufferData].self, from: data)), but cannot figure out how to call the update method. Thank you! – Sam Jul 27 '18 at 07:33
  • Since you want to update an array you probably have to implement `DecoderUpdatable` for it and then call `try decoder.update(&buffers, from: data)`. – Fabian Jul 27 '18 at 10:34
  • Yes I have an array of type [ARBufferData] which is created once at the beginning through the normal implementation of Decodable and this call `var urlData = try decoder.decode([ARBufferData].self, from: data)`. Now I need to update this array continuously and if I write `try decoder.update(&urldata, from: data)` I get an error saying "[ARBufferData] does not conform to type DecoderUpdatable". Once this works this will be incredible for me, thank you for taking the time! – Sam Jul 27 '18 at 12:09
  • I told you go implement DecoderUpdatable for Array then you come back and answer "Array has DecoderUpdatable not implemented yet". What the hell? If you don't care enough to do it then I don't care either. – Fabian Jul 27 '18 at 12:17
  • Sorry I did not mean to be offensive. I actually tried implementing `extension Array: DecoderUpdatable{}`, but could not figure out how to do it. My best guess is that the Array extension needs to call the update method of `ARBufferData` for each array element. I am ready to spend several hours and a lot of work on this. I do care, I just do not know how to do it. – Sam Jul 27 '18 at 13:09
  • I'll help you with questions. What if in &buffers there are wholly different ARBufferData objects? What if &buffers changed their order? What if some items vanished and got replaced with others? What if the buffers in the data are more than the ones in &buffers? You may find it easier to decode the new buffers in the update method and go from there comparing and then choosing what old buffer to update with what new buffer. You may make assumptions like the order is stable though if you want. You may require Equality of the buffers to compare buffers to be able to ignore order changes. – Fabian Jul 27 '18 at 14:05
  • The problem is comparing arrays and how to update the old array to reflect the new while achieving what you want out of KVO. There may be tutorials out there.. Dunno whether its worth the time though. And I dunno whether it will work as you want either. – Fabian Jul 27 '18 at 14:08
  • With Equality I mean identity comparison. `return a.id == b.id` . – Fabian Jul 27 '18 at 14:15
  • I have realised I am not good enough a programmer yet to implement this. Instead I have opted for a less-efficient, but more standard solution of decoding periodically to new objects, then comparing the numberOfElements values and copying them if they are not equal. I thank you for helping out a beginner programmer and wish you a pleasant day! :) Should I still mark your answer as correct? – Sam Jul 27 '18 at 14:50