3

In my app I am doing something like this:

struct Record {
    var exampleData : String
}

class ExampleClass : UIViewController {
    let records = [Record]()

    override func viewDidLoad() {
        super.viewDidLoad()

        let data = NSKeyedArchiver.archivedDataWithRootObject(self.records) as! NSData
    }

    ...

}

But in the last line of viewDidLoad() I got this error:

Argument type '[Record]' does not conform to expected type 'AnyObject'

How can I fix this? Thanks.

2
  • 1
    If you want to keep struct you can encode data using withUnsafePointer(): gist.github.com/nubbel/5b0a5cb2bf6a2e353061 Commented Oct 1, 2015 at 23:12
  • @AaronBrager Thanks, that's exactly what I need. Can you write an answer so I can accept it? Commented Oct 1, 2015 at 23:16

3 Answers 3

7

If you want to keep struct, you can encode data using withUnsafePointer(). Here's an example, which I adapted from this Gist:

import UIKit

enum EncodingStructError: ErrorType {
    case InvalidSize
}

func encode<T>(var value: T) -> NSData {
    return withUnsafePointer(&value) { p in
        NSData(bytes: p, length: sizeofValue(value))
    }
}

func decode<T>(data: NSData) throws -> T {
    guard data.length == sizeof(T) else {
        throw EncodingStructError.InvalidSize
    }

    let pointer = UnsafeMutablePointer<T>.alloc(1)
    data.getBytes(pointer, length: data.length)

    return pointer.move()
}

enum Result<T> {
    case Success(T)
    case Failure
}

I added some error handling and marked the method as throws. Here's one way you can use it, in a docatch block:

var res: Result<String> = .Success("yeah")

var data = encode(res)

do {
    var decoded: Result<String> = try decode(data)

    switch decoded {
    case .Failure:
        "failure"
    case .Success(let v):
        "success: \(v)" // => "success: yeah"
    }
} catch {
    print(error)
}

The error handling I added will not decode if the NSData length doesn't match the type size. This can commonly happen if you write the data to disk, the user updates to a newer version of the app with a different-sized version of the same type, and then the data is read in.

Also note that sizeof() and sizeofValue() may return different values on different devices, so this isn't a great solution for sending data between devices (NSJSONSerialization might be better for that).

Sign up to request clarification or add additional context in comments.

2 Comments

Aaron and I spoke earlier today - changing pointer.move() to pointer.memory allows you to extend this code to reference types and objective-c types as well.
If I use GameCenter I must use NSData not Json, and transfer bytes between devices. What is the suitable method for that?
3

AnyObject means any reference type object, primarily a class. A struct is a value type and cannot be passed to a function needing an AnyObject. Any can be used to accept value types as well as reference types. To fix your code above, change struct Record to class Record. But I have a feeling you may want to use a struct for other reasons. You can create a class wrapper around Record that you can convert to and from to use for functions that need an AnyObject.

Comments

1

I did a similar thing:

    static func encode<T>(value: T) -> NSData {
        var val = value
        return withUnsafePointer(to: &val) { pointer in
            NSData(bytes: pointer, length: MemoryLayout.size(ofValue: val))
        }
    }

    static func decode<T>(data: NSData) -> T {
        guard data.length == MemoryLayout<T>.size.self else {
            fatalError("[Credential] fatal unarchiving error.")
        }

        let pointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
        data.getBytes(pointer, length: data.length)

        return pointer.move()
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.