2

I am trying to create a multiplayer game that will send moves between players using Game Center. I'm still learning a lot about programming, so please excuse me if my question is ill-formed. Also, I am not very familiar with Obj-C, so a Swift answer would be great.

In my toy program to try and teach myself, I am trying to follow the strategy used by Shayne Meyer using the GameKitHelper class here: https://github.com/shaynemeyer/SwiftCircuitRacer/tree/master/SwiftCircuitRacer

Using this approach, Shayne sends messages to other players online using structs sent as NSData. I am able to send integers (e.g., the ILoveYou message) but not messages that carry a string property (e.g., the Thanks message). In this latter case I get "Thread 1: EXC_BAD_ACCESS(code=1, address=0x78674100)" at the line "var messageThanks = UnsafePointer,MesssageThanks>(data.bytes).memory"

Eventually, I would like to send game moves that provide both strings and integers together. How does one send a message struct as NSData when properties also include a string? Secondly, I would be appreciative if someone could help me understand fundamentally what is going on when the data is packaged and how what UnsafePointer is doing as it related to sending data via Game Center.

Thank you. Cliff

enum MessageType: Int {
    case ILoveYou, Thanks
}

struct Message {
    let messageType: MessageType
}    
struct MessageILoveYou {
    let message: Message
    let messageSenderNumber: UInt32    
}

struct MessageThanks {
    let message: Message
    let messageSenderName: String
    let messageSenderNumber: UInt32    
}

func sendILoveYou() {    
    println("sendILoveYou:")
    let nameNumber = UInt32(56)
    var message = MessageILoveYou(message: Message(messageType: MessageType.ILoveYou), messageSenderNumber: nameNumber)
    let data = NSData(bytes: &message, length: sizeof(MessageILoveYou))
    sendData(data)    
}

func sendThanks() {
    println("sendThanks:")
    let nameString = "Don J"
    let senderNumberInt = UInt32(88)
    var message = MessageThanks(message: Message(messageType: MessageType.Thanks), messageSenderName: nameString, messageSenderNumber: senderNumberInt)        
    let data = NSData(bytes: &message, length: sizeof(MessageThanks))
    sendData(data)        
}

func matchReceivedData(match: GKMatch, data: NSData, fromPlayer player: String) {

    println("matchReceivedData:")
    var message = UnsafePointer<Message>(data.bytes).memory        
    if message.messageType == MessageType.ILoveYou {            
        println("messageType == ILoveYou")
        let messageILoveYou = UnsafePointer<MessageILoveYou>(data.bytes).memory
        iLoveYouThanksDelegate?.iLoveYouReceived(from: messageILoveYou.messageSenderNumber)

    } else if message.messageType == MessageType.Thanks {            
        println("messageType == Thanks")
        var messageThanks = UnsafePointer<MessageThanks>(data.bytes).memory
        iLoveYouThanksDelegate?.thanksReceived(from: messageThanks.messageSenderName)
    }
}

func sendData(data: NSData) {
    var sendDataError: NSError?
    let gameKitHelper = GameKitHelper.sharedInstance

    if let multiplayerMatch = gameKitHelper.multiplayerMatch {
        let success = multiplayerMatch.sendDataToAllPlayers(data, withDataMode: .Reliable, error: &sendDataError)
        if !success {
            if let error = sendDataError {
                println("Error:\(error.localizedDescription)")
                matchEnded()
            }
        }
    }
}
cdub
  • 180
  • 1
  • 12

1 Answers1

0

The problem here is that when you create a String in Swift, it allocates a bit of memory itself, and then uses that memory to store the actual characters of the string. All that the string value really holds is some data representing a pointer to that memory and some other info (like how much memory has been allocated, so that it can be freed properly.

You can see this here:

let str = "This is quite a long string, certainly more than 24 bytes"
sizeofValue(str)  // and yet this only returns 24

When you stuff variables into an NSData object, the initializer takes a pointer to the memory of the string variable that is holding those pointers, not the characters itself:

// only storing those 24 bytes, not the actual string
let data = NSData(bytes: &str, length: sizeofValue(str))

Note, the type of the bytes argument is UnsafePointer<Void>. This is an indication that you are heading into tricky territory.

Then, when you unmarshal the data at the other end, all your receiver is going to get is some pointers to random memory (sadly, memory on the other user’s device!)

If you want to put string values into an NSData object, you are going to need to marshal them first into raw data. For example, you could encode them into an array:

let data = Array(str.utf8).withUnsafeBufferPointer { buf in
    NSData(bytes: buf.baseAddress, length: buf.count)
}

As it happens, since this is a common thing to want to do, there’s a method to do this directly:

let data = str.dataUsingEncoding(NSUTF8StringEncoding)

Then, to unpack the data, you can use NSString’s constructor from an NSData object:

let newStr = NSString(data: data, encoding: NSUTF8StringEncoding)

edit: if you wanted to encode more than just a string in a single NSData, you could do something along these lines… I should say, I’ve never had to do this myself so I’m in no way familiar with the standard practices for this, there could be much better techniques or helper classes/functions. Hopefully someone with more experience can edit to show how to do this properly :)

var type = MessageType.Thanks

// start the data with the type
let data = NSMutableData(bytes: &type, length: sizeofValue(type))

// then append the string
data.appendData(Array(str.utf8).withUnsafeBufferPointer { buf in
    NSMutableData(bytes: buf.baseAddress, length: buf.count)
    })

switch UnsafePointer<MessageType>(data.bytes).memory {
case .ILoveYou:
// ...
case .Thanks:
    let str = NSString(data: data.subdataWithRange(NSMakeRange(1, data.length-1)), encoding: NSUTF8StringEncoding)
}
Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118
  • Thanks. This helps me understand a little better. Is there an approach so that I can not only have data that has a single packed string, but also an approach for a message struct, which has both an integer property AND a string property inside? Or perhaps the answer you gave me accomplishes this and I'm just not adept enough to see that. – cdub Apr 05 '15 at 20:16
  • Updated with some code packing multiple things, but I’m definitely not an expert in this kind of stuff so there could be some idioms I’m butchering... – Airspeed Velocity Apr 05 '15 at 20:40
  • Would it be asinine (or acceptable) if I just packaged all my game state information (for a Turn-based Game Center game) as a single string, sent that string as NSData, and then decoded that into an array of turn objects on the device? I fear this is the bone-headed way to do it and I will end up having to do something else down the line because I don't foresee the problems I will have created. – cdub Apr 06 '15 at 16:03