I am trying to make this block.
+ (void)getUsersAndTracksWithSearch:(NSString *)search withCompletion:(void(^)(NSMutableArray *users, NSMutableArray *tracks)) completion
{
}
I am using SoundCloud API. I am using their API method for getting a list of tracks and users. which is:
[SCRequest performMethod:SCRequestMethodGET
onResource:[NSURL URLWithString:userURL]
usingParameters:nil
withAccount:nil
sendingProgressHandler:nil
responseHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
})];
I am using this method twice and getting two responses, so how should I set up a block so that on completion of the block I have both responses?
Thanks
Have a look at NSOperationQueue, it can be used in a way which chains their operations i.e. chains their blocks.
I need to get image information from server, such image name, image id. Then use image id as one of parameters to make post, get image actual data. More specific, there are three images I should get.
First, I use getImageInfo to get image information.
- (void)getImageInfo {
// compose request
NSUserDefaults *getUserInfo = [NSUserDefaults standardUserDefaults];
NSString *uid = [getUserInfo objectForKey:#"uid"];
NSString *checkCode = [getUserInfo objectForKey:#"checkCode"];
NSString *data = [NSString stringWithFormat:#"uid=%#&yangzhengma=%#", uid, checkCode];
NSURL *url = [NSURL URLWithString:#"http://121.199.35.173:8080/xihuan22dcloud/services/Shibietupianservice/serviceGetallshibietu"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPBody = [data dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPMethod = #"POST";
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
if (!error) {
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
// parse data in ram and put into images' imageInfos array
[self.images parseImageInfo:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
[self getImageRawData];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}
}] resume];}
Then I use getImageRawData to get three image data.
- (void)getImageRawData {
// compose request dynamically
NSUserDefaults *getUserInfo = [NSUserDefaults standardUserDefaults];
NSString *uid = [getUserInfo objectForKey:#"uid"];
NSString *checkCode = [getUserInfo objectForKey:#"checkCode"];
NSURL *url = [NSURL URLWithString:#"http://121.199.35.173:8080/xihuan22dcloud/services/Shibietupianservice/serviceGetthetupian"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = #"POST";
NSInteger count = 0;
for (ImageInformation *temp in self.images.imageInfos) {
NSString *data = [NSString stringWithFormat:#"uid=%#&yangzhengma=%#&tupianid=%#", uid, checkCode, temp.imageId];
request.HTTPBody = [data dataUsingEncoding:NSUTF8StringEncoding];[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
// if client side is no errors, continue
if (!error) {
// if server side is no errors, continue
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
NSLog(#"图片内容:%#", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
// in ram and put into images' imageRawData array
[self.images parseImageRawData:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] withImageId:temp.imageId withIndex:count];
// store data to disk
// NSString *path = [[NSString alloc] initWithFormat:#"image%#", temp.imageId];
// [FCFileManager writeFileAtPath:path content:data];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}
}] resume];
count++;
}}
Here, it will loop three times, three responses come back, only the last one is complete, the others carry a error message, or incomplete raw data sometimes. Now I'm diving into concurrency programming guide, I guess serial queue likely can solve this problem.
Output like this:
2014-12-16 22:38:48.739 WeddingNewVersion[997:83366] 图片内容:<ns:serviceGetthetupianResponse xmlns:ns="http://serviceimpl.my.com"><ns:return>error</ns:return></ns:serviceGetthetupianResponse>
2014-12-16 22:38:48.749 WeddingNewVersion[997:83366] 图片内容:<ns:serviceGetthetupianResponse xmlns:ns="http://serviceimpl.my.com"><ns:return>error</ns:return></ns:serviceGetthetupianResponse>
2014-12-16 22:38:51.943 WeddingNewVersion[997:83366] 图片内容:<ns:serviceGetthetupianResponse xmlns:ns="http://serviceimpl.my.com"><ns:return>/9j/...(complete data)...9k=%%226654474.0</ns:return></ns:serviceGetthetupianResponse>
parameters of requests:
2014-12-17 14:59:25.364 WeddingNewVersion[1875:226651] uid=6&yangzhengma=odWoDXWcBv1jOrEhywkq7L&tupianid=41
2014-12-17 14:59:25.368 WeddingNewVersion[1875:226651] uid=6&yangzhengma=odWoDXWcBv1jOrEhywkq7L&tupianid=42
2014-12-17 14:59:25.368 WeddingNewVersion[1875:226651] uid=6&yangzhengma=odWoDXWcBv1jOrEhywkq7L&tupianid=43
the problem is likely not in composing request.
------------------------------------------------update1-----------------------------------------------
I have tried to put data task of session into a serial queue. Disappointed, this is not working.
dispatch_async(self.serialQueue, ^{
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){...}] resume];
});
Meanwhile, I make delegateQueue of session as nil, reference says if nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.
Now I am still confused how to make it right.
-----------------------------------------------update2------------------------------------------------
I add [NSThread sleepForTimeInterval:0.5] into the block dispatched to serial queue.
dispatch_async(self.serialQueue, ^{
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){...}] resume];
[NSThread sleepForTimeInterval:0.5];
});
It does not work. The three responses are complete, but they are all the same.
Thank you in advance!
I'm just guessing as I've never tried it, but possibly your data tasks are all using the same TCP port on your end.
That would be OK if they were serialized - one after the other, in sequence - but if they overlap, then the server would receive garbled HTTP requests:
GET /foo
GET /bar
GET /baz
What the server would see might be something like:
GET /fGET /baroo
GET /baz
That your third requests actually works OK might be an accident of the timing.
If you absolutely require the three requests to be issued simultaneously, there are ways to open three different ports on your end. I don't know how to do it with Cocoa and Objective-C, but you can certainly do it with C and Berkeley Socket system calls. The Cocoa / Cocoa Touch networking methods are just wrappers around sockets.
A couple of thoughts:
Your technique of using a single NSMutableURLRequest instance, and repeatedly mutating it for each request (while the prior requests are still in progress) is curious.
In the spirit of thread safety, I would use a separate NSMutableURLRequest for each concurrent request. You don't want to risk having your thread issuing these requests mutate the request object while some background thread performing one of the prior requests. (See Apple's Thread Safety Summary in the Threading Programming Guide in which they point out that mutable classes are not generally thread safe.)
Having said that, the NSURLConnection documentation leaves us with the impression that this request object would be copied, mitigating this problem. I don't see this sort of assurance in the NSURLSession documentation (though I suspect it does the same thing).
I don't think this is the problem here (if this was the problem, the problem would likely be more erratic than what you report, and besides, I suspect that NSURLSession is handling this gracefully, anyway), but as a matter of good thread-safe coding habits, it would be prudent to let each concurrent request have its own NSMutableURLRequest object.
You have confirmed that the information being used in the requests looks valid.
If you wanted to take this to the next level, you might use Charles (or Wire Shark or whatever tool you prefer) to observe the actual requests as they go out. These sorts of tools are invaluable for debugging these sorts of problems.
If you observe the requests in Charles and confirm that they are valid, then this categorically eliminates client-side issues from the situation.
What is curious is that you are not receiving NSError object from dataTaskWithRequest. Nor are you receiving statusCode other than 200 from your server. That means that your requests were successfully sent to the server and received by the server.
Instead, the server is processing the request, but is having a problem fulfilling the request. This leads me to wonder about the server code, itself. I suspect that there is something in the server code that is preventing concurrent operations from taking place (e.g., locking some shared resource, such as temp file or SQL table, for the duration of the request). I would take a hard look at the server code and make sure there are no potential contention issues.
Furthermore, I would modify the server code to not simply report "error", but rather to produce a meaningful error message (e.g. system provided error messages, error codes, etc.). Your server is detecting an error, so you should have it tell you precisely what that error was.
Note, I am explicitly not advising you to make your requests run sequentially. That is inadvisable. While it might solve the immediate problem, you pay a huge performance penalty doing that, and it's not scalable. And remember, you really must handle concurrent requests gracefully, as you're likely to have multiple users of the app at some point.
I would take a hard look at the server code, adding further debugging information to the error messages in order to track down the problem.
I put request into for loop, it works. The first thought of rob about NSMutableRequest and NSURLSession seems right, I'm trying to catch the whole idea. Thanks for rob's answer. Anyway, this is code.
for (ImageInformation *temp in self.images.imageInfos) {
// compose request dynamically
NSURL *url = [NSURL URLWithString:#"http://121.199.35.173:8080/xihuan22dcloud/services/Shibietupianservice/serviceGetthetupian"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = #"POST";
NSString *data = [NSString stringWithFormat:#"uid=%#&yangzhengma=%#&tupianid=%#", uid, checkCode, temp.imageId];
request.HTTPBody = [data dataUsingEncoding:NSUTF8StringEncoding];
// data task
dispatch_async(self.serialQueue, ^{
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
// if client side is no errors, continue
if (!error) {
// if server side is no errors, continue
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
// in ram and put into images' imageRawData array
[self.images parseImageRawData:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] withImageId:temp.imageId];
// store data to disk
// [FCFileManager writeFileAtPath:path content:data];
// dispatch display image task to main
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.images.imageDrawDatasDic count] == [self.images.imageInfos count]) {
[self.tableView reloadData];
}
});
}
}
}] resume];
[NSThread sleepForTimeInterval:0.5];
});
}
}
I am beginner in iOS and objective C programming, and I have an issue I didn't manage to solve because I just don't know where to begin, and what to do. Here is the context : I have a sidebar menu (like facebook's one), and when you select a category of this sidebar menu, I display an UITableView that contents a list of image and some text, the list of image and the text comes from internet with a JSON feed. My app currently works fine, but each time I change the category it freezes the time to load JSON data from internet. Here is a sample of code of my TableViewController.m :
- (void)viewDidLoad {
[super viewDidLoad];
[self refreshTable];
}
-(void)refreshTable {
// Send a synchronous request
NSURLRequest * urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:URLValue]];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * data = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
if (error == nil)
{
// Parse JSON data
}
}
I would really appreciate some explanations with the steps to follow since I am really beginner. I have found some similar questions and they talk about delegate, what is delegate and how do I call it?
Thank you very much guys for taking the time to read my issue.
You want to use:
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// parsing code goes here
// update table view
[self.tableView reloadData];
}
Move your parsing code inside the completion block. For more info see: https://developer.apple.com/library/Mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html#//apple_ref/occ/clm/NSURLConnection/sendAsynchronousRequest:queue:completionHandler:.
At the end of parsing you can call [self.tableView reloadData] to refresh.
Intro:
I have basically learned Obj-C and Cocoa Touch from Stackoverflow over the last few weeks, so bravo community, bravo.
Alas I have come to a question I cannot find an answer to/don't know how to Google.
I am making an app that 100% interfaces with an API service. Here's the process:
App launch -> Retrieve token
Subsequent requests -> HTTP Header token
My first view controller is a loading icon while a synchronous request takes place.
I would like to make Async requests in the future as currently my UI gets blocked while these requests take place.
My PHP background has caused me to set up my files like this, so let me know if this is incorrect.
APIConnect.h/.m -> Holds all my API methods
-(NSDictionary *)APIcall:(NSString *)api postData:(NSDictionary *)postData
TableViewController.h/.m -> Table based off data loaded from the method above.
When selecting a row, it moves forward in the UI and retrieves the details of that item (another API call).
When moving forward, it's apparent the main thread is blocked.
I therefore have tried to set up an async method
-(NSDictionary *)asyncAPIcall:(NSString *)api postData:(NSDictionary *)postData
APIConnect.m (abbreviated):
-(NSDictionary *)asyncAPIcall:(NSString *)api postData:(NSDictionary *)postData
{
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error){
if ([data length] > 0 && error == nil)
[self returnedData:data];
else
[self downloadError:error];
}];
}
-(void)returnedData:(NSData*)data
{
NSMutableDictionary *returnData = [[NSMutableDictionary alloc] init];
[returnData setDictionary:[NSJSONSerialization JSONObjectWithData:data
options:0
error:nil]];
NSLog(#"Returned Data %#", returnData);
[self setReturnURLData:returnData];
}
I really don't know how to ask my question, so maybe you can help me with that... However in theory I would like to:
Make an async request within APIConnect.m
Once async is done, it returns data to TableViewController.m
Once TableViewController receives data it will run it's processing and run [self.tableView reloadData];
All meanwhile, an activity indicator or something is visible to show that it's working on getting something
Any guidance on what to Google would be super helpful.
I have some difficulties to set up the correct configuration relative to sendAsynchronousRequest:queue:completionHandler: method (NSURLConnection class).
My scenario is the following:
I set up a singleton class that manages different NSURLConnections. This singleton istance has a NSOperation Queue (called downloadQueue) that makes a request to a web server and retrieves a string path (1).
Once done, the path is used to download a file within a web server (2). Finally, when the file has been correctly downloaded, I need to update the UI (3).
I figured out only the first request: the one through which I'm able to download the path. Could you suggest me a way to perform the other two steps?
Few questions here:
the download queue (downloadQueue) is not the main one, is it possible to open a new NSURLConnection in that queue? In other words, is it correct? (See comments in code snippets)
if the previous question is correct, how can I grab the main queue and update the UI?
Here the code snippet I use to perform the first step where downloadQueue is an instance variable that can be obtain through accessor mehods (#property/#synthesized);
// initializing the queue...
downloadQueue = [[NSOperation alloc] init];
// other code here...
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[self downloadQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if([data length] > 0 && error == nil) {
// here the path (1)
// how to perform a second connection?
// what type of queue do I have to use?
}
}];
You're on the right track for performing your first download.
In the completion handler block after the first download, you're computing the URL that you'll need for a second download, right? Then you can perform that second download the same way: call +[NSURLConnection sendAsynchronousRequest:...] again with the new URL and the same queue. You can do this within the completion block for the first download.
To update the UI after the second download is done, switch to the main queue within the completion block for that download. You can do this with dispatch_async() or dispatch_sync() (in this case it doesn't matter which because you don't have further work to do on the download queue) and dispatch_get_main_queue(), or with -[NSOperationQueue addOperationWithBlock:] and +[NSOperationQueue mainQueue].
Your code should look something like this:
// init download queue
downloadQueue = [[NSOperationQueue alloc] init];
// (1) first download to determine URL for second
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[self downloadQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if([data length] > 0 && error == nil) {
// set newURLRequest to something you get from the data, then...
// (2) second download
[NSURLConnection sendAsynchronousRequest:newURLRequest queue:[self downloadQueue] completionHandler:^(NSURLResponse *newResponse, NSData *newData, NSError *newError) {
if([newData length] > 0 && newError == nil) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// (3) update UI
}];
}
}];
}
}];
For updating the ui, as far as I know, you have to do that on the main thread. The ui could be updated from other threads but those updates are not fully reliable. In an app that I put together that made request to a web service, I make use of dispatch_async() to get access to the main queue and then I update, in my case a table view, from that call.
dispatch_async(dispatch_get_main_queue(), ^{
//block to be run on the main thread
[self.tableView reloadData];
});
I hope this helps.