I’m experiencing a very weird issue, which after several months of testing just happened to pop up as I was about to submit the app.
I have the folliwing method which takes some JSON data and converts it to a dictionary:
NSError *e;
NSMutableDictionary *result= [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers error:&e];
if (e != nil) return nil;
For the past few months, this method has been used non stop, with absolutely no problems. But just today, it just stopped working. It now always results in an error (with no description; just a non-nill error).
It turns out, all I had to do to fix the problem was set NSError *e = nil;. I thought it was just good practice to do that, not absolutely critical. This scares me. I wonder how many times else I’m doing this in my code. Can anyone explain what could possibly be going on?
Also, I am using ARC, which I guess makes it even stranger that this is happening.
Your code is wrong, and so is your fix. What you need to do is say
If
resultis anything besidesnil, then you’re not allowed to assume anything about the contents ofe.The basic reason here is that APIs that have
NSError**return values are not required to put anything in that spot unless the API returned an error. Typically this means that in the non-error case they don’t modify the value at all, so whatever you had in yourevariable before is what you have after. If your code is compiled without ARC, yourevariable will have garbage from the stack in it. Under ARC it will be initialized tonil, but I’m guessing you’re not under ARC for reasons I will get to.However, it’s more complicated than this. Even if the method does not return an error, it may still have modified the
NSError**value anyway. The simple example is if this method calls another method, passing the sameNSError**into that method, and then recovers from the error and returns a success value instead. Yet the second method may have populated yourNSError*variable with an error that is no longer valid.Now, the reason why I think your code isn’t ARC is because, to the best of my knowledge, all of the Cocoa APIs these days take pains to not modify the
NSError**value unless an error occurred. This is in line with the new guidelines established a year or two ago (it was either at the beginning of 2011 or 2012, I forget which) that say that methods that have anNSError**parameter should only modify it in the error case. This is intended to allow code that saysNSError *e = nil; [foo callAPIWithError:e]; if (e) ...to work, even though that’s not actually following the rules ofNSErrorAPIs, purely as a practical matter to be more resilient in the face of incorrect code. And since ARC nils out all automatic variables with an object type, your crash suggests thatewas not nilled out and therefore you’re not in ARC.However, despite what I just said in the above paragraph, you still should not assume that any
NSError-enabled API will leave theNSErrorvalue alone when no error is thrown. The current guidelines for implementing such APIs do say that should be true, but that’s not a hard requirement. Any code written before those guidelines may not behave that way, and any code written after the guidelines may just ignore them. The rules for calling anNSError-enabled API continue to state that the return value of the API must be consulted, and theNSError*variable may only be observed if the return value indicated an error occurred.