Use the following standard calls to get data from a server - and standard Alert if there's an error - like no internet access. If I turn off the network the app crashes, never hitting the NSLog calls for *response or *error, never entering the alert.
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the URL call and download in bg
dispatch_async(concurrentQueue, ^{
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"https://www.websitethatdownloadsdata.com"] completionHandler:^(NSData *myData, NSURLResponse *response, NSError *error) {
NSLog(#"Resp value from NSURL task: %#", response);
NSLog(#"Error value from NSURL task: %#", error);
if (error == nil) {
NSLog(#"Downloading data...");
}
if (error != nil) {
UIAlertController * alert = [UIAlertController alertControllerWithTitle:#"Network Problem" message:#"Cannot download data" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction * actionOK = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
//Here Add Your Action
abort();
}];
[alert addAction:actionOK];
[self presentViewController:alert animated:YES completion:nil];
}
The background queue is redundant because NSURLSession dispatches its tasks on a background thread anyway.
But you have to present the alert controller on the main thread
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"https://www.websitethatdownloadsdata.com"] completionHandler:^(NSData *myData, NSURLResponse *response, NSError *error) {
NSLog(#"Resp value from NSURL task: %#", response);
NSLog(#"Error value from NSURL task: %#", error);
if (error == nil) {
NSLog(#"Downloading data...");
} else {
UIAlertController * alert = [UIAlertController alertControllerWithTitle:#"Network Problem" message:#"Cannot download data" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction * actionOK = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
//Here Add Your Action
abort();
}];
[alert addAction:actionOK];
dispatch_async(dispatch_get_main_queue()) {
[self presentViewController:alert animated:YES completion:nil];
}
}
}
And you should display also the reason of the error in the NSError instance
Related
I'm try to do synchronous NSURLSessionDataTask with the below code but unable to proceed.
__block NSData *rData = nil;
__block BOOL taskDone = NO;
__block NSData *rError = nil;
NSURL *url = [NSURL URLWithString:dataURL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:1 timeoutInterval:30];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:nil delegateQueue:nil];
NSURLSessionDataTask *taskData = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
rData = [NSData dataWithData:data];
rError = [error copy];
taskDone = YES;
}];
[taskData resume];
while (taskDone == NO) {
if (_close == YES) {
[taskData cancel];
return nil;
}
usleep(20000);
}
I need to synchronous call so that I can remove the while loop which is not needed.
Below is my code with synchronous call using semaphore
dispatch_semaphore_t sem;
__block NSData *rData = nil;
__block BOOL taskDone = NO;
__block NSData *rError = nil;
NSURL *url = [NSURL URLWithString:dataURL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:1 timeoutInterval:30];
// creating semaphore
sem = dispatch_semaphore_create(0);
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:nil delegateQueue:nil];
NSURLSessionDataTask *taskData = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
rData = [NSData dataWithData:data];
rError = [error copy];
taskDone = YES;
//call semaphore
dispatch_semaphore_signal(sem);
}];
[taskData resume];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
// THIS part not sure... how can we accommodate this below code
while (taskDone == NO) {
if (_close == YES) {
[taskData cancel];
return nil;
}
usleep(20000);
}
above code could be correct ?
I understand that what you want to do is wait for the DataTask to be completed before continue with you code, the best way is to put your request in a function with a completionHandler.
First create a function that will return a NSURLSessionDataTask with a completion handler:
-(NSURLSessionDataTask*)startSessionDataTaskWithCompletionHandler:(void (^)(NSData *myData))completionBlock {
//Set your request
NSString *dataURL = #"www.yoururl.com";
NSURL *url = [NSURL URLWithString:dataURL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:1 timeoutInterval:30];
// I recommend to use sharedSession because is a simple request, so its not needed a specific session configuration.
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest: request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
if (completionBlock){
completionBlock(data);
return;
//When you call this function, the completionBlock will use this data
}
} else {
//Error handle
return;
}
}];
[dataTask resume];
return dataTask;
}
Then you can call this function from anywhere:
NSURLSessionTask *task = [self startSessionDataTaskWithCompletionHandler:^(NSData *myData) {
// put whatever code you want to perform when the asynchronous data task finish, for example:
rData = [NSData dataWithData:myData];
}];
if (!task) {
// handle failure to create task any way you want
}
You can make NSURLSessionDataTask synchronous with PromiseKit. Install it manually or add the following line to the Podfile if you use CocoaPods (tested with CocoaPods 1.7.3):
pod "PromiseKit", "6.10.0"
Add the following line to the top of the code file:
#import PromiseKit;
Then create a wrapper for your task:
- (AnyPromise*)promiseToLoadData:(NSString*)dataURL {
return [AnyPromise promiseWithResolverBlock:^(PMKResolver _Nonnull resolver) {
NSURL *url = [NSURL URLWithString:dataURL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:1 timeoutInterval:30];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:nil delegateQueue:nil];
NSURLSessionDataTask *taskData = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error != nil) {
resolver([error copy]);
} else {
resolver([NSData dataWithData:data]);
}
}];
[taskData resume];
}];
}
Use wait to resolve the promise synchronously:
id value = [self promiseToLoadData:#"http://your.url"].wait;
if ([value isKindOfClass:[NSData class]]) {
NSLog(#"%#", [[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding]);
}
i'm hiting an API in postman there i get result fine, this how i'm making POST request in postman,
But when i hit same API in my application using objective c, i got errors, i'm passing parameters fine but result is not coming true, i'm confuse that why it is not showing results true, This is my code for POST request,
- (void)sendRequest
{
NSArray *userArray = [NSArray arrayWithObjects: #"ishaqshafiq#hotmail.com",nil];
NSDictionary *emp = #{#"lstUsers": userArray,
#"message":#"Your Order is Booked",
#"data": #{
#"type":#"text",
}};
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSString *urlLinkA=#"http://sajjenweb.azurewebsites.net/api/HubConnection/PostMobileNotification";
NSURL * url = [NSURL URLWithString:urlLinkA];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
NSString *parameters = [NSString stringWithFormat:#"%#",emp];
NSLog(#"parameter %#",parameters);
[urlRequest setHTTPMethod:#"POST"];
//[urlRequest setHTTPBody:[parameters dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Response:%# ", response);
NSLog(#"Error is %#",error);
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
// NSLog(#"DDD %#",dictionary);
NSString *res = [dictionary valueForKey:#"recipients"];
NSLog(#"RR: %#", res);
NSString *msg=#"Successfully Submitted";
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Success"
message:msg
preferredStyle:UIAlertControllerStyleAlert];
int duration = 2; // duration in seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[alert dismissViewControllerAnimated:YES completion:nil];
});
}];
NSLog(#"network error :");
[dataTask resume];
}
I have a method that posts HTTP data, After getting the response from API I want to display an UIAlertController on the basis of status codes.
Suppose the status code I got is 409, So the controller will say "data already exists".
How to present alert controller and in which class, service class or any other view controller?
ViewController.h
- (IBAction)logIn:(id)sender {
if (username.text == nil || [password.text isEqualToString:#""])
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:
#"all the fields are mendetary"
message:#"missing username or password " preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction
actionWithTitle:#"OKAY" style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action)
{
//[self dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];
}
else
{
[[service SharedInstance] logIn:[NSDictionary dictionaryWithObjectsAndKeys:username.text,#"username",password.text,#"password",nil] params:#"logIn" block:^(const BOOL success, id resultObject, NSError *error )
{
// [self performSegueWithIdentifier:#"Loggedin" sender:self];
}];
}
username.text =#"";
password.text =#"";
}
Service.m
-(void)logIn:(NSDictionary *)Details params:(NSString *)params block:(ResponseBlock)block{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSError *error;
NSString *URL = #"http://localhost:8080";
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:nil delegateQueue:nil];
NSString *requestURL = [URL stringByAppendingString:#"/api/signupUser"];
NSURL *url = [NSURL URLWithString:requestURL]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:120.0]; NSData *data = [params dataUsingEncoding:NSDataBase64Encoding64CharacterLineLength];
NSString *base64Encoded = [data base64EncodedStringWithOptions:0];
NSLog(#"base64Encoded%#",base64Encoded);
NSString *basicString = #"Basic";
basicString = [NSString stringWithFormat:#"%#%#",basicString,base64Encoded];
NSLog(#"basicString%#",basicString);
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:basicString forHTTPHeaderField:#"Authorization"];
[request setHTTPMethod:#"POST"];
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:Details options:0 error:&error];
[request setHTTPBody:bodyData];NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * data, NSURLResponse *response, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
long statusCode =[httpResponse statusCode];
NSLog(#"response status code: %ld", statusCode);
f (error)
{
block(NO, response,error);
}
else
{
NSError *err = nil;
id responseData = [NSJSONSerialization
JSONObjectWithData:data options:kNilOptions error:&err];
NSDictionary* headers= [(NSHTTPURLResponse *)response allHeaderFields];
NSLog(#"all header fields %#",headers);
[defaults removeObjectForKey:#"userToken"];
NSLog(#"token %#",[defaults valueForKey:#"userToken"]);
NSDate *expireDate = [[NSDate date]dateByAddingTimeInterval:60*25];
[defaults setObject:expireDate forKey:#"sessionDate"];
if(err)
{
block(NO, response, error);
}
else
{
block(YES, responseData,nil);
}
[task cancel];
}
}];
[task resume];
}
#end
It looks like you are making some http call from a service file. There are few options here:
Implement delegate of your service class in ViewController to perform any task there.
Use blocks.
Use framework like Rx here.
or if you just need to display the alert add the alert to current window e.g.
I have created an extension for this purpose:
extension UIAlertController {
/// display alert with custom number of buttons
static func presentAlert(_ title: String?, message: String?, alertButtonTitles: [String], alertButtonStyles: [UIAlertActionStyle], vc: UIViewController, completion: #escaping (Int)->Void) -> Void
{
let alert = UIAlertController(title: title,
message: message,
preferredStyle: UIAlertControllerStyle.alert)
for title in alertButtonTitles {
let actionObj = UIAlertAction(title: title,
style: alertButtonStyles[alertButtonTitles.index(of: title)!], handler: { action in
completion(alertButtonTitles.index(of: action.title!)!)
})
alert.addAction(actionObj)
}
vc.present(alert, animated: true, completion: nil)
}
}
use:
UIAlertController.presentAlert("My Title", message: "My message", alertButtonTitles: "OK", alertButtonStyles: [.default], vc: (UIApplication.shared.keyWindow?.rootViewController)!, completion: { (btnIndex) in
})
Above code is adding UiAlerController over window.
i am very new to objective c .I am trying to delete the table row from uitableview. whenever i trying to delete the first row of the tableview i am getting the following exception "Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete row 1 from section 0 which only contains 1 rows before the update'" i don't what is the mistake i done.
can anyone please suggest me what is the mistake i done?
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle==UITableViewCellEditingStyleDelete)
{
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:#"alert"
message:#"Are you want to delete the selected date."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* canclebutton = [UIAlertAction
actionWithTitle:#"CANCEL"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
//Handle your yes please button action here
}];
UIAlertAction* continuebutton = [UIAlertAction
actionWithTitle:#"CONTINUE"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
NSString *userid = [[NSUserDefaults standardUserDefaults]
stringForKey:#"UserId"];
NSString *encodeddistrictvalue = [selecteddistrictvalue stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet alphanumericCharacterSet]];
NSString *encodedPanchaytvalue = [selectedpanchayatvalue stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet alphanumericCharacterSet]];
NSString *urlStr = [NSString stringWithFormat:#"my web service url"];NSURL *url = [NSURL URLWithString:urlStr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
if (data.length > 0 && connectionError == nil)
{
NSDictionary *dictionaryvalue = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
NSString *code = [[dictionaryvalue objectForKey:#"code"] stringValue];
NSLog(#"the response string value is %#",code);
if ([code intValue]==1) {
NSIndexPath *selectedIndexPath = [tableView indexPathForSelectedRow];
NSLog(#"the second button to naviagte %#",selectedIndexPath);
[assessmentnumber removeObjectAtIndex:indexPath.row];
[tableView reloadData];
}
else
{
NSLog(#"alredy deleted");
[assessmentnumber removeAllObjects];
[_tableview reloadData];
}
}
} ];
}];
[alert addAction:canclebutton];
[alert addAction:continuebutton];
[self presentViewController:alert animated:YES completion:nil];
alert.view.tintColor = [UIColor blackColor];
}
}
As i am deleting the json data .whenever i am deleting the json data i have to remove that particular data in the tableview. Also whenever i trying to remove the data in the first row it throws the exception like "Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete row 1 from section 0 which only contains 1 rows before the update'" and app getting crashed
can anyone suggest me what was problem in the following coding?
- (IBAction)deletebutton:(id)sender {
NSString *userid = [[NSUserDefaults standardUserDefaults]
stringForKey:#"UserId"];
NSString *encodeddistrictvalue = [selecteddistrictvalue stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet alphanumericCharacterSet]];
NSString *encodedPanchaytvalue = [selectedpanchayatvalue stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet alphanumericCharacterSet]];
NSString *urlStr = [NSString stringWithFormat:#"my json url",encodeddistrictvalue,encodedPanchaytvalue,taxtypevariable,selctedassessmentno,userid];NSURL *url = [NSURL URLWithString:urlStr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
if (data.length > 0 && connectionError == nil)
{
NSDictionary *dictionaryvalue = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
NSString *code = [[dictionaryvalue objectForKey:#"code"] stringValue];
NSLog(#"the response string value is %#",code);
if ([code intValue]==1) {
NSLog(#"deletd sucessfully");
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:#"Alert"
message:#"Are u sure u want to delete the table data"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* yesButton = [UIAlertAction
actionWithTitle:#"yes"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
{
[assessmentnumber removeObject:selctedassessmentno ];
[self.tableview registerClass:[UITableViewCell class] forCellReuseIdentifier:#"expandingCell"];
[_tableview reloadData];
}
}];
[alert addAction:yesButton];
[self presentViewController:alert animated:YES completion:nil];
alert.view.tintColor = [UIColor blackColor];
}
}
} ];
}
put this viewDidLoad
[self.tableview registerClass:[UITableViewCell class] forCellReuseIdentifier:#"expandingCell"];
You can use begin & end update:
Table.beginUpdates()
for i in 0 ..< sections[section].items.count
{
Table.reloadRows(at: [IndexPath(row: i, section: section)], with: .automatic)
}
Table.endUpdates()