I'm attempting to download data from MySQL. However when NSURLSession creates a new thread, the data hasn't completely downloaded and thus the main thread is dealing with null values / crashes. I've used GCD's dispatch_async, however it's a few milliseconds slow.
In my test program, I have two NSLogs, one directly after calling the NSURLSession method and the other in the method itself. The output (below) shows NSURLSession is delayed by 30 milliseconds. Although while the time itself isn't much, it would have an enormous impact in how I'll structure my task. Is there any way to pause the main thread until the data has been downloaded and the method has been finished?
2014-03-30 18:56:06.224 TestProgram[1396:60b] (null)
2014-03-30 18:56:06.258 TestProgram[1396:60b] [{"error":200}]
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
if(error == nil)
{
dispatch_async(dispatch_get_main_queue(), ^{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"%#",text);
NSError *error;
self.json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
for(int i =0 ; i < self.json.count ; i++)
{
[dataParse setDictionary:_json[i]];
}
});
} else {
NSLog(#"Response:%# %#\n", response, error);
}
}];
[dataTask resume];
Take a look at the documentation for the NSURLSessionDataTask completion routine. It seems to imply that the data is not completely downloaded at that point.
Related
Hi I am using NSURLSession to fetch data from a server and I am using the following code for that purpose.
[[[NSURLSession sharedSession] dataTaskWithURL:urlForDiseaseTypes completionHandler:^(NSData *data, NSURLResponse *response,NSError *error)
{
if(!error)
{
NSHTTPURLResponse *httpRespc = (NSHTTPURLResponse *)response;
if(httpRespc.statusCode == 200)
{
NSArray *result = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
diseaseArray = [result valueForKeyPath:#"Value"];
diseaseIDarray = [result valueForKeyPath:#"ID"];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableViewMain reloadData];
});
}
}
}]resume];
And I am getting value inside both arrays(diseaseArray,diseaseIDarray). diseaseArray values are supposed to get displayed on table view. But when tableview opens no values are getting visible. When checked inside in Table View methods the array count is zero. How can I load my tableview with the values I get from NSURLSession block.
Currently I am calling this function in viewDidLoad.
I am trying to submit user input to a webpage and then retrieve returned data. This webpage takes a person's name and returns his/her information from a database. I tried to make a POST request when user clicks a button then send user's input to that webpage. But when I print out returned data, it's just the html source code of that webpage, only without javaScript. How can I change/add the below function to get the person's information page? I've spent lots of time but still can't figure it out. Please help me! Thank you!
-(void) sendHTTPPost{
NSString* input = nameTextField.text;
//1
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://find.pitt.edu"]];
[req setHTTPMethod:#"POST"];
//2
NSData* postData = [input dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
//3
NSString *postLength = [NSString stringWithFormat:#"%lu",(unsigned long)[postData length]];
[req setValue:postLength forHTTPHeaderField:#"Content-Length"];
[req setValue:#"text/html" forHTTPHeaderField:#"Content-type"];
[req setHTTPBody:postData];
//4
NSURLSession *session = [NSURLSession sharedSession];
//5
NSURLSessionDataTask *task = [session dataTaskWithRequest:req
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// Do something with response data here - convert to JSON, check if error exists, etc....
if(error != nil){
NSLog(#"error: %#", error);
}
else{
NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", str);
NSLog(#"response = %#", response);
}
}];
[task resume];
}
It is unlikely that the POST body is supposed to contain only the value. Chances are, it should contain
key=value&key2=value2&key3=value3...
where key is the name of the input, and value is the value of that input, in a URL-encoded format. (See "Encoding URL Data" in URL Session Programming Guide for details on how to URL-encode the values.)
I'm a beginner in the area to work with database on iOS. However I could find a way to connect to a MySQL database, download and parse the json feed. Now in iOS 9, I can't use NSURLConnection anymore, that's why I have to replace it with NSURLSession. I saw many tutorials for example this here. So far, I was not able to replace it. Because I'm under time pressure, I can't waste more time to do this. Is here anyone who could help me to replace it?
My code looks exactly like this:
- (void)downloadItems
{
// Download the json file
NSURL *jsonFileUrl = [NSURL URLWithString:#"http://myhost.ch/test.php"];
// Create the request
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:jsonFileUrl];
// Create the NSURLConnection
[NSURLConnection connectionWithRequest:urlRequest delegate:self];
}
#pragma mark NSURLConnectionDataProtocol Methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// Initialize the data object
_downloadedData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the newly downloaded data
[_downloadedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Create an array to store the locations
NSMutableArray *_locations = [[NSMutableArray alloc] init];
// Parse the JSON that came in
NSError *error;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:_downloadedData options:NSJSONReadingAllowFragments error:&error];
// Loop through Json objects, create question objects and add them to our questions array
for (int i = 0; i < jsonArray.count; i++)
{
NSDictionary *jsonElement = jsonArray[i];
// Create a new location object and set its props to JsonElement properties
Location *newLocation = [[Location alloc] init];
newLocation.idS = jsonElement[#"idStatistic"];
newLocation.temp = jsonElement[#"temp"];
newLocation.hum = jsonElement[#"hum"];
newLocation.date_time = jsonElement[#"date_time"];
// Add this question to the locations array
[_locations addObject:newLocation];
}
// Ready to notify delegate that data is ready and pass back items
if (self.delegate)
{
[self.delegate itemsDownloaded:_locations];
}
}
You can try this,
{
NSURL *url = [NSURL URLWithString:#"http://myhost.ch/test.php"];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
[theRequest setHTTPMethod:#"POST"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:theRequest
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
responseDict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSLog(#"Result:%#", responseDict);
}];
[task resume];
}
json is:
{
"Title":"Kick", "Year":"2009", "Rated":"N/A", "Released":"08 May 2009",
"Runtime":"N/A", "Genre":"Comedy, Romance, Thriller", "Director":"Reddy Surender",
"Writer":"Abburi Ravi (dialogue), Reddy Surender (screenplay), Vakkantham Vamsi (story)",
"Actors":"Ravi Teja, Ileana, Shaam, Ali",
"Plot":"A guy with an abnormal lifestyle tries to find thrill and pleasure in every work he does and finally becomes a thief.",
"Language":"Telugu", "Country":"India", "Awards":"1 nomination.",
"Poster":"N/A","Metascore":"N/A",
"imdbRating":"7.6",
"imdbVotes":"736",
"imdvid":"tt1579592",
"Type":"movie",
"Response":"True"
}
The Code is:
-(void)parseData:(NSData *)data
{
NSString *responseString = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
NSLog(#"responseString %#",responseString);
NSError *error;
NSArray *json = [NSJSONSerialization JSONObjectWithData:data
options:(kNilOptions)
error:&error];
NSLog(#"json data == %#", json);
NSMutableArray *response =[[NSMutableArray alloc]init];
for (int i = 0; i < json.count; i++)
{
response = [json valueForKey:#"imdbRating"];
User * userObj = [[User alloc]init];
userObj.movieRating = [json valueForKey:#"imdbRating"];
[response addObject:userObj];
}
[delegate getIMDBMovie_Rating:response];
}
How to save data in bussiness model object for key imdbRating
The Json you specified is not an array. This is an NSDictionary object. Also as this is a NSDictionary you don't need to use for loop.
Second thing you are doing wrong with your response variable.
you did:
response = [json valueForKey:#"imdbRating"];
User * userObj = [[User alloc]init];
userObj.movieRating = [json valueForKey:#"imdbRating"];
[response addObject:userObj];
here response variable is not an array even though you allocated it as NSMutableArray earlier. As you assign new value in response in the line: response = [json valueForKey:#"imdbRating"];
your variable type will be either NSNumber or NSString based on the type of [json valueForKey:#"imdbRating"].
So remove the line response = [json valueForKey:#"imdbRating"]; and it will work.
By doing this you will hv an array of User objects containing imdbRatings.
UPDATE
According to your comment your code has:
-(void)getIMDBMovie_Rating:(NSMutableArray*)retrievedData {
if (retrievedData.count > 0)
{
userObj = [retrievedData objectAtIndex:0];
NSLog(#"%#is the value",retrievedData);
iMDB_MovieRatingLabel.text=userObj.movieRating;
}
}
Problem is in your getIMDBMovie_Rating. as your method is accepting NSMutableArray as parameter. But when you supply value for your method is NSDictionay [delegate getIMDBMovie_Rating:response]; (you can check by NSLog(#"%#", [response class]);).
So in your getIMDBMovie_Rating method you are accessing [retrievedData objectAtIndex:0];. objectAtIndex is not available with NSDictionay that's why your app get crash.
Try like this,
-(void)parseData:(NSData *)data
{
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"responseString %#",responseString);
NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:(kNilOptions) error:&error];
NSLog(#"json data == %#", json);
NSMutableArray *response =[[NSMutableArray alloc]init];
NSArray *aArray = [json valueForKey:#"imbdRating"];
for (NSDictionary *dictionary in aArray)
{
/* Create a initWithData method in your User Model class and pass the dictionary object to it like,
- (id) initWithData:(NSDictionary *) jsonDictionary
{
self=[super init];
if(self)
{
self.movieRating =[json valueForKey:#"imdbRating"];
} */
User * userObj = [[User alloc]initWithData:dictionary];
[response addObject:userObj];
}
[delegate getIMDBMovie_Rating:response];
}
I have an API that requires I post a complex JSON object. The API saves and responds with a primitive ID (or an error object). I am not able to map the primitive ID. Any ideas? The example below, for simplicity's sake, is not doing the POST object mapping, but some of my API calls will require that as well.
I saw suggestions to utilize RestKit to build the request, and pass it to AFNetworking, but this will not parse a possible error return object.
RKObjectMapping* map = [RKObjectMapping mappingForClass:[MyPrimitiveResponse class]];
[map addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"success"]];
RKResponseDescriptor *errDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[MyErrorResponse objectMap] method:RKRequestMethodGET pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassServerError)];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:map method:RKRequestMethodGET pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
NSURL *URL = [NSURL URLWithString:apiUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[ errDescriptor, responseDescriptor ]];
[objectRequestOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"Load collection of Articles: %#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
[objectRequestOperation start];
I get the following error in the debugger:
2013-10-29 10:23:15.196 TestParser[6988:70b] E app:MyHomeViewController.m:54 Operation failed with error: Error Domain=org.restkit.RestKit.ErrorDomain Code=-1017 "Loaded an unprocessable response (200) with content type 'application/json'" UserInfo=0x8daca20 {NSErrorFailingURLKey=....., NSUnderlyingError=0x8db4cd0 "The operation couldn’t be completed. (Cocoa error 3840.)", NSLocalizedDescription=Loaded an unprocessable response (200) with content type 'application/json'}
UPDATE:
This is my final bits of code to handle this situation. It may prove useful to others...
// Manually Map Request to JSON & send to server
NSDictionary *parameters = [RKObjectParameterization parametersWithObject:payload requestDescriptor:[payload.class requestDescriptor] error:&error];
NSMutableURLRequest* request = [self.apiManager requestWithObject:nil method:RKRequestMethodPOST path:url parameters:parameters];
RKHTTPRequestOperation *requestOperation = [[RKHTTPRequestOperation alloc] initWithRequest:request];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
unichar firstChar = [operation.responseString characterAtIndex:0];
NSData *newData;
if (firstChar != '{' && firstChar != '[') {
// Force into JSON array so it can be parsed normally
newData = [[[#"[" stringByAppendingString:operation.responseString] stringByAppendingString:#"]"] dataUsingEncoding:NSUTF8StringEncoding];
} else {
newData = operation.responseData;
}
// Parse JSON response into native object, whether primitive NSNumber (integer or boolean) response or a full fledged JSON error object.
RKResponseDescriptor *errDescriptor = [MyErrorResponse responseDescriptor];
RKObjectResponseMapperOperation* mapper = [[RKObjectResponseMapperOperation alloc] initWithRequest:request response:operation.response data:newData responseDescriptors:#[errDescriptor, [payload.class responseDescriptor]]];
[mapper setDidFinishMappingBlock:^(RKMappingResult *mappingResult, NSError *error) {
if (mappingResult) { //Success
RKLogInfo(#"Load response: %#", mappingResult.firstObject);
} else {
RKLogError(#"Operation failed with error: %#", error);
}
}];
[mapper start];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
[requestOperation start];
I saw suggestions to utilize RestKit to build the request, and pass it to AFNetworking
Yes, do that.
but this will not parse a possible error return object
True, but you can use a mapping operation in RestKit to process that. Or, if the error response is relatively simple just use NSJSONSerialization (or similar) to convert the response.
The 'primitive ID' should be mappable with a nil-keypath mapping. But you may still have issues in mapping an error response if it isn't being mapped back into the source object used in the POST request.