New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sub-objects get reinitialized after parent objects during transfer of data from CloudKit #54
Comments
I think I have to debug this issue to see what's really going on. It should be in the code where the object structure is created based on a dictionary. I think the moment it's at a sub object and want to parse the sub dictionary to that it creates a new object without checking if there is already a default object set. If that's the case, then I think I should be able to test if there is already an object and then use that instead of a new one. I will have a look at it this weekend. |
Yes, that is exactly what is happening. In my example code, if I set a breakpoint in class B's init method, I first see it called from class A's initializer, which was called from swiftClassFromString when the parent class A was initialized. If I proceed, class B's init breakpoint is hit again, except this time it is being called directly from swiftClassFromString. This code always creates a new instance of the object:
After always being called by:
The fix will need to occur further upstream, where it is known that a sub-object is being restored and to check for it having already been initialized. Here's the call stack I see at the point of the sub-object being reinitialized:
|
Did you get a chance to look at this over the weekend? If so, do you still feel this will be a straightforward fix? |
I was working on a version this weekend, but it kept crashing. I'm now thinking of an alternate way to fix this. I will let you know tomorow. |
Okay great, thanks for the update. I appreciate the continued effort to resolve this. It seems like we're very close to a working solution! |
FYI, I made a temporary modification to EVReflection that makes this work as expected for me. Please let me know if there are problems with my approach. I modified the setPropertiesFromDictionary and dictToObject methods in EVReflection.swift:
The only issue these changes introduced, and I'm not sure it really is an issue, is having to implement an override of the NSObject's valueForUndefinedKey method. I had already implemented an override of the setValue:forUndefinedKey method for those properties, so it made sense that this would be needed as well. Hope this helps! |
I have continued to debug issues in my app and have run across another issue that is similar to this one. It looks to me as though a class instance is replaced with another instance whenever its state is saved. This occurs in the EVCloudData::upsertObject method, the pertinent portion of which I have copied below:
In my case, I have a connection that retrieves all user-created documents (but not their content, which is stored separately) in my app. I also have a separate Optional property in a view model that references the currently opened user-created document. I assign the selected document to this property when the user opens one of their documents. The problem is, that referenced instance doesn't get updated after the first change when the above code replaces the referenced instance with another one. Is there no practical way to update the existing object's properties instead of always replacing it with a new instance? I can create a separate issue for this if needed. Thanks. |
For the original issue: I did make some progress, but still 12 tests are failing. I will keep on working on it. I want to fix the issue. As for your data object being replaced: I think it's a design issue. The connect method is meant to mirror a CloudKit data collection into an array of objects. These objects where meant to be data objects without additional functionality. In your case you are adding functionality to them. But it would not be a big issue for me to just update the original object. I already have reflection functions that will do that. So instead of the remove and insert I could call a EVReflection function to update all properties. Is that what you meant? This is a separate issue. I will have a look at it after i finish this issue. |
It's sometimes so easy to overlook a stupid mistake... |
I had to find out if this solved your issue. I could not wait. So I added a unit test for your scenario and it seems to be working. I just pushed a new version of EVReflection (2.16.1) to GitHub. |
And for your other issue it could be a quick fix by replacing the 2 lines at row 450 in EVCloudData.swift. Change this:
to:
I will have a look at this tomorrow to see if it breaks something. |
Thank you so much for diving into this and hopefully resolving it. I will test the update and your suggested fix for the object updating and get back to you later today. |
I'm starting to test it now. The first difference I see is that with your changes for the sub-objects, properties that were handled by my temporary fix now require special handling in the setValue:forUndefinedKey method. I understand that my temporary fix may have broken other things that I wasn't testing or using. Its no big deal for me to add the handling of those properties to setValue:forUndefinedKey. But I wanted to let you know about it in case you wanted to look into it. Here's one of the warnings I now see. Note that the property being referred to isn't any of the unsupported types. All the CloudBool sub-object is adding to the AudioOptions class is a public Int property called "state".
Here's an excerpt of the AudioOptions class that declares the CloudBool instance property:
And here is the current CloudBool class definition:
|
This issue may be caused by multiple levels of sub-objects. The structure of this record has been in place for a while now, before I created this ticket, but didn't cause the setValue:forUndefinedKey to be called for the CloudBool state properties Here is the CKRecord structure for the class this is happening in. Record Type: Fields: Out of the above fields, it is giving the undefinedKey warning for: audio__muteAllAudio__state |
Good news on the EVCloudData upsertObject change! It seems to be working great for me so far. Hopefully that change won't break any of your other tests. Also, after reverting your sub-object changes and putting my temporary fixes back in, that area is also working as expected now. Hopefully it won't be difficult to track down the new issues I ran into, or I can make the needed setValue:forUndefinedKey additions. |
So I have a question regarding your recent commit that included the EVCloudData upsertObject change. I've only viewed the changes here on GitHub, but it looks like you added this code:
after this code:
instead of replacing the removeAtIndex/insert combo with the setPropertiesFromDictionary call. Is that correct? Your original suggestion, and what I implemented and tested, was replacing the combo with the setPropertiesFromDicitionary call. Did you find that it needed to be done this way instead? |
Oops sorry, last minute change that should not have been committed. I corrected the code... |
I have found the root cause of some of my problems with property values not being their expected values when retrieving data saved in CloudKit using EVCloudKitDao. I'm not sure if this can be easily or at all fixed though. Please review my observations on this and provide your thoughts.
Consider a class structure like the following:
If you set breakpoints in Class A's assignment of 9 to the b.intProp property and the EVLog("intProp set to...") statements, you will see the following behavior in action.
If an instance of class A is saved to CloudKit using EVCloudKitDao, then retrieved, the value of the "b" variable in the class A instance will be 5, not 9 as expected. This is due to a second instance of class B being directly instantiated by EVCloudKitDao and assigned to the class A instance's "b" property after it has already been instantiated from an instance of Class A. This also means sub-objects are instantiated multiple times, one per level of however deep they are nested in the root EVCloudKitDataObject being saved.
Is it possible to assign stored values to the already-instantiated sub-objects instead of instantiating new instances? If not, I believe this means I will have to remove any objects that I was expecting EVCloudKitDao to ignore using the propertyMapping override.
The text was updated successfully, but these errors were encountered: