How to save an array of custom struct to NSUserDef

2020-02-10 08:09发布

问题:

I have an custom struct called 'News' that I want to append into the array to NSUserDefault. But it's showing error "Type 'News' does not conform to protocol 'AnyObject'".

I don't want to change the 'News' struct to a class since it's being used for other code already. Is there anyway that I can change NSUserDefaults.standardUserDefaults().arrayForKey("savedNewsArray") type to [News]?

var savedNews = NSUserDefaults.standardUserDefaults().arrayForKey("savedNewsArray")
var addSavedNews = savedNews as? [News]
addSavedNews.append(News(id: "00", title: newsTitle, source: source, imageURL: imageURL, url: url))
NSUserDefaults.standardUserDefaults().setObject(addSavedNews, forKey: "savedNewsArray")
NSUserDefaults.standardUserDefaults().synchronize()

Here is the 'News' struct.

public struct News {
    public var id: String
    public var title: String
    public var source: String?
    public var imageURL: String?
    public var date: NSDate?
    public var url: String

    init(id: String, title: String, source: String, imageURL: String, url: String) {
        self.id = id
        self.title = title
        self.source = source
        self.imageURL = imageURL
        self.url = url
    }
}

回答1:

NSUserDefaults can only save a very small set of types: NSData, NSString, NSNumber, NSDate, NSArray containing only these types, or NSDictionary containing only these types. So your best bet is to encode your struct using an NSKeyedUnarchiver, which required a value that conforms to NSCoding. You could make your type conform to this, but I think it's cleaner to hide that from your users and simply have a private class for the internal representation, like this:

struct Foo {
    var a : String
    var b : String?
}

extension Foo {
    init?(data: NSData) {
        if let coding = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? Encoding {
            a = coding.a as String
            b = coding.b as String?
        } else {
            return nil
        }
    }

    func encode() -> NSData {
        return NSKeyedArchiver.archivedDataWithRootObject(Encoding(self))
    }

    private class Encoding: NSObject, NSCoding {
        let a : NSString
        let b : NSString?

        init(_ foo: Foo) {
            a = foo.a
            b = foo.b
        }

        @objc required init?(coder aDecoder: NSCoder) {
            if let a = aDecoder.decodeObjectForKey("a") as? NSString {
                self.a = a
            } else {
                return nil
            }
            b = aDecoder.decodeObjectForKey("b") as? NSString
        }

        @objc func encodeWithCoder(aCoder: NSCoder) {
            aCoder.encodeObject(a, forKey: "a")
            aCoder.encodeObject(b, forKey: "b")
        }

    }
}

Then to save your array you can simply map .encode over your array:

let fooArray = [ Foo(a: "a", b: "b"), Foo(a: "c", b: nil) ]
let encoded = fooArray.map { $0.encode() }
NSUserDefaults.standardUserDefaults().setObject(encoded, forKey: "my-key")

and to get it back you can simply pass the NSData to the init:

let dataArray = NSUserDefaults.standardUserDefaults().objectForKey("my-key") as! [NSData]
let savedFoo = dataArray.map { Foo(data: $0)! }


回答2:

Following is the flow that I have follow

  1. Make your struct object
  2. Convert struct to Json object (I will give you file link to convert struct to JsonObject and jsonString -> https://github.com/vijayvir/SwiftApi/blob/master/StructureToJson/StructToJson.swift ) import this file in your project :-)
  3. Append that value to array
  4. If you want to save in user default I prefer you to make class object of array as follow ,(as you can access from any where )

  5. save your array in user default as follow

Struct to save

struct CardDetails : StructJSONSerializable {
var emailId :String

var accountNo : String

var expDate : String

var cvc : String

var isCurrrent : Bool = false      


init(card : Dictionary<String, Any>  )
{
    emailId =  card["emailId"]! as! String

    accountNo =  card["accountNo"]! as! String

    expDate  =  card["expDate"]! as! String

    cvc =  card["cvc"]! as! String

    isCurrrent = card["isCurrrent"]! as! Bool

}

 }

Save Array to User Default

  class var  cards : [AnyObject] {
    get
        {
            if (UserDefaults.standard.object(forKey:"cardss") != nil)
            {

            if let data = UserDefaults.standard.object(forKey: "cardss") as? Data
            {
                let unarc = NSKeyedUnarchiver(forReadingWith: data)

                let newBlog = unarc.decodeObject(forKey: "root")



                return newBlog as! [AnyObject]
            }
            else
            {
                return []
            }

          }
            else
            {
            return []
            }

        }

    set
    {
        //print(type(of: newValue) , newValue)

        let archiveData = NSKeyedArchiver.archivedData(withRootObject: newValue)

          let ud = UserDefaults.standard
          ud.set(  archiveData  ,forKey: "cardss")
          ud.synchronize()
    }

}

How to save Struct element to array Here convert you struct object to json Object

     let  firstCard = CardDetails(emailId: "asdf",
                                 accountNo: "dada",
                                 expDate: "nvn",
                                 cvc: "e464w",
                                 isCurrrent: true)

      CardsViewController.cards.append(firstCard.toJsonObect())

How to access object from array Here convert you json Object object to Struct

let sameCardIs = CardDetails(card:  CardsViewController.cards.last
as! Dictionary<String, Any>)



    print ("Element Function " , sameCardIs )

:-)



回答3:

WORKING IN SWIFT 3

Following a similar approach to ahruss, I ended up implementing in the following way:

struct MyObject {
    var foo : String
    var bar : String?
}

extension MyObject {
    init?(data: NSData) {
        if let coding = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as? Encoding {
            foo = coding.foo as String
            bar = coding.bar as String?
        } else {
            return nil
        }
    }

    func encode() -> NSData {
        return NSKeyedArchiver.archivedData(withRootObject: Encoding(self)) as NSData
    }

    private class Encoding: NSObject, NSCoding {
        let foo : NSString
        let bar : NSString?

        init(_ myObject: MyObject) {
            foo = myObject.foo
            bar = myObject.bar
        }

        @objc required init?(coder aDecoder: NSCoder) {
            if let foo = aDecoder.decodeObject(forKey: "foo") as? NSString {
                self.foo = foo
            } else {
                return nil
            }
            bar = aDecoder.decodeObject(forKey: "bar") as? NSString
        }

        @objc func encode(with aCoder: NSCoder) {
            aCoder.encode(foo, forKey: "foo")
            aCoder.encode(bar, forKey: "bar")
        }

    }
}

Oh, and to use it just type the following:

let myObject = MyObject(foo: "foo", bar: "bar")
UserDefaults.standard.set(myObject.encode(), forKey: "test")

To fetch it,

let objectData = UserDefaults.standard.object(forKey: "test") as? Data?
guard let storedObject = objectData else {
    return
}
let fetchedObject: MyObject = MyObject(data: storedObject as NSData)