JSON Data returned on NSURLConnection - arrays

I have an iOS app where Users have to Register before using the app. I have created the UI in Storyboard and I am reading the Users details from the UITextfields. I then send the details to the Register API that sends back a JSON response. I am using NSURLConnection for the communication.
Here is the response I am receiving from the test URL - for testing purposes only:
{"username":"Hans","password":"Hans"}
However, when I try to read the password to make sure that the user doesn't already exists (again, just for testing purposes), I get nil returned for password value returned.
In my .h file I declare the Data and Connection:
#interface RegisterViewController : UIViewController <NSURLConnectionDataDelegate>
{
// Conform to the NSURLConnectionDelegate protocol and declare an instance variable to hold the response data
NSMutableData *buffer;
NSURLConnection *myNSURLConnection;
}
In my .m file, when someone clicks the REGISTER button, I create the request and start the connection as below. I am giving a dummy URL in example but the response I am receiving is:
{"username":"Hans","password":"Hans"}
- (IBAction)registerButtonClicked:(id)sender
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://myDummyURL/Login.php"]];
// Construct the JSON Data
NSDictionary *stringDataDictionary = #{#"firstname": firstname, #"lastname": lastname, #"email": email, #"password" : password, #"telephone" : telephone};
NSError *error;
NSData *requestBodyData = [NSJSONSerialization dataWithJSONObject:stringDataDictionary options:0 error:&error];
// Specify that it will be a POST request
[request setHTTPMethod:#"POST"];
// Set header fields
[request setValue:#"text/plain" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
//NSData *requestBodyData = [stringData dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:requestBodyData];
myNSURLConnection = [NSURLConnection connectionWithRequest:request delegate:self];
// Ensure the connection was created
if (myNSURLConnection)
{
// Initialize the buffer
buffer = [NSMutableData data];
// Start the request
[myNSURLConnection start];
}
}
The connection is created with no problem here.
In my .m file I implement the delegate methods and in connectionDidFinishLoading() I try and read the returned JSON. Below is the code I am using for this.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Dispatch off the main queue for JSON processing
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
NSString *jsonString = [[NSJSONSerialization JSONObjectWithData:buffer options:0 error:&error] description];
// Dispatch back to the main queue for UI
dispatch_async(dispatch_get_main_queue(), ^{
// Check for a JSON error
if (!error)
{
NSError *error = nil;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&error];
NSDictionary *dictionary = [jsonArray objectAtIndex:0];
NSString *test = [dictionary objectForKey:#"password"];
NSLog(#"Test is: %#", test);
}
else
{
NSLog(#"JSON Error: %#", [error localizedDescription]);
}
// Stop animating the Progress HUD
});
});
}
From the log screen grab below you can see the jsonString returned has values but the jsonArray is always nil. The error reads: error NSError * domain: #"NSCocoaErrorDomain" - code: 3840 0x00007ff158498be0
Thanks in advance.

Your jsonString is in fact the NSDictionary object - created by NSJSONSerialization - you're looking for, there is no array. In a JSON string curly braces {} represent a dictionary and square brackets [] represent an array
Try this
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Dispatch off the main queue for JSON processing
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:buffer options:0 error:&error];
// Dispatch back to the main queue for UI
dispatch_async(dispatch_get_main_queue(), ^{
// Check for a JSON error
if (!error)
{
NSString *test = [dictionary objectForKey:#"password"];
NSLog(#"Test is: %#", test);
}
else
{
NSLog(#"JSON Error: %#", [error localizedDescription]);
}
// Stop animating the Progress HUD
});
});
}
Edit: I overlooked the description method at the end of the NSJSONSerialization line. Of course that must be deleted.

Your code has 2 issues:
You are converting JSON server response to NSString.
Your JSON data is indeed a NSDictionary.
This must fix your issue:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Dispatch off the main queue for JSON processing
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:buffer options:0 error:&error];
// Dispatch back to the main queue for UI
dispatch_async(dispatch_get_main_queue(), ^{
// Check for a JSON error
if (!error)
{
NSString *test = [dictionary objectForKey:#"password"];
NSLog(#"Test is: %#", test);
}
else
{
NSLog(#"JSON Error: %#", [error localizedDescription]);
}
// Stop animating the Progress HUD
});
});
}

Related

Spring Cloud Open Feign: Decoder for ByteArrayResource

I have a Spring Boot Rest End Point defined in an interface to download an image
#GetMapping(value = "/{name}")
ResponseEntity<ByteArrayResource> getFileByName(#PathVariable("name") String name);
And I use Feign Builder to invoke this end point.
Feign.builder()
.client(new ApacheHttpClient())
.contract(new SpringMvcContract())
.decoder(new JacksonDecoder())
.encoder(new JacksonEncoder())
.target(clazz, url)
On invoking, I get below error
com.fasterxml.jackson.core.JsonParseException: Unexpected character ('�' (code 65533 / 0xfffd)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')
at [Source: (BufferedReader); line: 1, column: 2]
When I try to invoke the end point directly from Insomnia, it works fine. But fails through Feign Builder. The response content type is image/jpeg
Is there any specific decoder in feign to handle ByteArrayResource? I tried ResponseEntityDecoder, StreamDecoder and JacksonDecoder. None of it works.
On debugging, I see that Jackson ObjectMapper readValue fails. I tried changing the return type from ByteArraySource to byte[], didn't work either.
Any help?
I wrote my own little decoder and the problem was resolved. Below is the decoder
private Decoder byteArrayResourceDecoder() {
Decoder decoder = (response, type) -> {
if (type instanceof Class && ByteArrayResource.class.isAssignableFrom((Class) type)) {
return StreamUtils.copyToByteArray(response.body().asInputStream());
}
return new JacksonDecoder().decode(response, type);
};
return new ResponseEntityDecoder(decoder);
}
Hope this template helps others who has similar issues. Would have expected Feign to have decoder that supports all return types.
Thanks Maz - your solution helped me.
I modified your solution for my needs to read Spring StreamingResponseBody
1.) Create the decoder wrapper that either returns JacksonDecoder (Default) or reads the responsebody into a byte array.
Decoder decoder = (response, type) -> {
Map<String, Collection<String>> headers = response.headers();
Collection<String> contentType = null;
for (String x : headers.keySet()){
if ("content-type".equals(x.toLowerCase())){
contentType = headers.get(x);
}
}
if (contentType == null || contentType.stream().filter(x -> x.contains("application/json")).findFirst().isPresent()) {
return new JacksonDecoder(getMapper()).decode(response, type);
}
InputStream initialStream = response.body().asInputStream();
byte[] buffer = new byte[512];
byte[] result = null;
try(ByteArrayOutputStream out = new ByteArrayOutputStream()) {
try {
int length = 0;
while ((length = initialStream.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
} finally {
out.flush();
}
result = out.toByteArray();
} finally {
initialStream.close();
}
return result;
};
2.) Use the custom decoder with the Feign.Builder
Feign.Builder builder = Feign.builder()
// --
.decoder(decoder)
// --
openfeignfeignspringstreamingresponsebody

EKCalendarChooser crashes on iOS11.1

I execute the following code to let the user choose multiple calendars to use for my notepad app. Until iOS 10.3.1, there was no problem. On 11.0.2, it was still working on actural devices. However, since 11.1 it crashes with the following error.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSDictionaryM setObject:forKeyedSubscript:]: key cannot be nil'
The code is as follows. Basically, I'm opening a blank calendar chooser.
if (_eventStore == nil) {
_eventStore = [[EKEventStore alloc] init];
}
// the selector is available, so we must be on iOS 6 or newer
[_eventStore requestAccessToEntityType:EKEntityTypeEvent
completion:^(BOOL granted, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error)
{
// display error message here
}
else if (!granted)
{
// display access denied error message here
}
else
{
// access granted
EKCalendarChooser *calendarChooser = [[EKCalendarChooser alloc]
initWithSelectionStyle:EKCalendarChooserSelectionStyleMultiple
displayStyle:EKCalendarChooserDisplayAllCalendars
eventStore:_eventStore];
calendarChooser.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
calendarChooser.delegate = self;
calendarChooser.showsCancelButton = YES;
calendarChooser.showsDoneButton = YES;
NSSet *calendarSet = [[NSSet alloc] init];
calendarChooser.selectedCalendars = calendarSet;
UINavigationController *sub = [[UINavigationController alloc] initWithRootViewController:calendarChooser];
sub.navigationBar.barStyle = UIBarStyleDefault;
sub.toolbar.barStyle = UIBarStyleDefault;
[self presentViewController:sub animated:YES completion:nil];
//ios11 crashes after this
}
});
}];
Thanks for your help.
It turns out that EKCalendarChooserDisplayAllCalendars was causing the crash. Although it's not ideal, now I can avoid the crash when iOS is 11.1 or higher.
EKCalendarChooserDisplayStyle displayStyle = EKCalendarChooserDisplayAllCalendars;
if (#available(iOS 11.1, *)) {
displayStyle = EKCalendarChooserDisplayWritableCalendarsOnly;
}
EKCalendarChooser *calendarChooser = [[EKCalendarChooser alloc]
initWithSelectionStyle:EKCalendarChooserSelectionStyleMultiple
displayStyle:displayStyle
eventStore:eventStore];

Save NSMutableDictionary in iOS Keychain using iOS 6+

I am trying to save NSMutableDictionary in iOS keychain using KeychainItemWrapper classes. But I am not able to save it. I am getting error
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Couldn't add the Keychain Item.'
Here is my data to be saved
{
country = USA;
id = 3;
name = "Test User";
photo = "http://www.mydomain.com/images/user1.jpg";
result = true;
"country" = 1;
}
Here is my code
// Call to save
[self storeLoggedInUserInfoInKeychainWithDictionary:dict];
-(void)storeLoggedInUserInfoInKeychainWithDictionary:(NSMutableDictionary*)dict
{
// Save Login Credentials
KeychainItemWrapper* loginUserkeychain = [[KeychainItemWrapper alloc] initWithIdentifier:LOGIN_USER_SERVICE accessGroup:nil];
NSString *error;
[loginUserkeychain setObject:(__bridge id)(kSecAttrAccessibleWhenUnlocked) forKey:(__bridge id)(kSecAttrAccessible)];
NSData *dictionaryRep = [NSPropertyListSerialization dataFromPropertyList:dict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
[loginUserkeychain setObject:dictionaryRep forKey:(__bridge id)(kSecValueData)];
}
-(NSMutableDictionary*)fetchLoggedInUserInfoFromKeychain
{
KeychainItemWrapper* loginUserkeychain = [[KeychainItemWrapper alloc] initWithIdentifier:LOGIN_USER_SERVICE accessGroup:nil];
NSString *error;
//When the NSData object object is retrieved from the Keychain, you convert it back to NSDictionary type
NSData *dictionaryRep = [loginUserkeychain objectForKey:(__bridge id)(kSecValueData)];
NSDictionary *dictionary = [NSPropertyListSerialization propertyListFromData:dictionaryRep mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error];
if (error) {
NSLog(#"%#", error);
}
return [NSMutableDictionary dictionaryWithDictionary:dictionary];
}
-(void)resetLoggedInUserInfoFromKeychain
{
KeychainItemWrapper* loginUserkeychain = [[KeychainItemWrapper alloc] initWithIdentifier:LOGIN_USER_SERVICE accessGroup:nil];
[loginUserkeychain resetKeychainItem];
}
Can anybody tell me whats wrong in above code ?
Thanks in advance.
After few attempts & research using below code I am able to save the data in keychain. If any one is interested can have a look at below code
-(void)storeLoggedInUserInfoInKeychainWithDictionary:(NSMutableDictionary*)dict
{
// Create encoded data
NSData *encodedData= [NSKeyedArchiver archivedDataWithRootObject:dict];
// Create encoded string from data
NSString *encodedString= [encodedData base64EncodedString];
// Save Login Credentials
KeychainItemWrapper* tranxkeychain = [[KeychainItemWrapper alloc] initWithIdentifier:LOGIN_USER_KEYCHAIN accessGroup:nil];
[tranxkeychain setObject:(__bridge id)(kSecAttrAccessibleWhenUnlocked) forKey:(__bridge id)(kSecAttrAccessible)];
[tranxkeychain setObject:LOGIN_USER_SERVICE forKey: (__bridge id)kSecAttrService];
[tranxkeychain setObject:LOGIN_USER_INFO forKey:(__bridge id)(kSecAttrAccount)];
[tranxkeychain setObject:encodedString forKey:(__bridge id)(kSecValueData)];
}
-(NSDictionary*)fetchLoggedInUserInfoFromKeychain
{
KeychainItemWrapper* tranxkeychain = [[KeychainItemWrapper alloc] initWithIdentifier:LOGIN_USER_KEYCHAIN accessGroup:nil];
[tranxkeychain setObject:(__bridge id)(kSecAttrAccessibleWhenUnlocked) forKey:(__bridge id)(kSecAttrAccessible)];
[tranxkeychain setObject:LOGIN_USER_SERVICE forKey: (__bridge id)kSecAttrService];
// Get decoded string
NSString *decodedString=[tranxkeychain objectForKey:(__bridge id)(kSecValueData)];
// Get decoded data
NSData *decodedData= [NSData dataFromBase64String:decodedString];
NSDictionary *dict =[NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
return dict;
}
-(void)resetLoggedInUserInfoFromKeychain
{
KeychainItemWrapper* tranxkeychain = [[KeychainItemWrapper alloc] initWithIdentifier:LOGIN_USER_KEYCHAIN accessGroup:nil];
[tranxkeychain resetKeychainItem];
}

How to access user input from UIAlertView completion block without delegation?

Using iOS6:
I would like to retrieve the text entered by a user into a UITextField associated with the UIAlertView. I am aware that I could achieve the desired result with a delegate however I am curious about solving this issue with a callback function as I believe this may be an interesting pattern. I began by examining a common pattern for category extension of the UIAlertView class. Code below. Thanks in advance for any suggestions.
import <UIKit/UIKit.h>
#interface UIAlertView (Block)
- (id)initWithTitle:(NSString *)title message:(NSString *)message completion:(void (^)(BOOL cancelled, NSInteger buttonIndex, UITextField *textField))completion cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION;
#end
The .m for the category follows:
#import "UIAlertView+Block.h"
#import <objc/runtime.h>
static char const * const alertCompletionBlockTag = "alertCompletionBlock";
#implementation UIAlertView (Block)
- (id)initWithTitle:(NSString *)title
message:(NSString *)message
completion:(void (^)(BOOL cancelled, NSInteger buttonIndex))completion
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ... {
self = [self initWithTitle:title message:message delegate:self cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil ];
if (self) {
objc_setAssociatedObject(self, alertCompletionBlockTag, completion, OBJC_ASSOCIATION_COPY);
va_list _arguments;
va_start(_arguments, otherButtonTitles);
for (NSString *key = otherButtonTitles; key != nil; key = (__bridge NSString *)va_arg(_arguments, void *)) {
[self addButtonWithTitle:key];
}
va_end(_arguments);
}
[self setAlertViewStyle:UIAlertViewStylePlainTextInput];
return self;
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
id completion = objc_getAssociatedObject(self, alertCompletionBlockTag);
[self complete:completion index:buttonIndex];
}
- (void) complete:(void (^)(BOOL cancelled, NSInteger buttonIndex))block index:(NSInteger)buttonIndex {
BOOL _cancelled = (buttonIndex == self.cancelButtonIndex);
block(_cancelled, buttonIndex );
objc_setAssociatedObject(self, alertCompletionBlockTag, nil, OBJC_ASSOCIATION_COPY);
//objc_removeAssociatedObjects(block);
}
#end
Usage for the category is set below. The main problem is my inability to reference the UIAlertView textField at Index 0 from within the completion block.
[[[UIAlertView alloc] initWithTitle:#"Add"
message:#"Add New Asset Type"
completion:^(BOOL cancelled, NSInteger buttonIndex){
if (!cancelled) {
//call on completion of UISheetAction ???
NSLog(#"%#",needToAccessUIAlertView._textFields[0]);
}
}
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"OK", nil] show];
So basically you want to access the alert view from the block. You can do something like this:
__block __weak UIAlertView *alertViewWeak;
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Add"
message:#"Add New Asset Type"
completion:^(BOOL cancelled, NSInteger buttonIndex){
if (!cancelled) {
//call on completion of UISheetAction ???
NSLog(#"%#",[alertViewWeak textFieldAtIndex:0]);
}
}
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"OK", nil];
alertViewWeak = alertView;
[alertView show];
If you want to make a category by yourself, above is good enough.
But, there are many classes that uses delegation pattern. Do you want to make categories one by one?
There is REKit. With it, you can use that classes as if they were Block-based:
UIAlertView *alertView;
alertView = [[UIAlertView alloc]
initWithTitle:#"title"
message:#"message"
delegate:nil
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"OK", nil
];
[alertView
respondsToSelector:#selector(alertView:didDismissWithButtonIndex:)
withKey:nil
usingBlock:^(id receiver, UIAlertView *alertView, NSInteger buttonIndex) {
// Do something…
}
];
alertView.delegate = alertView;
Try this library Here is another useful library to do the same. http://ichathan.com/2014/08/19/ichalertview/

Use calendarItemWithIdentifier to lookup calendar event

I'm trying to lookup a calendar event by the new iOS method calendarItemWithIdentifier. I can't use the eventWithIdentifier because the identifier is changed after the event is syncronized with the server. The calendarItemIdentifier is not.
But the calendarItemWithIdentifier always returns (null).
EKEventStore *store = [[EKEventStore alloc] init];
[store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted) {
// Create event.
EKEvent *event = [EKEvent eventWithEventStore:store];
event.title = self.title;
event.startDate = [[NSDate date] dateByAddingTimeInterval:3600];
event.endDate = [[NSDate date] dateByAddingTimeInterval:7200];
event.timeZone = [NSTimeZone defaultTimeZone];
event.calendar = [store defaultCalendarForNewEvents];
BOOL success = [store saveEvent:event span:EKSpanThisEvent commit:YES error:&error];
if (success)
{
NSString *calendarItemIdentifier = event.calendarItemIdentifier;
NSLog(#"Assigned identifier: %#", calendarItemIdentifier);
// Look up the event in the calendar.
event = (EKEvent *)[store calendarItemWithIdentifier:calendarItemIdentifier];
if (event) {
NSLog(#"FOUND");
} else {
NSLog(#"NOT FOUND");
}
}
}
}];
From the log:
2013-01-13 10:32:52.042 CalendarIntegration[6095:1303] Assigned identifier: C5FD3792-EBF1-4766-B27D-2767E5C8F3BE
2013-01-13 10:32:52.043 CalendarIntegration[6095:1303] NOT FOUND
Help would be appreciated.
According the doc, this behavior is as expected, link,
A full sync with the calendar will lose this identifier. You should have a plan for dealing with a calendar whose identifier is no longer fetch-able by caching its other properties.

Resources