Swift: Saving NSDictionary to a file, because NSUserDefaults is to slow - ios

I'm using the NSUserDefaults for saving more the 10 big NSDictionarys.
Because the performace is slow, I have the idea to saving the Data into one [or 10 files] instead of using the NSUserDefaults. I need the data just by the starting of the app, so the idea is, saving the data to a file and saving into NSUserDefaults only data I need fast.
I found this solution on stack overflow:
Read and write data from text file
let dirs : [String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true) as? [String]
if ((dirs) != nil) {
let dir = dirs![0]; //documents directory
let path = dir.stringByAppendingPathComponent(file);
let text = "some text"
//writing
text.writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: nil);
//reading
let text2 = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: nil)
}
That works for strings. But when I try to use
NameOfMyDict.writeToFile
I get an error, because Dicts don't have the writeToFile function.
How can I save Dicts to a file?

NSDictionary provides a function to save to a file. It is part of Foundation framework:
writeToFile(_ path: String,
atomically flag: Bool) -> Bool
If you use a swift Dictionary<>, you may need to cast as a NSDictionary. All elements of the dictionary should implement NSCoding protocol.

You could serialize it to JSON and save the JSON string to a file. See NSJSONSerialization.

Related

how to unarchive plists in bundle using a path in Swift 5

Short version:
How to access an archived file in the bundle for unarchiving with unarchivedObject(ofClass:from:). The core issue is going from a path (string or URL) to Data which is required by the method.
Long version:
I have many plists containing SCNNode hierarchies stored as plists in bundle resources.
I was able to access them in Swift 3 with:
let fileName = MoleName.DNA_ideal_comps // retrieves String
let filePath = getFilePath(fileName)
guard let components = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? [SCNNode] , components.count >= 4
else { // no value for this key, or not enough nodes
print("Couldn't find molecule (DNA_ideal_comps)")
return
} // [ atoms_A, bonds_A, atoms_B, bonds_B ]
scaleNode_2 = components[0] // DNA A
fixedNode_2 = components[1] // bonds
scaleNode_3 = components[2] // DNA B
fixedNode_3 = components[3] // bonds
// where filePath is:
func getFilePath(_ fileName: String) -> String {
if let path = Bundle.main.path(forResource: fileName, ofType: "plist") {
return path
}
return "path could not be found"
}
Here fileName is the name of the plist to retrieve, "DNA_ideal_comps" in this example. I created these independently of the active program due to the sheer volume of data; some of these plists contain over 30,000 items and there are about 90 total.
The above attempt to unarchive is deprecated and I've struggled to replace it. My best try so far:
guard let components = NSKeyedUnarchiver.unarchivedObject(ofClass: SCNNode.self, from: moleData), components.count >= 4
But this fails with Cannot convert value of type 'String' to expected argument type 'Data'. Unlike the method in Swift 3, there appears no way here to use the path of the object to retrieve it.
Is there a way to access these plists using this method? Is there another method more appropriate?
The core issue is going from a path (string or URL) to Data which is required by the method
Well, that's trivial enough; just call the Data initializer that reads from a file on disk. You would then decode the Data by calling NSKeyedUnarchiver.unarchivedObject(ofClass: SCNNode.self, from: theData).
https://developer.apple.com/documentation/foundation/data/3126626-init
https://developer.apple.com/documentation/foundation/nskeyedunarchiver/2983380-unarchivedobject
However, I'm confused as to why you're using NSKeyedUnarchiver to read a plist file. A plist file would be read using a PropertyListSerialization object.
https://developer.apple.com/documentation/foundation/propertylistserialization

How to read-write json file without disturbing the sequences of its key-value pairs in iOS?

I am serializing a json file whose key-value pairs should not be shuffled when writing to a new file after editing. Even if I do not edit it still goes shuffles the pairs.
I just need the same sequence of the key-value pairs in the new file(written file) as it was in the previous file that I read.
Here is the sample json
[
{
"StudentName":"Amit",
"StudentId":"1"
},
{
"StudentName":"Lalit",
"StudentId":"2"
},
{
"StudentName":"Ram",
"StudentId":"3"
},
{
"StudentName":"Shyam",
"StudentId":"4"
}
]
What I am getting after writing the jsonObject to a new file is:
[
{
"StudentName":"Lalit",
"StudentId":"2"
},
{
"StudentName":"Ram",
"StudentId":"3"
},
{
"StudentName":"Shyam",
"StudentId":"4"
},
{
"StudentName":"Amit",
"StudentId":"1"
}
]
I have two code files, since I am working on command line tool.
1)main.swift
import Foundation
var behavioralJsonObject : AnyObject
var newBehavioralDataObject: NSData = NSData()
let fileManager = NSFileManager.defaultManager()
var path = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first
var stringPath: String = (path?.path)!
var behavioralFilePath: String = stringPath.stringByAppendingString("/BehavioralFile.json")
var newBehavioralFilePath : String = stringPath.stringByAppendingString("/BehavioralFileNew.json")
behavioralJsonObject = MyJsonParser().jsonObject(withFilePath: behavioralFilePath)
print(behavioralJsonObject.description)
newBehavioralDataObject = try NSJSONSerialization.dataWithJSONObject(behavioralJsonObject, options: .PrettyPrinted)
newBehavioralDataObject.writeToFile(newBehavioralFilePath, atomically: true)
2) MyJsonParser.swift
import Foundation
class MyJsonParser: NSObject {
func jsonObject(withFilePath path:String)-> AnyObject{
let dataObject = NSData.init(contentsOfFile: path)
var jsonObject: AnyObject = []
do{
jsonObject = try NSJSONSerialization.JSONObjectWithData(dataObject!, options: .AllowFragments)
}
catch{
print("Serialization error : \(error)")
}
return jsonObject;
}
}
Has anybody already faced this problem or has a solution for this?
Please re-check your code. If your code is correct, you are parsing a JSON array, converting it back to data, and don't write the array elements in the correct order. If that is what really happens, then the only thing you can do is send a bug report to Apple and let them fix it. Array elements have a defined order; if that is changed by the act of reading and writing the array then there is a serious bug somewhere.
It's different with dictionaries. The key/value pairs in a dictionary are unordered. You can't find out in which order they were parsed, and the order in which they are written is undefined. So if your first array element was written as
{
"StudentId":"1"
"StudentName":"Amit",
},
that would be totally to be expected and completely correct. Changing the order of array elements however is a bug. Note that if you actually read a dictionary and printed out the array of values of the dictionary, that would again be in undefined order.

filemanager.createFileAtPath works not correctly

I try to create a PLIST-File with the NSFileManager and the method createFileAtPath. In the end, the file was created, it has the size of 0 Bytes, and i even can see the specific PLIST-Icon for that file in the Finder.
But when i want to open it (for example with Xcode) it says:The data couldn't be read because it isn't in the correct format.
I want to write to this file but when its not in the correct format i can't do this.
There is something wrong with the File-creation but i don't know what it is.
I hope you can help me with this.
Here is my code:
pListPath = NSURL(fileURLWithPath: reportsPath.path!).URLByAppendingPathComponent("myReports.plist", isDirectory: false)
let data: NSData = NSData()
var isDir: ObjCBool = false
if fileManager.fileExistsAtPath(pListPath.path!, isDirectory: &isDir)
{
print("File already exits")
}
else
{
let success = fileManager.createFileAtPath(pListPath.path!, contents: data, attributes: nil)
print("Was file created?: \(success)")
print("plistPath: \(pListPath)")
}
reports.path = .../UserDir/.../Documents/Reports
Any help is highly appreciated.
filemanager.createFileAtPath works absolutely correctly,
but you're creating an empty file by writing an empty NSData object to disk.
NSData objects are not implicitly serialized to a property list.
Either use the NSPropertyListSerialization class or – simpler - write an empty dictionary to disk.
let dictionary = NSDictionary()
let success = dictionary.writeToURL(pListPath, atomically: true)
print("Was file created?: \(success)")
print("plistPath: \(pListPath)")
PS: you don't need to create an URL from an URL
pListPath = reportsPath.URLByAppendingPathComponent("myReports.plist", isDirectory: false)
but I recommend to use more descriptive variable names to distinguish String paths and NSURL e.g. pListURL and reportsURL

Get json results in NSuserDefault and display result for annotation in MapKit in Swift

I want to get result from json and save this result in NSUserDefault, after I want to use the json array saved in NSUserDefault to add multiple annotation on the MapKit.
Currently to get json result, I use this : ( Swift 2.x )
let defaults = NSUserDefaults.standardUserDefaults()
//Get user content//
let url = NSURL(string: "http://www.example.com/folder/coordonate.php")
let request = NSMutableURLRequest(URL: url!)
// modify the request as necessary, if necessary
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if data == nil {
print("request failed \(error)")
return
}
do {
if let json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary {
print(json)
// print : {tab = "[london,paris]";}
var test = json["tab"]
defaults.setObject(test, forKey: "annotation") as? NSArray
}
} catch {
print(error)
}
}
task.resume()
NSUserDefaults.standardUserDefaults().synchronize()
And to get the NSUserdefault in another view, I try this code :
let defaults = NSUserDefaults.standardUserDefaults()
let test = NSUserDefaults.standardUserDefaults().objectForKey("annotation") as! NSArray
map.addAnnotations(test as! [MKAnnotation])
print("We saved this data: \(test)")
//No print, error before
But I have an error when I try with this method.
Could not cast value of type '__NSCFString' (0x1971a3958) to 'NSArray' (0x1971a4308).
If the "tab" property JSON actually being returned is:
[london, paris]
Then the property is not an array. What you're looking for would be:
['london', 'paris']
But additionally I can tell you that even if the "tab" property is a properly formatted JSON array your code will fail when it attempts to convert it to [MKAnnotation] anyway. That's because iOS's JSON library does not know how to convert a generic NSArray into it's typed equivalent. The NSJSONSerialization documentation indicates all the types that JSON will convert to. Best case scenario the "tab" property is an array of items with the same structure as MKAnnotation and are being converted to an array of dictionaries that you will have to convert to MKAnnotation yourself. But the JSON provided currently evaluates as a string. With my suggested change it will instead evaluate to an array of strings- still not sufficient to create an MKAnnotation from.
Your JSON data has to be one of the valid NSUserDefault types (String, NSArray, NSDictionary, NSData).
The quickest fix would be to store the JSON in NSUserDefaults as the NSData that comes back from the server. Then deserialize the NSData on the reading of NSUserDefaults.
If storing a subset of the JSON from the server is really needed, I would use Dictionaries, Arrays and validate the data before storing it. As a general Swift rule, I avoid using NSDictionary and NSArray to ensure the types are what I expect and won't cause a runtime crash.
change your php code to
$results = Array("tab" => ["london","paris"]);
instead of
$results = Array("tab" => "[london,paris]");
p.s
if using php earlier than 5.5 (or 5.4 not quite remember) then use:
$results = Array("tab" => Array("london","paris"));
==========================
you are casting the setObject func to NSArray and not the test object
defaults.setObject(test, forKey: "annotation") as? NSArray
should be
if let arrayTest = test as? NSArray{
defaults.setObject(arrayTest, forKey: "annotation")
}

Swift: How to save data of different types in a file

I want to save data including string, number, date and coordinate in a file, and then transmit the file to a server. How to do this using swift?
And I'd like to process these data from the server in the future. What type of file is better to save them?
If i am getting this right you can use NSData
First you have to create a dictionary like this
var dictionary = [String:AnyObject]()
dictionary["age"] = 13
dictionary["name"] = "Mike"
Then you have to transform this dictionary into NSData using nsjsonserialization
if let data = NSJSONSerialization.dataWithJSONObject(dictionary, options:NSJSONWritingOptions(0), error:nil) as NSData? {
request.HTTPBody = data
}
But this is always depend on what the server is able to understand
Hope i helped. Sorry for my english

Resources