Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to encode and decode struct to NSData in swift?

Tags:

sqlite

ios

swift

I have the following struct definition:

struct ThreadManager: Equatable {
  let fid: Int
  let date: NSDate
  let forumName: String
  let typeid: Int
  var page: Int
  var threadList: [Thread]
  var totalPageNumber: Int?
}

and the thread is :

  struct Thread: Equatable {
    let author: Author
    let replyCount: Int
    let readCount: Int
    let title: String
    let tid: Int
    let isTopThread: Bool
    var attributedStringDictionary: [String: NSAttributedString]
    var postDescripiontTimeString: String
    var hasRead: Bool
}

How can I encode a ThreadManager variable to NSData? I tried to used the following functions, but it does not worK.

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

func decode<T>(data: NSData) -> T {
  let pointer = UnsafeMutablePointer<T>.alloc(sizeof(T))
  data.getBytes(pointer, length: sizeof(T))

  return pointer.move()
}

I have ThreadManager items, and I want to store them into sqlite. So I need to convert them to NSData. I have a variable called threadManager, the number of items in its threadList is about 70. I run the code and set a breakpoint, and input encode(threadManager) in xcode console, it is only 73bytes. It is wrong. How can I encode and decode those struct to NSData.

like image 387
leizh00701 Avatar asked Oct 30 '25 20:10

leizh00701


2 Answers

If your database is to be read on any other platform (Android, the web, wherever), you'd better choosing a cross-platform format such as JSON, or spread your struct members in their dedicated columns in a database table.

If you only target iOS/OSX/tvOS/etc, I recommend NSCoder. It is efficient, and most importantly:

  1. NSCoder is platform-independant, which means that your NSData coding and decoding is not dependent on the particular memory layout currently used by the platform. For example, you don't have to fear 32 / 64 bits compatibility.
  2. NSCoder lets you change your type over time, while keeping the ability to import old versions of your struct.

The code below adds a asData() function to your struct, and an init(data:) initializer. Those two let you go back and forth from your struct to NSData.

import Foundation

struct MyStruct {
    let name: String
    let date: NSDate
}

extension MyStruct {
    init(data: NSData) {
        let coding = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! Coding
        name = coding.name as String
        date = coding.date
    }

    func asData() -> NSData {
        return NSKeyedArchiver.archivedDataWithRootObject(Coding(self))
    }

    class Coding: NSObject, NSCoding {
        let name: NSString
        let date: NSDate

        init(_ myStruct: MyStruct) {
            name = myStruct.name
            date = myStruct.date
        }

        required init?(coder aDecoder: NSCoder) {
            self.name = aDecoder.decodeObjectForKey("name") as! NSString
            self.date = aDecoder.decodeObjectForKey("date") as! NSDate
        }

        func encodeWithCoder(aCoder: NSCoder) {
            aCoder.encodeObject(name, forKey: "name")
            aCoder.encodeObject(date, forKey: "date")
        }
    }
}

let encodedS = MyStruct(name: "foo", date: NSDate())
let data = encodedS.asData()
let decodedS =  MyStruct(data: data)
print(decodedS.name)
print(decodedS.date)
like image 99
Gwendal Roué Avatar answered Nov 02 '25 11:11

Gwendal Roué


@Gwendal Roué : you are right, but I have to build another class according to each struct. I used the following method, it is ugly, but it works. Can you help me to improve it?

init(data: NSData) {
    let dictionary = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! NSDictionary
    fid = (dictionary["fid"] as! NSNumber).integerValue
    date = dictionary["date"] as! NSDate
    forumName = dictionary["forumName"] as! String
    typeid = (dictionary["typeid"] as! NSNumber).integerValue
    page = (dictionary["page"] as! NSNumber).integerValue
    totalPageNumber = (dictionary["totalPageNumber"] as? NSNumber)?.integerValue
    let threadDataList = dictionary["threadDataList"] as! [NSData]
    threadList = threadDataList.map { Thread(data: $0) }
}
extension ThreadManager {
func encode() -> NSData {
    let dictionary = NSMutableDictionary()
    dictionary.setObject(NSNumber(integer: fid), forKey: "fid")
    dictionary.setObject(date, forKey: "date")
    dictionary.setObject(forumName, forKey: "forumName")
    dictionary.setObject(NSNumber(integer: typeid), forKey: "typeid")
    dictionary.setObject(NSNumber(integer: page), forKey: "page")
    if totalPageNumber != nil {
        dictionary.setObject(NSNumber(integer: totalPageNumber!), forKey: "totalPageNumber")
    }
    let threadDataList: [NSData] = threadList.map { $0.encode() }
    dictionary.setObject(threadDataList, forKey: "threadDataList")

    return NSKeyedArchiver.archivedDataWithRootObject(dictionary)
}
}
like image 30
leizh00701 Avatar answered Nov 02 '25 11:11

leizh00701



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!