I need to edit the data stored in a persistent object.
I have some idea about this in Android -- there we use Editor to edit data in shared preference.
Get the object stored in the PersistentObject, cast that object into whatever type it should be, make your changes and then commit the results back to the PersistentObject so your code would look something like this:
PersistentObject perstObj = PersistentStore.getPersistentObject(<UNIQUE KEY>);
MyClass myObj = (MyClass) perstObj.getContents();
//Make whatever changes you need to myObj
perstObj.setContents(myObj);
perstObj.commit();
Related
Say I have 2 NSManagedObjects in CoreData.
class House: NSManagedObject {}
class Location: NSManagedObject {}
I also have data model structs like this:
struct HouseModel {
var objectID: NSManagedObjectID
...
}
sruct LocationModel {
var objectID: NSManagedObjectID
...
}
For each loaded managedObject I basically use its attributes to initialize a new model struct to use for the UI and stuff (mainly collection views)
I have to have the NSManagedObjectID attribute in the structs in order to be able to make changes to the managedObject that struct belongs to. (I learned that I should use the mainViewContext only for reading while using something like persistentContainer.performBackgroundTask for writing. Thus, I need the NSManagedObjectID to load the objects on a background queue)
That's working but there is a problem with this approach:
I can't initialize one of these data models without a managed object. That's annoying when I want to create dummy data for UI testing or unit testing.
I know one solution: Create a Dummy managedObject with exactly one instance and use its objectID for stuff like that. But I don't really like this. Is there a better / more convenient way?
I mean, I would love to entirely remove the objectID attribute to keep CoreData separate from these model structs. But I don't see a way to do this. I need the connection.
For passing NSManagedObjects to a detail view for editing, it is often useful to do that on a new main queue managed object context, which simplifies your UI access and allows you to throw away the context if the user cancels changes.
But that's not what you asked.
Your problem is that you want to identify a managed object, but not use NSManagedObjectID. For this, you can use a URL property instead. NSManagedObjectID has a uriRepresentation() that returns a URL, and NSPersistentStoreCoordinator can convert a URL back into a managed object ID using managedObjectID(forURIRepresentation:). So you can store any old URL in the struct for testing purposes, and still be securely referring to managed objects in your app logic.
So basically what I'm trying to do is list all properties of an object in a tableview in a key = value format.
But I want this to be independent of future changes to the object, so that if a new property is added later, no changes will be needed to my code.
So far I can access the properties via Mirror, but I run into problems when I'm trying to access the properties via value(forKey:), even though the class is inheriting NSObject it crashes with:
this class is not key value coding-compliant for the key
Some properties work while others don't, which I'm guessing is down to some of them being private and others #objc variables?
So is there any way to pre-validate that a key (property) can be accessed via value(forKey:) - so it doesn't end in a crash, so I if nothing else can show the values of the accessable properties?
Better yet, is there another way of accessing all properties and values of a given object in a dynamic way? (handling later additions of properties)
Code snippet:
let properties = Mirror(reflecting: currentUser).children.compactMap { $0.label }
if properties.count > 0 {
for property in properties {
if let test = currentUser[property] {
newData.append( (name: property, value: currentUser.value( forKey: property ).debugDescription) )
}
}
}
What you might be able to do is make your object conform to Encodable. That way, you could simply convert it into JSON (or some other format), then make your table view dynamically pull out all the keys and values from the JSON.
As long as any future changes to the object don’t break Encodable compliance, the table view will still be able to parse whatever JSON it receives.
When I delete an NSMangedObject from the database, what happens to local variables who were assigned to it?
For example, I have a simple NSManagedObject:
class MyManagedObject: NSManagedObject {
#NSManaged var name: String
}
And then in my ViewController, I pull it out of the database, and assign it locally:
class ViewController: UIViewController {
var myManagedObject: MyManagedObject!
}
Then I delete it from the database.
If print the object name I get the following in the console
print("myManagedObject.name = \(myManagedObject.name)")
//prints: "myManagedObject.name = "
As if the object isn't there? But if I turn the variable into an optional and check it for nil, I am told it's not nil.
I'm not quite sure how to reconcile this in my mind. There seems to be something pointing to the local variable, but its properties are gone.
If I have many disparate UI objects that rely on that object for its properties, I can't assume that there is some local deep copy of it in memory?
Here is more complete code:
In viewDidLoad I create the new object, save the context, fetch the object, then assign it locally.
class ViewController: UIViewController {
var myManagedObject: MyManagedObject!
override func viewDidLoad() {
super.viewDidLoad()
//1 Create the new object
let newObject = NSEntityDescription.insertNewObject(forEntityName: "MyManagedObject", into: coreDataManager.mainContext) as! MyManagedObject
newObject.name = "My First Managed Object"
//2 Save it into the context
do {
try coreDataManager.mainContext.save()
} catch {
//handle error
}
//3 Fetch it from the database
let request = NSFetchRequest<MyManagedObject>(entityName: "MyManagedObject")
do {
let saved = try coreDataManager.mainContext.fetch(request)
//4 Store it in a local variable
self.myManagedObject = saved.first
} catch {
//handle errors
}
}
}
At this point if I print the local variable's name property, I get the correct response:
print("The object's name is: \(myManagedObject.name)")
//prints: The object's name is: My First Managed Object
So, now I delete it from the database:
if let storedObject = myManagedObject {
coreDataManager.mainContext.delete(storedObject)
do {
try coreDataManager.mainContext.save()
} catch {
//handle error
}
}
But now, when I print I get the strangest output:
print("myManagedObject.name = \(myManagedObject.name)")
//prints: "myManagedObject.name = "
This is totally not the way I'm expecting memory to work. If I create a instance of a class Foo, and then pass that instance around to different objects, it's the same instance. It only goes away once no one is pointing to it.
In this case--- what is the variable, myManagedObject? It's not nil. And what is the string, name? Is it an empty string? Or is it some other weird meta-type?
The main thing what you are probably looking here for is the core data context. The context is a connection between your memory and the actual database.
Whenever you fetch the data you fetch it through context. These are managed objects which can be modified or even deleted. Still none of these really happen on the database until you save the context.
When you delete an object it is marked for deletion but it is not deleted from the memory, it must not be since if nothing else it will still be used by the context to actually delete the object from the database itself.
What happens to the managed object once you call to delete it is pretty much irrelevant, even if documented it may change as it is a part of the framework. So it is your responsibility to check these cases and to refetch the objects once needed. So you must ensure your application has a proper architecture and uses core data responsibly.
There are very many ways on how you use your database and more or less any of them has a unique way of using it optimally. You need to be more specific on what you are doing and where do you see potential issues so we can get you on the right track.
To give you an example consider data synchronization from remote server. Here you expect that data can be synchronized at any time no matter what user is doing or what part of the application he is.
For this I suggest you have a single context which operates on a separate thread. All the managed objects should be wrapped and its properties copied once retrieved from database. On most entities you would have something like:
MyEntity.findAll { items in
...the fetch happens on context thread and returns to main, items are wrappers
}
MyEntity.find(id: idString, { item in
...the fetch happens on context thread and returns to main, items are wrappers
})()
Then since you do not have any access to the managed object directly you need some kind of method to copy the data to the managed object like:
myEntityInstance.commit() // Copies all the data to core data object. The operation is done on a context thread. A callback is usually not needed
And then to save the database
MyEntity.saveDatabse {
... save happens on the context thread and callback is called on main thread
}
Now the smart part is that saveDatabse method will report to a delegate that changes have been made. A delegate is usually the current view controller so it makes sense to have a superclass like DataBaseViewController which on view did appear assigns itself as a delegate MyEntity.delegate = self, on view did load calls some method reloadData and in the databaseDidChange delegate method calls reloadData and same in viewWillAppear.
Now each of your view controllers that are subclass of DataBaseViewController will override the reloadData and in that method you will fetch the data from the database again. Either you are fetching all items or a single one. So for those singe ones you need to save the id of the object and fetch it again by that id. If the returned object is nil then item was deleted so you catch the issue you seem to be mentioning.
All of these things are oversimplified but I hope you get a basic idea about core data and how to use it. It is not easy, it never was and it most likely never will be. It is designed for speed to be able to access the data even from a very large database in shortest time possible. The result is that it might not be very safe.
I have a form that I'm creating and to simplify things, I'm trying to create a form field mapper to an object. As such, I create the following dictionary:
self.fieldPropertyMapper = #{
#(CompanyFieldName):self.company,
#(CompanyFieldDescription):self.company.description,
#(CompanyFieldWebsite):self.company.website,
#(CompanyFieldTwitter):self.company.twitter,
#(CompanyFieldAddress):self.company.address,
#(CompanyFieldAddress2):self.company.address2,
#(CompanyFieldCity):self.company.city,
#(CompanyFieldState):self.company.state,
#(CompanyFieldZipcode):self.company.zipcode,
#(CompanyFieldPhone):self.company.phone
};
The keys here are members of the CompanyFieldType enum.
My goal here is to later in my form to assign a value to the returned pointer. Here's what I mean: when a text field in one of my forms stops editing, I'm looking to set the value. Here's what I'd like to accomplish:
- (void)textFieldDidEndEditing:(UITextField *)textField
{
CompanyFieldType fieldType = [self fieldTypeForTag:textField.tag];
// Set the value of the respective company property
// In theory it would be something like:
// self.fieldPropertyMapper[#(fieldType)] = textField.text;
}
I'm assuming there's a way to assign by reference but I'm forgetting how to do this. (Is it using the & symbol or **?) I don't remember. Help appreciated! If I'm messing up my terminology, feel free to let me know.
You can't do exactly what you want to do. That is to say, there is no pointer magic that will do what you want.
You can get essentially the same effect, though, with key-value coding. Instead of storing the result of accessing the property (e.g. self.company.website), instead you want to just store the key path to the value you're interested in as a string — e.g. #"company.website". Then you can do like so:
[self setValue:textField.text forKey:self.fieldPropertyMapper[textField.tag]];
Using NSMapTable initialized with NSPointerFunctionsStrongMemory for the keys and NSPointerFunctionsOpaqueMemory for the values. Then you could store the addresses of your iVars backing your properties as the values in the table.
[self.mapTable setObject:&_company forKey:#(CompanyFieldName)];
Haven't tested this but this should get you started.
if I put a custom class in session, then in an action method I get an instance of that class from session, and populate some fields, I noticed that when a different controller gets that class from session, those fields are populated. Even though after the first call didn't save the updated class back in session.
is this typical behavior for session objects?
I thought I had to use keyword 'static' on the class in session for this to happen
thanks
As your class is a reference type this is the normal behavior. Here's what's happening in memory:
You instantiate an object and put it in session
var someObj = new SomeObject();
Session["someObj"] = someObj;
At this stage a new object is created on the heap and Session["someObj"] is pointing to this object.
You retrieve the object from session in controller A and modify some property but you do not call Save:
var someObj = (SomeObject)Session["someObj"];
someObj.SomeProp = "new value";
Now someObj is pointing to this same object you created earlier on the heap. As someObj is only a reference you are actually modifying the original object in memory.
You retrieve the object from session in Controller B:
var someObj = (SomeObject)Session["someObj"];
Now someObj points to the same memory location which has been modified.
is this typical behavior for session objects?
Well yes, but, it is typical of .NET objects in general. You took a reference, so you were changing the original object still pointed to by the session.
Since your session is in memory this is the expected behavior. If you store the session in an external store then you have to save the objects back to the session to get that same behvior. I would try to avoid such development becuase when you do change the store the behavior is totally different.