I've seen that NSUserDefaults do not allow objects other than Array, Dictionary, String, NSData, NSNumber, Bool, NSDate. But
Why it is not allowed to store other objects?
What if I need to store some properties as a single object? If I do it using dictionary, I've to write the keys somewhere which I've used to store the value. So, what could be the other alternatives.
What were the problems will arise if Apple allows the other objects also to store.
What if we use CoreData instead NSUserDefaults. I Know NSUserDefaults is globally available.
What is the best way to make a value available globally even after we relaunched the app if get terminated?
As suggested by #Hoa, previously I forgot mention NSCoding option also
What if we have many properties in a custom class, Do we need to encode and decode all of them using NSCoding methods?
You can save any object to a plist file as long as it is compliant with the NSCoding protocol.
You can use code similar to this:
+(id) readObjectFromFile:(NSString*) sFileName
{
return [NSKeyedUnarchiver unarchiveObjectWithFile:sFileName];
}
+(bool) saveObject:(id <NSCoding>) anObject ToFile:(NSString*) sFileName
{
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:anObject];
NSError * error;
[data writeToFile:sFileName options:NSDataWritingAtomic error:&error];
if (error)
{
NSLog(#"Save Cats Data error: %#", error.description);
return NO;
}
return YES;
}
Swift Version:
func readObjectFromFile(sFileName: String) -> AnyObject {
return NSKeyedUnarchiver.unarchiveObjectWithFile(sFileName)
}
func saveObject(anObject: AnyObject, ToFile sFileName: String) -> Bool {
var data: NSData = NSKeyedArchiver.archivedDataWithRootObject(anObject)
var error: NSError
data.writeToFile(sFileName, options: NSDataWritingAtomic, error: error)
if error != nil {
print("Save Cats Data error: \(error.description)")
return false
}
return true
}
To learn more about the NSCoding protocol you can read:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Protocols/NSCoding_Protocol/
The intention behind NSUserDefaults is to save contextual data relevant to the application state, for example saving the user's preferences, the state of the application when the user stopped using it (so you can return to that state when it fires up), login session data, etc..
Core Data is a more appropriate way to store persistent data, you can map your data model as you like and has a broader variety of options to save datatypes.
Whilst NSUserDefaults is available "everywhere", this should not be a turning point to decide if this is a better option for saving your data.
You can write a singleton class that serves as a data provider so that you can access your data in the same way you access the NSUserDefaults shared instance. You just need to keep in mind that this class or module should do only one thing, serve as an interface between your model and your implementation, so you do not store any objects in this class, just use it to pass over the requests to get and save data to CoreData.
This class could look something like this:
class CoreDataProvider {
static let sharedInstance = SUProvider()
let managedObjectContext : NSManagedObjectContext
let sortDescriptor: NSSortDescriptor
let fetchRequest: NSFetchRequest
private init(){
managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
fetchRequest = NSFetchRequest(entityName: "MyObject")
sortDescriptor = NSSortDescriptor (key:"objectAttribute", ascending:true)
self.fetchRequest.sortDescriptors = [self.sortDescriptor]
}
func getSavedObjects() -> [MyObject]? {
fetchRequest.sortDescriptors = [sortDescriptor]
do {
return try self.managedObjectContext.executeFetchRequest(fetchRequest) as? [MyObject]
} catch {
print("no records found")
}
}
}
Which you would use like this:
func getAllRecords() {
let records = CoreDataProvider.sharedInstance.getSavedObjects()
//- Do whatever you need to do
}
A temporary way to store object is to create json strings of your dictionaries or arrays. I have used it in some low scale apps. You can then store those string in NSUserDefault.., and when you need to use it, you can extract it, and you can use Object Mapper library that will automatically map the json data to object type.
example, you can create a function in your class extension that parses json data to your objects any time you need it.
I would suggest using the above method only for small scale apps. If you are going for high traffic/large scale app, you might wanna look into Core Data or even SQlite3..
Feel free to ask any question
Reference to Object Mapper library is here
Related
I want to state that I am fairly new to Swift and I was exploring CoreData concepts. I tried to test with a ToDo list app which on welcome screen shows user created task categories and upon clicking on any category the user will see all the tasks in that group. I tried to create a generic class something like ToDoListViewController<T: NSManagedObject> and to implement functionality available for both view controllers(CategoryViewController, TaskViewController). In that class I created a function loadItems which takes a predicate as an argument and populates the page with items from a fetch request. So the code roughly looks like this:
class ToDoListViewController<T: NSManagedObject>: UITableViewController {
func loadItems(predicate: NSPredicate? = nil) {
let request: NSFetchRequest<T> = T.fetchRequest()
if predicate != nil {
request.predicate = predicate!
}
do {
let items = try context.fetch(request)
// Do something
} catch {
print("Error fetching items from context \(error)")
}
}
}
The issue is that when I try to compile I get error:
Cannot assign value of type NSFetchRequest<NSFetchRequestResult> to type NSFetchRequest<T>
when assigning request variable. But if I force cast NSFetchRequest<NSFetchRequestResult> to NSFetchRequest<T> like this:
let request: NSFetchRequest<T> = T.fetchRequest() as! NSFetchRequest<T>
everything works fine. Since The NSManagedObject documentation clearly states that it conforms to NSFetchRequestResult protocol, why do I have to force cast NSFetchRequest<NSFetchRequestResult> to NSFetchRequest<T>?
You have to cast the type because the generic type of NSFetchRequest – which is constrained to NSFetchRequestResult – can also be NSDictionary or NSNumber or NSManagedObjectID.
Rather than making a generic type more generic I recommend to use a protocol with associated types like described here
Below is my method in which there is fetch I make on a Managed object Class Appointment. I need to use same function for other similar managed object Classes. How do I pass different "Class" as parameter every time as I need. And also use it to fetch as I have currently "Appointment" Class. I might need to use Generics may be. Dont know how though.
func getAppointmentArray(aPredicate : String , aModel : Any) -> [Any]
{
var apptArr = [Any]()
let fetchRequest = NSFetchRequest<Appointment>(entityName: "Appointment")
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = NSPredicate(format: aPredicate)
do{
let records = try managedObjectContext.fetch(fetchRequest)
if let records = records as? [NSManagedObject]{
if !records.isEmpty{
print("coreData apptmnt result : \(records)")
var appointment : Appointment?
for obj in records
{
}
}else{
print("No records found")
apptArr = []
}
}
}catch{
print("Error")
apptArr = []
}
return apptArr
}
The good folks at Objc.io provide a really good approach for this. First declare a protocol which inherits 'NSFetchRequestResult' protocol as below.
protocol Managed: class, NSFetchRequestResult {
static var entityName: String { get }
}
Now we can provide a very convenient protocol extension for our protocol 'Managed'.
We do the check 'Self: NSManagedObject' as we want the static method entity() of the NSManagedObject class to get the 'NSEntityDescription' describing the entity associated with our class. Particularly this way we get the entity name dynamically(and conveniently too) for all our ManagedObjects that conform to our protocol.
extension Managed where Self: NSManagedObject {
static var entityName: String { return entity().name! }
}
We now improve the protocol extension by providing a method which conveniently creates a fetch request and then calls a configuration block which might be used to configure the created fetch request by whoever calls it. At the end of this method we do a fetch using the created request.
extension Managed where Self: NSManagedObject {
static var entityName: String { return entity().name! }
//Continued
static func fetch(in context: NSManagedObjectContext, configurationBlock: (NSFetchRequest<Self>) -> ()) -> [Self] {
let request = NSFetchRequest<Self>(entityName: Self.entityName)
configurationBlock(request)
return try! context.fetch(request)
}
}
As you can see we do the following things here:
We make good use of protocols and protocol extensions for making our life easy.
We get the entity name without needing to write a method for each concrete managed object class that we might create. This is reusable for every managed object class that will conform to 'Managed'
The fetch method that we wrote makes use of the dynamic and convenient entityName.
The fetch method again makes use of Self which is implementation independent here. This way we make FetchRequests which are generic in itself.
We provide a convenient way to configure the request to whoever calls this method.
And at atlast we return result after fetching which is also dynamic [Self]
To see our protocol in action we can do this for your case:
class Appointment: NSManagedObject, Managed{
//properties for attributes
//etc...
//Will I get free implementation for entity name and a fetch method
//without writing extra code ?
//Yes why not
}
Testing our hard earned knowledge:
let aPredicate = "......
let context: NSManagedObjectContext.....
let appointments = Appointment.fetch(in: context) { fetchRequest in
//Configuration code like adding predicates and sort descriptors
fetchRequest.predicate = NSPredicate(format: aPredicate)
}
You can use the same pattern for any other ManagedObjects if they conform to the protocol. Eg a Doctor ManagedObject subclass conforming to our Managed protocol:
let aPredicate = "......
let context: NSManagedObjectContext.....
let doctors = Doctor.fetch(in: context) { fetchRequest in
//Configuration code like adding predicates and sort descriptors
fetchRequest.predicate = NSPredicate(format: aPredicate)
}
for the generic you can do something like this:
class FetchingDataHandler<T>{
func getAppointmentArray<T>(forClass : T, aPredicate : String , aModel : Any) -> [Any]
{
}
}
So far I can create my own Data Models in a swift file. Something like:
User.swift:
class User {
var name: String
var age: Int
init?(name: String, age: Int) {
self.name = name
self.age = age
}
}
When I create a Core Data model, ie. a UserData entity, (1) do I have to add the same number of attributes as in my own data model, so in this case two - the name and age?
Or (2) can it has just one attribute eg. name (and not age)?
My core data model:
UserData
name
age
The second problem I have is that when I start the fetch request I get a strange error in Xcode. This is how I start the fetchRequest (AppDelegate is set up like it is suggested in the documentation):
var users = [User]()
var managedObjectContext: NSManagedObjectContext!
...
func loadUserData() {
let dataRequest: NSFetchRequest<UserData> = UserData.fetchRequest()
do {
users = try managedObjectContext.fetch(dataRequest)
....
} catch {
// do something here
}
}
The error I get is "Cannot assign values of type '[UserData] to type [User].
What does this error mean? In the official documentation are some of the errors described, but not this particularly one.
If you are designing a user model in core data you don't have to write the class yourself. In fact by default Xcode will generate subclasses of NSManagedObject automatically once you create them in your project's, but you can also manually generate them if you would like to add additional functionality:
Then you can go to Editor and manually generate the classes
Doing this will give you User+CoreDataClass.swift and User+CoreDataProperties.swift. I see in your question you are asking about how the core data model compares to your 'own' model, but if you're using core data then that IS the model. The generated User class, which subclasses NSManagedObject, is all you need.
Then you might fetch the users like this:
let userFetch = NSFetchRequest(entityName: "User")
do {
users = try managedObjectContext.executeFetchRequest(userFetch) as! [User]
} catch {
fatalError("Failed to fetch users: \(error)")
}
You cannot use custom (simple) classes as Core Data model.
Classes used as Core Data model must be a subclass of NSManagedObject.
If your entity is named UserData you have to declare the array
var users = [UserData]()
The User class is useless.
I wonder if its possible to cache tableView JSON data?
In my VC I have this variable:
//Categories json data
var categories: JSON! = []
Then later inside a alamofire api call I get the json data and assign it like:
self.categories = JSON["catData"]
self.tableView.reloadData()
But is there any way to cache this data so I dont have to make a API call everytime?
You can create a singleton DataCache class where you can store all the data you want to cache. Prefer to store data inside a dictionary for specific key
class DataCache: NSObject {
static let sharedInstance = DataCache()
var cache = [String, AnyObject]()
}
Now in your api call, call this method
DataCache.sharedInstance.cache["TableViewNameKey"] = JSON["catData"]
self.categories = JSON["catData"] // Set this property for first time when you hit API
and in viewWillAppear(animated: Bool) method
if let lCategories = DataCache.sharedInstance.cache["TableViewNameKey"] {
self.categories = lCategories
}
Swiftlycache can be used for data caching. You can directly cache the data that complies with the codable protocol. It's very convenient. If you need it, you can try it
https://github.com/hlc0000/SwiftlyCache
I'm trying to create a bundle realm for my application. I thought it should be quite simple. Since I already have all needed records in Parse, I just have to:
create realm models and objects
load parse records to realm objects
save the realm
So, I created two realm models:
class RealmContainer : Object {
dynamic var rContainerId: String! //should be equal objectId from Parse
dynamic var rContainerName: String! //should be equal "name" field from Parse
...
var messages = List<RealmMessage>()
override static func primaryKey() -> String? {
return "rContainerId"
}
}
and
class RealmMessage : Object {
dynamic var rMessageId: String!
...
dynamic var rParentContainer: RealmContainer!
}
Getting results from Parse seems to be working. Also my realm objects are also good
var allUserContainers: [RealmContainer] = []
I was able to populate this array with values from Parse. But when I'm trying to save this realm, I'm getting a) nothing or b) error message
My code (this one'll get nothing):
let realm = try! Realm()
try! realm.write {
realm.add(self.allUserContainers[0])
print(Realm().path)
print(realm.path)
}
My code (this one'll get nothing too):
let realm = try! Realm()
try! realm.write {
realm.create(RealmContainer.self, value: self.allUserContainers[0], update: true)
print(Realm().path)
print(realm.path)
}
My code 3 (this will get me an error message "Terminating app due to uncaught exception 'RLMException', reason: 'Illegal recursive call of +[RLMSchema sharedSchema]. Note: Properties of Swift Object classes must not be prepopulated with queried results from a Realm"):
//from firstViewController, realm is a global variable
let realm = try! Realm()
//another swift module
try! realm.write {
realm.create(RealmContainer.self, value: self.allUserContainers[0], update: true)
print(Realm().path)
print(realm.path)
}
Obviously I don't properly understand how it should work, but I tried several swift/realm tutorials and they were actually straightforward. So, what did I do wrong?
Update
So, I updated my code to make it as much simple/readable as possible. I have a Dog class, and I am trying to get Dogs from Parse and put them to Realm.
AppDelegate.swift
let realm = try! Realm() //global
Dog.swift
class Dog : Object {
dynamic var name = ""
dynamic var age = 0
}
User.swift (getDogs and putDogs functions)
class User {
var newDogs:[Dog] = []
...
func getDogs() {
self.newDogs = []
let dogsQuery = PFQuery(className: "Dogs")
dogsQuery.limit = 100
dogsQuery.findObjectsInBackgroundWithBlock { (currentModes, error) -> Void in
if error == nil {
let tempModes:[PFObject] = currentModes as [PFObject]!
for var i = 0; i < tempModes.count; i++ {
let temp = Dog()
temp.name = currentModes![i]["dogName"] as! String
self.newDogs.append(temp)
}
} else {
print("something happened")
}
}
}
...
func putDogs() {
print(self.newDogs.count)
try! realm.write({ () -> Void in
for var i = 0; i < newDogs.count; i++ {
realm.add(newDogs[i])
}
})
try! realm.commitWrite() //doesn't change anything
}
error message still the same:
Terminating app due to uncaught exception 'RLMException', reason:
'Illegal recursive call of +[RLMSchema sharedSchema]. Note: Properties
of Swift Object classes must not be prepopulated with queried
results from a Realm
I believe I just have some global misunderstanding about how Realm is working because it is extremely simple configuration.
About your RealmSwift code : You have implemented it right.
When you declare a class of realm in swift, it's a subclass of Object class. Similarly for the parse it's subclass of PFObject.
You custom class have to have only one base class. So can't use functionalities of both the libraries Parse as well as Realm.
If you have to have use both Parse and Realm, you need to declare two different classes like RealmMessage and ParseMessage.
Retrieve data for ParseMessage from parse and copy properties to RealmMessage.
Why you want to use both Parse and Realm ?
parse also provides local data store by just writing Parse.enableLocalDatastore() in appDelegate before
Parse.setApplicationId("key",
clientKey: "key")
Your Realm Swift code seems fine. Realm is very flexible when creating objects using (_:value:update:), in being able to take in any type of object that supports subscripts. So you could even directly insert PFObject if the schemas matched.
The problem seems to be how you're populating the allUserContainers array. As the error says, you cannot fetch a Realm Object from Realm and then try and re-add it that way. If you're trying to update an object already in Realm, as long as the primary key properties match, you don't need to supply the whole object back again.
Can you please revisit the logic of how your allUserContainers variable is being populated, and if you can't fix it, post the code into your question?
Sidenote: You probably don't need to define your Realm properties as implicitly unwrapped as well. This is the recommended pattern:
class RealmContainer : Object {
dynamic var rContainerId = ""
dynamic var rContainerName = ""
}
Actually I found what was wrong: as i suspected it was a stupid mistake, some property in my class was UIImage type, and Realm just can't work with this type (even if I'm not trying to put objects of this class into realm). So, my bad. I am slightly embarassed by it, but will not delete my question because error messages from Realm were not very understandable in this case