AFNetworking 2 AFHTTPRequestOperation failure block stripping keys from error userInfo - ios

I have just updated my app from AFNetworking 1.3.3 to 2.0.1, which required rewriting my network client that used to subclass AFHTTPClient.
I swapped out AFHTTPClient for AFHTTRequestOperationManager (I need to support iOS 6) and everything works fine apart from this:
The server gives me a JSON error string with the details of the error in:
error.userInfo.localizedRecoverySuggestion
However, this key (localizedRecoverySuggestion) is no longer in my NSError object.
Does anybody any idea how I can access it? Or what part of AFNetworking is stripping it out? The server is still sending it, it just doesn't make it as for as the error object in my POST: etc methods.
I've spent some time on this and I'm struggling to find where the issue is.

After a spot of debugging, it looks like the data takes the following path through AFNetworking:
AFURLConnectionManager connection:didReceiveData:
AFURLResponseSerialization validateResponse:data:error:
At which point it is thrown away.
So as a quick fix I just added an extra dictionary entry to that method where userInfo is created as follows:
NSLocalizedRecoverySuggestionErrorKey: [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
This is obviously a complete hack, but if anyone could give a pointer on how to accomplish this correctly, I'd be very grateful.

Related

NSData Assignment Vanishes (becomes nil) Directly After Assigned

Let me start by saying I'm not proficient in objective c, nor am I an iOS developer. I'm working on a react-native app and find that I'm having to dig into the native code. So, I appreciate your patience with me and would also very much appreciate if you made zero assumptions about what I might, or might not know. Thx!
I'm trying to use react-native-mail but it fails to attach the photo I've selected to the email.
In troubleshooting, I jumped into Xcode's debugger for the first time. Stepping through the code, it appears as though the attachmentPath which is something like file:///var/mobile/... is being assigned to the variable fileData as type NSData. But then, taking one step further into the code it becomes nil.
I'm not sure why this would happen nor how to go about troubleshooting this. Here's an image of the debugger session with 3 screenshots stitched together side-by-side.
Here's the code: RNMail.m
All pointers, tips, guidance, and advice welcome
In your first screenshot, the debugger is still on the line that declares and assigns the fileData variable. This means that that line hasn't actually been executed yet. -dataWithContentsOfFile: hasn't yet been called, and thus the value that appears to be in fileData is not meaningful; what you're seeing is just garbage data prior to the variable actually being assigned. In your second screenshot, the -dataWithContentsOfFile: method has finished running, and it has returned nil. What you need to do is to figure out why you're getting nil from -dataWithContentsOfFile:. Perhaps the path to the file is incorrect, or perhaps you don't have permission to read it, or perhaps you have a sandboxing issue.
I would suggest using -dataWithContentsOfURL:options:error: instead of -dataWithContentsOfFile:. This will return an error by reference (create an NSError variable ahead of time, assign it to nil, pass a pointer to the error as the third parameter to -dataWithContentsOfURL:options:error:, and then check the error if the method returns nil). More likely than not, the contents of the error will explain what went wrong when trying to read the file.
EDIT: Looking at your screenshot again, the problem is clear; from the description of the contents of attachmentPath, we can see that it isn't a path at all, but instead it contains a URL string (with scheme file:). So you cannot pass it to the APIs that use paths. This is okay, since the URL-based mechanisms are what Apple recommends using anyway. So, just turn it into a URL by passing the string to -[NSURL URLWithString:] (or, even better, -[[NSURLComponents componentsWithString:] URL], since it conforms to a newer RFC). So, something like:
// Get the URL string, which is *not* a path
NSString *attachmentURLString = [RCTConvert NSString:options[#"attachment"][#"path"]];
// Create a URL from the string
NSURL *attachmentURL = [[NSURLComponents componentsWithString:attachmentURLString] URL];
...
// Initialize a nil NSError
NSError *error = nil;
// Pass a pointer to the error
NSData *fileData = [NSData dataWithContentsOfURL:attachmentURL options:0 error:&error];
if (fileData == nil) {
// 'error' should now contain a non-nil value.
// Use this information to handle the error somehow
}

Get NSString out of ENNoteContent with Evernote Cloud SDK 2.0

I am new to Evernote SDK development and am using the evernote cloud SDK 2.0 as recommended by Evernote.
However, I am having trouble to get the NSString content out of the ENNoteContent object. I have tried the followings from searching online but none seems to work with the cloud sdk as I guess they are all for the old version of Evernote SDK...
1 Using "convertENMLToHTML" method.
According to this and this, I could call convertENMLToHTML directly on an ENNoteContent object much like this convertENMLToHTML:note.content. However, in the cloud SDK, this resulted in an exception inside ENMLUtility that terminates the app because convertENMLToHTML is expecting an NSString as opposed to ENNoteContent and the first thing this function does is trying to call [enmlContent dataUsingEncoding:NSUTF8StringEncoding]] which caused the exception if enmlContent is a pointer to ENNoteContent but not a pointer to NSString.
2 Attempting to get _emml object out of the ENNoteContent object
This post has a quote of calling [note.content enml] but this again doesn't work with cloud sdk as object enml isn't defined in the interface.
Does anyone know how one can get an NSString out of ENNoteContent? I would expect this to be a very straightforward process but am surprised that I wasn't able to find anything that works for the Cloud SDK.
3 Using generateWebArchiveData method
Per Sash's answer below, I have also attempted to use the generateWebArchiveData method in the example from the cloud sdk. The code I have looks like this:
[[ENSession sharedSession] downloadNote:result.noteRef progress:^(CGFloat progress) {
} completion:^(ENNote *note, NSError *downloadNoteError) {
if (note) {
NSLog(#"%#", note.title);
[note generateWebArchiveData:^(NSData *data) {
NSString* strContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"test content %#", strContent);
}];
} else {
NSLog(#"Error downloading note contents %#", downloadNoteError);
}
}];
However, strContent outputs "null" for a note that I have verified with legitimate content.
As a temporary hack, we added #property (nonatomic, copy) NSString * emml;
in ENNoteContent.h and removed the same line in ENNoteContent.m to get around this for now.
You are close. Technique #1 above is what you want, but as you discovered the enml property is private in the "default" SDK. Import the "advanced" header and you'll have access to note.content.enml. That is a string, and you can send it to convertENMLtoHTML if you prefer an HTML representation.
Do note that there is no "plaintext" string content for an existing note. You'll always see it as markup, and if you want to get rid of the markup, doing so is beyond the scope of the SDK-- how to do that depends very much on what the content you're dealing with looks like.
You should check out their samples included with SDK, seems like
-[ENNote generateWebArchiveData:] will get you HTML NSData in the completion block
https://github.com/evernote/evernote-cloud-sdk-ios/blob/master/Getting_Started.md#downloading-and-displaying-an-existing-note might also help

NSData initWithContentsOfURL reading not all data, but only on device

I am banging my head about an issue I have on iOS7 development. I use the following piece of code to load an image from a webserver:
NSData* data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://someServer/someImage.jpg"]];
This works like a charme in simulator, reading exactly the 134185 bytes that the image has. Creating an UIImage from that data works as intended.
Once I test the exact same code on a device (iPad Mini, iOS 7.03), though, it just reads 14920 byte from the same URL. Needless to say that I can't create an UIImage from that data then, creation fails and returns a nil.
The read does not produce any errors (no console output, and also using the signature with the error output param returns nil here). Is there anything I missed around this rather straightforward task? Haven't found anything on the web on this…
Thanks, habitoti
So you don't have any error, and something is downloading. Maybe try to read this response and post here (I guess it is html/text body)?
You can use NSString method:
+ (instancetype)stringWithContentsOfURL:(NSURL )url encoding:(NSStringEncoding)enc error:(NSError *)error;
Can I suggest you use a library like SDWebImage to retrieve your image, it caches it and downloads the images asynchronously.
It also has a category for UIImageView so you can just call [imageView setImageWithURL:]; and it will load the image in when its ready.

NSData dataWithContentsOfURL: not returning data for URL that shows in browser

I am making an iOS client for the Stack Exchange API. After a long, drawn out fight I finally managed to implement authentication - which gives me a token I stick into a URL. When the token is valid, the URL looks like this:
https://api.stackexchange.com/2.1/me/associated?key=_____MY_SECRET_KEY______&access_token=_____ACCESS_TOKEN_:)_____
which, when valid, brings me to this JSON in a webpage:
{"items":[{"site_name":"Stack Overflow","site_url":"http://stackoverflow.com","user_id":1849664,"reputation":4220,"account_id":1703573,"creation_date":1353769269,"badge_counts":{"gold":8,"silver":12,"bronze":36},"last_access_date":1375455434,"answer_count":242,"question_count":26},{"site_name":"Server Fault","site_url":"http://serverfault.com","user_id":162327,"reputation":117,"account_id":1703573,"creation_date":1362072291,"badge_counts":{"gold":0,"silver":0,"bronze":9},"last_access_date":1374722580,"answer_count":0,"question_count":4},...
And I get the correct JSON with this code:
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"https://api.stackexchange.com/2.1/me/associated?key=__SECRET_KEY_:)__&access_token=%#", [[NSUserDefaults standardUserDefaults] objectForKey:#"token"]]];
NSData *jsonData = [NSData dataWithContentsOfURL:url];
if (jsonData)
{
NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:nil];
}
When I manually invalidate the token, however, the URL still looks the same, and the page in a browser displays this:
{"error_id":403,"error_name":"access_denied","error_message":"`key` is not valid for passed `access_token`, token not found."}
However, dataWithContentsOfURL: is always nil. Why? What am I doing wrong?
I do get an NSError returned:
Error Domain=NSCocoaErrorDomain Code=256 "The operation couldn’t be completed. (Cocoa error 256.)" UserInfo=0x1dd1e9f0 {NSURL=https://api.stackexchange.com/2.1/me/associated?key=key((&access_token=to‌​ken))}
NSCocoaErrorDomain Code=256 actually means a "file system or file I/O related error whose reason is unknown".
Why you get this error is likely because using dataWithContentsOfURL: will not work with that remote URL - or maybe because of the query params which contain the authentication and the token. Thus, you get the "weird" error.
In general, NSData's dataWithContentsOfURL: should only be use to access local file resources.
In order to solve your problem, you should improve your code in two steps:
1) Use NSURLConnection's convenient class method
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
The block defines what to do with the response data when the request finished. Generally, first check the error parameter, then status code of the response and Content-type - in this order.
2) Replace the former with your own instance method (or one from a third party) with a similar signature but which is much more sophisticated.
Approach #2 enables you to implement and use the following important features
Cancellation
customize authentication in every aspects
load body data to files
process received chunks simultaneous
perform multiple requests in a queue which controls the number of simultaneous connections
and a couple more.
Approach #2 is oftentimes implemented as a subclass of NSOperation and encapsulates a NSURLConnection object (which you need to cancel the connection).
You'll find answers of how to use NSURLConnection in asynchronous mode implementing the delegates. Also, there are third party solutions.
You might find the official documentation invaluable, too:
Using NSURLConnection
For a quick start, you may take a look at my "Simple GET request" class on Gist:
SimpleGetHTTPRequest
This class is NOT based on NSOperation, but it can be modified easily. Consult the official documentation of NSOperation how to make a subclass. This is basically easy, but has a few important things (KVO) which you should get correct.
In my case, adding AppTransportSecuritySettings dictionary into info.plist and setting key AllowArbitraryLoads to true.
Fixed my problem...
Hope it helps new developers.
Your URL might be having a space that's why it returns nil.Just replace the space in URL with a '+' :
stringByReplacingOccurrencesOfString:#" " withString:#"+"
I had this problem when I changed my folder structure. It gave me other NSError codes such as 512 and 4 for any file operations (local and web). The solution was to delete my IOS Simulator folders (Library\Developer\CoreSimulator).
If you are trying to access a remote url via HTTP and using XCode 7 or later you may get a NSCocoaErrorDomain returned from [NSData dataWithContentsOfURL:].
The root cause of this may actually be your "App Transport Security Settings". By default iOS doesn't allow arbitrary loading of URLS.

Using output parameters with ARC

So I have read this question, which seems to be exactly the kind of problem I am having, but the answer in that post does not solve my problem. I am attempting to write a data serialization subclass of NSMutableData. The problematic function header looks like this:
-(void)readString:(__autoreleasing NSString **)str
I do some data manipulation in the function to get the particular bytes the correspond to the next string in the data stream, and then I call this line:
*str = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding];
No errors in this code. But when I try to call the function like so:
+(id) deserialize:(SerializableData *)data
{
Program *newProgram = [[Program alloc] init];
[data readString:&(newProgram->programName)];
On the line where I actually call the function, I get the following error:
Passing address of non-local object to __autoreleasing parameter for write-back
I have tried placing the __autoreleasing in front of the NSString declaration, in front of the first *, and between the two *'s, but all configurations generate the error.
Did I just miss something when reading the other question, or has something in the ARC compiler changed since the time of that post?
EDIT:
It seems that the problem is coming from the way I am trying to access the string. I can work around it by doing something like this:
NSString* temp;
[data readString&(temp)];
newProgram.programName = temp;
but I would rather have direct access to the ivar
You can't. You might gain insight from LLVM's document Automatic Reference Counting, specifically section 4.3.4. "Passing to an out parameter by writeback". However, there really isn't that much extra detail other than you can't do that (specifically, this isn't listed in the "legal forms"), which you've already figured out. Though maybe you'll find the rationale interesting.

Resources