Coding Key for @escaping Void function?

I would like to inherit from this SKNode object, but need the inherited object to conform to Codable, but I can't imagine what to do with the @escape Void function?

class Item: SKNode, Codable {

    var item1: SKSpriteNode
    var item2: SKSpriteNode
    var item3: SKCropNode
    var item4: () -> Void
    var item5: Bool  = true
    var item6: SKLabelNode?

    
    enum CodingKeys : CodingKey {
        case item4
    }

    required init?(coder decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        item4 = try container.decode(Data.self, forKey: .item4)
    }
    
    init(itemName: String, itemTitle: String? = "", itemAction: @escaping () -> Void) {
        item1 = SKSpriteNode(imageNamed: itemName)
        item6 = SKLabelNode(text: itemTitle)
        
        item2 = SKSpriteNode(color: UIColor.black, size: item1.size)
        item2.alpha = 0
        
        item3 = SKCropNode()
        item3.maskNode = item1
        item3.zPosition = 3
        item3.addChild(item2)

        item4 = itemAction
        
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Because the parameters are string types for the SKSpriteNodes I assume that the @escape void function is the issue. There being no Data type I assume that I would have to create one, but I don't know how that would work?

You cannot encode arbitrary closures. You will need to use some strategy of indirection.

Several examples:

  • If the closure is one of several static options, then the node can store an identifier instead, by which it can look up the actual closure from an external source.
  • If the closure can be derived from the other properties, you can skip encoding it and assemble it afresh upon decoding.

You can think of closures as type-erased structures with single method, and captured values being properties of that structure.

Your problem becomes easier if you actually use structs and existentials instead of closures.

Then, you are left with two sub-problems to solve:

1. Coding existentials

During encoding you need to encode metatype of the value, and the value itself. And during deciding decode metatype first, and then use it to decode the value.

But metatype’s themselves are not codable. So you need some bidirectional mapping between metatype and some codable identifier.

It might be tempting to use mangled names as codable identifier. I don’t remember details, but I recall that it did not work for me in general case - I was getting mangled strings with memory pointers embedded directly between ASCII bytes. And maybe other problems as well.

Maintaining a registry of serializable metatypes by hand is more reliable. Some codegen may help.

You can make registry available during coding through userInfo of the Decoder/Encoder.

2. Dealing with non-codable captured values.

This would need to be solved on a case-by-case basis.

Presumably you could represent that type as a struct and implement callAsFunction() so you could still call it like you would if it were an actual closure?

1 Like
Terms of Service

Privacy Policy

Cookie Policy