Search code examples
swiftstructmpc

Swift 3, Multipeer, send struct as Data


I am struggling with a problem. I want to send a struct to connected devices.

Here is the code I use copied into Playground

In the code I added some extension to the Data class for coding/decoding. I create a Dictionary of [String:Player] and use NSKeyedArchiver to create a Data record to send.

I don't do the send here but in my program I do. Here in the playground the code/decode work fine. When I do it in my program the decoding fails when extracting the Player from Data. It fails with: EXC_BAD_ACCESS

If I use a String instead of the Player structure it decodes fine in the didReceive data function. Fails only when using a Struct.

Anyone with some suggestions?

//: Playground - noun: a place where people can play
import UIKit

// Player structure
struct Player {
    var initials : String
    var name : String
}

//
// Data extension
//
extension Data {

    // struct
    init<T>(from value: T) {
        var value = value
        self.init(bytes: &value, count: MemoryLayout<T>.size)
    }

    // extract Struct
    func extract<T>(from: T.Type) -> T {
        return self.withUnsafeBytes { $0.pointee }   // FAILS HERE: EXC_BAD_ACCESS
    }
}

// Make a data record for sending
let sply = Player(initials: "PN", name: "Player Name")
let spda = Data(from: sply)
let sdic : [String:Data] = ["Player" : spda]
let sdta : Data = NSKeyedArchiver.archivedData(withRootObject: sdic)

// Do the sending
// ... send...

//Decode
// The following code that is used on the receiving device in 
// the "didReceive data" function.
let rdic = NSKeyedUnarchiver.unarchiveObject(with: sdta) as! [String : Data]
let rdta = rdic["Player"]!
let rply = rdta.extract(from: Player.self)
print(rply.name)        // Prints: "Player Name"

Solution

  • What worked for me was a custom class that inherits from NSObject and conforms to NSCoding.

    class Player: NSObject, NSCoding {
        var initials: String!
        var name: String!
    
        required convenience init(coder decoder: NSCoder) {
            self.init()
            self.initials = decoder.decodeObject(forKey: "initials") as! String
            self.name = decoder.decodeObject(forKey: "name") as! String
        }
    
        convenience init(initials: String, name: String) {
            self.init()
            self.initials = initials
            self.name = name 
        }
    
        func encode(with aCoder: NSCoder) {
            if let initials = initials { aCoder.encode(initials, forKey: "initials") }
            if let name = name { aCoder.encode(name, forKey: "name") }
        }
    }
    

    Make data to send:

    let data = NSKeyedArchiver.archivedData(withRootObject: Player(initials: "PN", name: "Player Name"))
    

    Get data back to Player:

    guard let player: Player = NSKeyedUnarchiver.unarchiveObject(with: data) as! Player? else { return }