PFUser currentUser saveInBackgroundWithBlock completes succeeded without even trying - parse-platform

I am trying to alter the logged in user. I make my changes as usual, and I call:
[[PFUser currentUser] saveInBackgroundWithBlock:^(BOOL succeeded, NSError *PF_NULLABLE_S error){
if(succeeded){
NSLog(#"Saved user successfully.");
}else{
NSLog(#"Unable to save user: %#", error);
}
}];
It saves successfully, but my changes are gone. Just before saving, my user objects has this key:
.meta.acceptsAllMessages = 1. The moment save completion block returns, that key is gone. `(meta is my generic JSON object at user, and other values in meta key are retained with no problem). My changes aren't also reflect to the server side too.
The first suspect was the beforeSave trigger, however there's absolutely nothing related to meta keys in my trigger, so that's not the case. Why would this happen?
UPDATE: There seems to be a problem deeper down. I was saving something else entirely, and ran into the same issue. I've enabled airplane mode, and I wanted to save my current user, and it called the completion handler immediately, with succeeded set to YES and error set to nil without an internet connection. I've double checked that I'm using saveInBackgroundWithBlock: and not saveEventually. Why does this happen?

Okay, I've found the solution.
I was adding an object to the array inside my user object, without assigning the property itself. In the latest instance, I was doing something like:
[[PFUser currentUser][#"myArray"] addObject:#"something"];
[[PFUser currentUser] saveInBackground...];
Because I was not assigning any object itself, [my assumption is that] Parse thought that my user object was not dirty, and it completed immediately without even trying to save. I've solved the problem like this:
NSMutableArray *array = [PFUser currentUser][#"myArray"];
[array addObject:#"something"];
[PFUser currentUser][#"myArray"] = array;
The last line is the key. I'm assigning to the "myArray" field of Parse object, which causes Parse to mark my user dirty. Then, when I save, because it is dirty, it actually saves my user to the server, and it works.

Related

Changing the ACL on a PFObject does not persist to the Parse Server

When I save a change to the ACL on a PFObject (in this case, making it publicly writeable), the completion block runs successfully but the change is not actually saved to the server. Re-fetching the object, or viewing it via Parse Dashboard, shows that the ACL change is not persisted. In fact, I have verified via server logging that Parse server never even receives a request.
// first fetch an object from the parse server, then...
print("before: \(object.acl?.hasPublicWriteAccess)") // "false"
object.acl?.hasPublicWriteAccess = true
object.saveInBackground { (success, error) in
// confirm that success is true and error is nil
print("after: \(object.acl?.hasPublicWriteAccess)") // "true" - object is updated client-side
// now, re-fetch the same object or check it in Parse Dashboard. It is not saved as publicly editable.
}
When changing an object's ACL, the object itself is not marked as 'dirty', so saving it does not result in a request to the server. You can verify this by checking the isDirty property on the object after changing the ACL.
This is in common with other PFObjects - a change to a pointer property does not mark the parent object as dirty. This is not normally encountered since it is natural to simply save the pointer object itself. Since there is no PFACL.save() function, we can instead re-set the acl property on the object to ensure that it is marked as dirty:
object.acl?.hasPublicWriteAccess = toggle.isOn
object.acl = object.acl
object.saveEventually()
Additional discussion of this can be found in this issue.

NSUserDefaults and serialized data

I am having a problem with our approach to data persistence in our app. It was decided to use NSUserDefaults with NSCoding compliant data model, which I disagree with due to the scale of our app.
The problem I'm seeing is when the data model changes, any attempt to deserialized results in a crash. The app must be uninstalled and reinstalled in order to re-serialize.
Scenario:
User installs app
User does stuff.
Developer decides that a property should be added to one of the serialized objects and pushes an update.
User installs update.
App goes 'kaboom'.
This is happening because the data had been serialized with a different model than it is now attempting to be deserialized as.
Example:
class Contact: NSCoding {
var name
var address
var userId
}
... // NSCoding compliance happens next. This object gets serialized.
Someone decides that Contact needs more stuff:
class Contact: NSCoding {
var name
var address
var userId
var phoneNumber
var emailAddress
}
Attempting to deserialize the Contact object, even though the NSCoding compliance for encoding and decoding has been updated to load and deserialize, causes
fatal error: unexpectedly found nil while unwrapping an Optional value
CoreDataManager.unarchiveUser
Worker.init
So, my question is, how could we possibly avoid this crash from occurring when running an updated version of the app that has a different schema??
You're crashing because,
You are attempting to decodeObject(forKey:) on a key that doesn't exist (because it didn't exist on the class when the object was encoded). This method returns nil.
You are using ! to force-unwrap the result of #1.
As a rule of thumb, if your Swift code crashes on a line that contains a !, there's about a 95% chance that the ! is the direct cause of the crash. If the error message mentions unwrapping an optional, it's a 100% chance.
The documentation for the decodeObject(forKey:) method explains that it may return nil. In your case this is guaranteed to happen if you're upgrading from a previous version of the class and you're decoding a key that you just added.
Your code needs to recognize that there might not be a value for the new properties. The simplest fix is replacing as! with as?. Then you'll get a nil value for the new property. For properties that are not optional, you can add something like ?? "default value" to the end.
You could also use the containsValue(forKey:) method to check if a value exists before trying to decode the key.

NSFileManager removeItemAtPath:error: does not respect POSIX permissions

I have some code that checks for the existence of a file and, if it is present, deletes it. The problem is, I am unable to get it to fail even if the file should not be writeable. My code looks like:
if([theManager fileExistsAtPath:savingAs isDirectory:&destIsDir])
{
BOOL itemRemoved=[theManager removeItemAtPath:savingAs error:&err];
if(!itemRemoved)
{
// why?
NSAlert *rebuildAlert=[NSAlert alertWithMessageText:#"Error removing item"
defaultButton:nil alternateButton:nil otherButton:nil
informativeTextWithFormat:#"%#",[err localizedDescription]];
[rebuildAlert runModal];
proceed=NO;
}
}
Even if I set ownership to root:wheel and mode to 000 (i.e. not readable, writeable or executable by anyone) the file is still silently deleted. The account I'm running this from is a user account with Admin privileges but even so, being able to kill files owned by root doesn't seem very safe. The only way to throw an error is to lock the file using chflags uchg filename. I have also implemented (as a stub for now) the fileManager:shouldRemoveItemAtPath: delegate method where I could check permissions if necessary. The problem is that returning NO from this method does not cause removeItemAtPath: to return an error. Re-checking with fileExistsAtPath: seems cumbersome. Finally, there doesn't seem to be a simple method of disambiguating which instance of NSFileManager is issuing the call to removeItemAtPath: in the delegate method. Typically these instances are transitory objects so their id's are not valid for any significant length of time. I could sub-class NSFileManager and add a tag instance variable but that seems like a sledgehammer to crack a nut.
In summary:
1) is it correct behaviour for removeItemAtPath to ignore files it does not own?
2) disallowing file deletion in the delegate method is not communicated back to the caller of removeItemAtPath
3) determining which invocation is calling the delegate method is hard
This is the correct behavior. Erasing a file does not require read access to the file, only the directory containing it. Think of a directory as a list of files, and erasing a file as simply removing it from that list, and it will all make sense.

Value stored during its initialization is never read

I am trying to create a Game so that I can change its data and save it back. I get two errors that are on the commented lines. Why am I getting these errors. I allocated the Game so I should have to release it correct. Here is my code to save my Game
Game *newGame = [[Game alloc] init];//error 1
newGame = [gamesArray objectAtIndex:gameNumber];
[newGame setTheShotArray:shotArray];
[gamesArray replaceObjectAtIndex:gameNumber withObject:newGame];
NSString *path = [self findGamesPath];
[NSKeyedArchiver archiveRootObject:gamesArray toFile:path];
[newGame release];//error 2
I get error 1 which says Value stored to 'newGame' during its initialization is never read.
The second error says Incorrect decrement of the reference count of an object that is not owned at this point by the caller.
What does this mean? And please don't tell me, you need to read up on memory management and just give me a link. Tell me how to fix the problem please.
Game *newGame = [[Game alloc] init];//error 1
You create a new instance and you own it since you’ve used +alloc.
newGame = [gamesArray objectAtIndex:gameNumber];
You obtain another instance from gamesArray and assign it to the same variable that was used in the previous line. This means that you’ve lost the reference to the previous object and, since you own the previous object, you’re responsible for releasing it. You don’t, so you’re leaking that object.
[newGame release];//error 2
At this point newGame points to the instance via from gamesArray. You do not own it since you haven’t obtained it via NARC, hence you should not release it.
NARC: a method whose name contains new, alloc, copy, or is retain.
Bottom line: you’re leaking the object that you’ve created via +alloc and you’re trying to release an object that you do not own.

How to check if self.navigationController is nil

In the following code, I am able to check with the debugger the values of self and childView.
[self.navigationController pushViewController:childView animated:YES];
However, I am not able to see the value of self.navigationController. How can I check if it is nil?
Just add the line:
UINavigationController* navController = self.navigationController;
And then set a breakpoint, or whatever else you want to do.
The reason is because navigationController is a property, so you can't just examine it; you would have to send the property's owner a getter message. In the debugger, that's pretty expensive, especially if it crashes or otherwise fails, plus it could always have side effects (e.g., faulting in a Core Data object, lazy-loading something, or changing some state in another ivar), so the debugger will not do this casually.
You must explicitly request the message using the Debugger Console:
po [self navigationController]
(I don't know whether it will let you use property-access syntax there. There's no difference between them, which is the root of the problem: A property access is an Objective-C message, which, as I described above, is why the debugger won't do one unless you specifically tell it to.)
You could always just do something like (self.navigationController == nil).

Resources