Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I'm new for development learning to parsing the data and how to work with synchronous and asynchronous requests.
NSString *url=#"http://content.guardianapis.com/search?api-key=test";
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSError *error=nil;
NSURLResponse *response;
NSData *content=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSString *string=[[NSString alloc]initWithData:content encoding:NSUTF8StringEncoding];
NSLog(#"response %#",string);
I receive a warning at the sendSynchronousRequest line. How do I process this asynchronously web request and get the response data. Explain difference of both.
Two separate issues here.
First, the request should be asynchronous and use NSURLSession. For example:
NSURL *url = [NSURL URLWithString:#"http://content.guardianapis.com/search?api-key=test"];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(#"%#", error);
return;
}
NSError *parseError;
NSDictionary *responseObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (responseObject) {
NSLog(#"%#", responseObject);
dispatch_async(dispatch_get_main_queue(), {
// if you want to update your UI or any model objects,
// do that here, dispatched back to the main queue.
});
} else {
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"problem parsing response, it was:\n%#", string);
}
}];
[task resume];
// but do not try to use `data` here, as the above runs asynchronously
// do everything contingent upon this request _inside_ the completion
// handler above
Note, when you get the JSON response, you can parse that with NSJSONSerialization, as shown above.
Nowadays, using http for requests is frowned upon because it's not secure. You generally should use https. And NSURLSession will enforce this, returning an error if you try to use http. But if you want to force it to permit an insecure http request, you have to update your info.plist with an entry that looks like:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>guardianapis.com</key>
<dict>
<!--Include to allow subdomains-->
<key>NSIncludesSubdomains</key>
<true/>
<!--Include to allow HTTP requests-->
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<!--Include to specify minimum TLS version-->
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
</dict>
</dict>
So, go to your info.plist, control-click on it and choose "Open As" ... "Source Code" and then add the above into it. See https://stackoverflow.com/a/31254874/1271826 for more information.
Related
I am trying to upload images to Amazon S3 using this logic:
- (void)uploadImageToS3: (UIImage *)image {
imageData = UIImageJPEGRepresentation(image, 0.7);
AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new];
getPreSignedURLRequest.bucket = #"dummyimages";
getPreSignedURLRequest.key = #"test.jpg";
getPreSignedURLRequest.HTTPMethod = AWSHTTPMethodPOST;
getPreSignedURLRequest.expires = [NSDate dateWithTimeIntervalSinceNow:3600];
NSString *fileContentTypeString = #"text/plain";
getPreSignedURLRequest.contentType = fileContentTypeString;
[[[AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder] getPreSignedURL:getPreSignedURLRequest] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"Error: %#", task.error);
} else {
NSURL *presignedURL = task.result;
NSLog(#"upload presignedURL is \n%#", presignedURL);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:presignedURL];
request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
[request setHTTPMethod:#"PUT"];
[request setValue:fileContentTypeString forHTTPHeaderField:#"Content-Type"];
NSURLSessionUploadTask *uploadTask = [[NSURLSession sharedSession] uploadTaskWithRequest:request fromData:imageData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"Upload errer: %#", error);
}
NSLog(#"Done");
}];
[uploadTask resume];
}
return nil;
}];
}
I am getting an error like this:
'NSInternalInconsistencyException', reason: 'The service configuration is nil. You need to configure Info.plist or set defaultServiceConfiguration before using this method.
could please help this issue
Thanks
Have a look at http://docs.aws.amazon.com/mobile/sdkforios/developerguide/setup.html , in the section Preparing iOS 9 Apps.
You need to configure your service configuration in the class that is responsible for managing the Amazon Identity.
If you are using the newer version of MobileHub then the AWS constants
are configured in your info.plist and you need to check this
If you are using an older version MobileHub then the constants file is AWSConfiguration.
The class responsible for managing identity is AWSIdentityManager where the function that configures the service is
- (AWSTask *)initializeClients:(NSDictionary *)logins
If you are not using MobileHub you need to implement something similar in your own identity manager.
Add below code line for info.Plist file
<key>NSExceptionDomains</key>
<dict>
<key>amazonaws.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSThirdPartyExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
<key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
<key>amazonaws.com.cn</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSThirdPartyExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
<key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
</dict>
Here's what I'm doing: I'm processing a login that requires manipulating the data afterwards then fires off a new view. I want to do an async request since the server isn't always immediately responsive (and I don't want a crash because I held up the main thread with a synchronous connection)
This is what it looks like now:
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *dataReturn, NSData *data, NSError *error)
{
//blah blah working code
//once data is received, it's processed and I want to call a new view
viewController.hidesBottomBarWhenPushed = YES;
viewController.name = returnUserData[0];
viewController.userID = returnUserData[1];
viewController.role = returnUserData[2];
viewController.sID = returnUserData[3];
[self.navigationController pushViewController:viewController animated:YES];
NSLog(#"Pushed new view controller.");//*/
}];//end async request
Now my problem is that it's not actually visually pushing the view. I can see my NSLog responses are working correctly (the new view immediately responds with "Hello, [name]"), but visually nothing is showing up - This is a problem.
That's fine though, I decided to instead separate the code and try to run the view transition on the main thread
This is an adaptation of I've seen posted online:
NSLog(#"init NSURL response");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Begin async request");
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *dataReturn, NSData *data, NSError *error)
{
NSLog(#"inside async");
if (error)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"There was an error communicating with the server. Please try again." delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[alert show];
[alert release];
checkField.text = #"";
}
else
{
//NSLog(#"raw: %# - filtered: %#",dataReturn, (NSString *)dataReturn);
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
rawJSONData = [response objectForKey:#"d"];
NSLog(#"Set jsondata");
}
NSLog(#"ended async");
}];//end async request
NSLog(#"Beginning main thread work");
dispatch_sync(dispatch_get_main_queue(), ^{
//not important for the example
So what it's doing is giving me this:
2014-03-28 21:53:37.059 myApp[652:60b] init NSURL response
2014-03-28 21:53:37.059 myApp[652:3507] Begin async request
2014-03-28 21:53:37.059 myApp[652:3507] Beginning main thread work
It's skipping over the embedded async request entirely.
So what I'm left with is two off-the-main-thread-solutions that aren't doing what I want:
I want to run NSURLConnection off the main thread, I'm getting back JSON data I need to parse, and I want to wait until I get & parse that data before transitioning views.
Is that I'm trying to do possible? Or are my eyes glazed over and I'm just not seeing something I should be?
In your first example, you can probably fix it by either using [NSOperationQueue mainQueue] for the queue rather than what I presume is some background queue. Or dispatch your push to the main queue. UI updates always should be done on the main queue.
A couple of thoughts regarding your second example:
You don't have to dispatch your sendAsynchronousRequest request to a background queue, because it will do it asynchronously already. Wrapping it in a dispatch_async is redundant.
If you want to initiate some transition to the next view once you've successfully parsed the JSON, then put that code to transition to the next view inside the completionHandler block, right where you parse the JSON, not after the sendAsynchronousRequest.
You can use your queue object if you want, but then make sure you dispatch your UIAlertView and your "transition to view controller" code back to the main queue (because all UI updates should take place on the main queue). Frankly, it's easier to just specify the mainQueue as the queue for the completionHandler (because nothing you have there is so time consuming as to warrant the use of a background queue).
Merely as a matter of convention, I'd suggest leaving the NSURLResponse object called response, and rename that other variable.
You might want to include more error checking (e.g. was the JSON successfully parsed). There are many server errors that would not result in the NSError object of the completionHandler to be set (e.g. that's just for basic internet connectivity problems, not random server problems).
Thus, this yields:
NSLog(#"Issuing asynchronous request");
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSLog(#"Received response");
if (error)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"There was an error communicating with the server. Please try again." delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[alert show];
[alert release];
checkField.text = #"";
}
else
{
//NSLog(#"raw: %# - filtered: %#",dataReturn, (NSString *)dataReturn);
NSError *parseError = nil;
NSDictionary *responseObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&parseError];
if (responseObject)
{
rawJSONData = [responseObject objectForKey:#"d"];
NSLog(#"Set jsondata");
// initiate the transition to the new view controller here
}
else
{
NSLog(#"JSON parse failed: %#", parseError);
NSLog(#"string from server: %#", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);
}
}
NSLog(#"ended async");
}];//end async request
Clearly, you should be checking the response and that the JSON parsing
im trying to parse some JSON. for simplicity ill explain using the default example at github:
when running:
NSURL *url = [NSURL URLWithString:#"http://httpbin.org/ip"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request success:^(
NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"IP Address: %#", [JSON valueForKeyPath:#"origin"]);
} failure:nil];
[operation start];
i get the correct output logged. however, when i copy the example's content (which is basically 1 element) to a txt or html file (so URLWithString gets #"http:// my server address /file.txt"), putting it on my testing server and trying to prase from there, i get no output. what is wrong with this? thanks for your time!
(note: if i go to http:// my server address /file.txt i can see the contents there clearly so that's not the problem)
edit: as suggested, the content is:
"{
"origin": "10.44.119.100"
}"
Your problem probably has something to do with the fact that you're serving content as a text file (.txt) rather than as JSON (Content-Type: application.json / .json extension). AFNetworking is strict about HTTP standards in order to guard against unexpected behavior. Either set the correct Content-Type header on your server, or (as a hack) do AFJSONRequestOperation +addAcceptableContentTypes: adding text/plain.
As a meta note: when asking a question on Stack Overflow, specifics matter. If you had posted the error you were seeing in the console, it would be much easier to determine what the problem was. Likewise, approximate code is not actual code; if you have a problem, be specific about exactly what's going on. Details matter.
You should encode the json data first and then write it into the text file and when you are reading the data from file... decode the data first...
EDIT:
replace JSON operation with simple http and check if you are able to get data from there...
and if you are then JSONOperation basically is seeking for json response which is not in text file... i guess
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
[operation setUploadProgressBlock:^(NSInteger bytesWritten,long long totalBytesWritten,long long totalBytesExpectedToWrite)
{
NSLog(#"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite);
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSString *str = [[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding];
NSLog(#" Success %#",str);
// id response = AFJSONDecode(responseObject, nil);
[self requestSucceed:response];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"error: %#", operation.responseString);
}];
I have an app that right now needs to download hundreds of small PDF's based on the users selection. The problem I am running into is that it is taking a significant amount of time because every time it has to open a new connection. I know that I could use GCD to do an async download, but how would I go about doing this in batches of like 10 files or so. Is there a framework that already does this, or is this something I will have to build my self?
This answer is now obsolete. Now that NSURLConnection is deprecated and NSURLSession is now available, that offers better mechanisms for downloading a series of files, avoiding much of the complexity of the solution contemplated here. See my other answer which discusses NSURLSession.
I'll keep this answer below, for historical purposes.
I'm sure there are lots of wonderful solutions for this, but I wrote a little downloader manager to handle this scenario, where you want to download a bunch of files. Just add the individual downloads to the download manager, and as one finishes, it will kick off the next queued one. You can specify how many you want it to do concurrently (which I default to four), so therefore there's no batching needed. If nothing else, this might provoke some ideas of how you might do this in your own implementation.
Note, this offers two advantages:
If your files are large, this never holds the entire file in memory, but rather streams it to persistent storage as it's being downloaded. This significantly reduces the memory footprint of the download process.
As the files are being downloaded, there are delegate protocols to inform you or the progress of the download.
I've attempted to describe the classes involved and proper operation on the main page at the Download Manager github page.
I should say, though, that this was designed to solve a particular problem, where I wanted to track the progress of downloads of large files as they're being downloaded and where I didn't want to ever hold the entire in memory at one time (e.g., if you're downloading a 100mb file, do you really want to hold that in RAM while downloading?).
While my solution solves those problem, if you don't need that, there are far simpler solutions using operation queues. In fact you even hint at this possibility:
I know that I could use GCD to do an async download, but how would I go about doing this in batches of like 10 files or so. ...
I have to say that doing an async download strikes me as the right solution, rather than trying to mitigate the download performance problem by downloading in batches.
You talk about using GCD queues. Personally, I'd just create an operation queue so that I could specify how many concurrent operations I wanted, and download the individual files using NSData method dataWithContentsOfURL followed by writeToFile:atomically:, making each download it's own operation.
So, for example, assuming you had an array of URLs of files to download it might be:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
for (NSURL* url in urlArray)
{
[queue addOperationWithBlock:^{
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *filename = [documentsPath stringByAppendingString:[url lastPathComponent]];
[data writeToFile:filename atomically:YES];
}];
}
Nice and simple. And by setting queue.maxConcurrentOperationCount you enjoy concurrency, while not crushing your app (or the server) with too many concurrent requests.
And if you need to be notified when the operations are done, you could do something like:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self methodToCallOnCompletion];
}];
}];
for (NSURL* url in urlArray)
{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *filename = [documentsPath stringByAppendingString:[url lastPathComponent]];
[data writeToFile:filename atomically:YES];
}];
[completionOperation addDependency:operation];
}
[queue addOperations:completionOperation.dependencies waitUntilFinished:NO];
[queue addOperation:completionOperation];
This will do the same thing, except it will call methodToCallOnCompletion on the main queue when all the downloads are done.
By the way, iOS 7 (and Mac OS 10.9) offer URLSession and URLSessionDownloadTask, which handles this quite gracefully. If you just want to download a bunch of files, you can do something like:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSFileManager *fileManager = [NSFileManager defaultManager];
for (NSString *filename in self.filenames) {
NSURL *url = [baseURL URLByAppendingPathComponent:filename];
NSURLSessionTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSString *finalPath = [documentsPath stringByAppendingPathComponent:filename];
BOOL success;
NSError *fileManagerError;
if ([fileManager fileExistsAtPath:finalPath]) {
success = [fileManager removeItemAtPath:finalPath error:&fileManagerError];
NSAssert(success, #"removeItemAtPath error: %#", fileManagerError);
}
success = [fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:finalPath] error:&fileManagerError];
NSAssert(success, #"moveItemAtURL error: %#", fileManagerError);
NSLog(#"finished %#", filename);
}];
[downloadTask resume];
}
Perhaps, given that your downloads take a "significant amount of time", you might want them to continue downloading even after the app has gone into the background. If so, you can use backgroundSessionConfiguration rather than defaultSessionConfiguration (though you have to implement the NSURLSessionDownloadDelegate methods, rather than using the completionHandler block). These background sessions are slower, but then again, they happen even if the user has left your app. Thus:
- (void)startBackgroundDownloadsForBaseURL:(NSURL *)baseURL {
NSURLSession *session = [self backgroundSession];
for (NSString *filename in self.filenames) {
NSURL *url = [baseURL URLByAppendingPathComponent:filename];
NSURLSessionTask *downloadTask = [session downloadTaskWithURL:url];
[downloadTask resume];
}
}
- (NSURLSession *)backgroundSession {
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:kBackgroundId];
session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
});
return session;
}
#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *finalPath = [documentsPath stringByAppendingPathComponent:[[[downloadTask originalRequest] URL] lastPathComponent]];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL success;
NSError *error;
if ([fileManager fileExistsAtPath:finalPath]) {
success = [fileManager removeItemAtPath:finalPath error:&error];
NSAssert(success, #"removeItemAtPath error: %#", error);
}
success = [fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:finalPath] error:&error];
NSAssert(success, #"moveItemAtURL error: %#", error);
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
// Update your UI if you want to
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
// Update your UI if you want to
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error)
NSLog(#"%s: %#", __FUNCTION__, error);
}
#pragma mark - NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
NSLog(#"%s: %#", __FUNCTION__, error);
}
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
AppDelegate *appDelegate = (id)[[UIApplication sharedApplication] delegate];
if (appDelegate.backgroundSessionCompletionHandler) {
dispatch_async(dispatch_get_main_queue(), ^{
appDelegate.backgroundSessionCompletionHandler();
appDelegate.backgroundSessionCompletionHandler = nil;
});
}
}
By the way, this assumes your app delegate has a backgroundSessionCompletionHandler property:
#property (copy) void (^backgroundSessionCompletionHandler)();
And that the app delegate will set that property if the app was awaken to handle URLSession events:
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
self.backgroundSessionCompletionHandler = completionHandler;
}
For an Apple demonstration of the background NSURLSession see the Simple Background Transfer sample.
If all of the PDFs are coming from a server you control then one option would be to have a single request pass a list of files you want (as query parameters on the URL). Then your server could zip up the requested files into a single file.
This would cut down on the number of individual network requests you need to make. Of course you need to update your server to handle such a request and your app needs to unzip the returned file. But this is much more efficient than making lots of individual network requests.
Use an NSOperationQueue and make each download a separate NSOperation. Set the maximum concurrent operations property on your queue to however many downloads you want to be able to run simultaneously. I'd keep it in the 4-6 range personally.
Here's a good blog post that explains how to make concurrent operations.
http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/
What came as a big surprise is how slow dataWithContentsOfURL is when downloading multiple files!
To see it by yourself run the following example:
(you don't need the downloadQueue for downloadTaskWithURL, its there just for easier comparison)
- (IBAction)downloadUrls:(id)sender {
[[NSOperationQueue new] addOperationWithBlock:^{
[self download:true];
[self download:false];
}];
}
-(void) download:(BOOL) slow
{
double startTime = CACurrentMediaTime();
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
static NSURLSession* urlSession;
if(urlSession == nil)
urlSession = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
dispatch_group_t syncGroup = dispatch_group_create();
NSOperationQueue* downloadQueue = [NSOperationQueue new];
downloadQueue.maxConcurrentOperationCount = 10;
NSString* baseUrl = #"https://via.placeholder.com/468x60?text=";
for(int i = 0;i < 100;i++) {
NSString* urlString = [baseUrl stringByAppendingFormat:#"image%d", i];
dispatch_group_enter(syncGroup);
NSURL *url = [NSURL URLWithString:urlString];
[downloadQueue addOperationWithBlock:^{
if(slow) {
NSData *urlData = [NSData dataWithContentsOfURL:url];
dispatch_group_leave(syncGroup);
//NSLog(#"downloaded: %#", urlString);
}
else {
NSURLSessionDownloadTask* task = [urlSession downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//NSLog(#"downloaded: %#", urlString);
dispatch_group_leave(syncGroup);
}];[task resume];
}
}];
}
dispatch_group_wait(syncGroup, DISPATCH_TIME_FOREVER);
double endTime = CACurrentMediaTime();
NSLog(#"Download time:%.2f", (endTime - startTime));
}
There is nothing to "build". Just loop through the next 10 files each time in 10 threads and get the next file when a thread finishes.
My problem I'm pretty positive is simple, I must just be missing something.. just not sure what.
I can send GET and POST for granular elements (this=that kind of stuff), but a web service call I need to send data too, takes a raw JSON block, with no "key"
Heres the method I wrote:
-(NSData *)execute {
// Smart Chooser ?
if(PostData.count >0 || Payload != nil)
[self setMethod:UPLINK_METHOD_POST];
else
[self setMethod:UPLINK_METHOD_GET];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.connectionUrl
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:10];
if([UPLINK_METHOD_GET isEqualToString:self.connectionMethod])
[request setHTTPMethod:#"GET"];
else
[request setHTTPMethod:#"POST"];
NSString *gData = [self compileGetData];
NSString *pData = [self compilePostData];
// if we have get data, set it into the URL string
if(GetData.count > 0) {
[self setURLWithString:[[self.connectionUrl absoluteString] stringByAppendingString:[#"?" stringByAppendingString:gData]]];
[request setURL:self.connectionUrl];
}
// if we have post data, set it in the body
if(PostData.count > 0) {
const char *bytes = [[NSString stringWithString:pData] UTF8String];
[request setHTTPBody:[NSData dataWithBytes:bytes length:strlen(bytes)]];
}
// Override any post data if a payload is already defined.
if(Payload != nil) {
[request setHTTPBody:[Payload dataUsingEncoding:NSUTF8StringEncoding]];
}
NSLog(#"URL : %#", request.URL);
NSURLResponse *response;
NSError *err;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&err];
if(err != nil)
NSLog(#"here was an error: %#", err);
return responseData;
}
-(NSDictionary *)executeAsJSON
{
NSData *responseData = [self execute];
NSError *e;
return [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:&e];
}
Ok SO, the way this thing works, is that it automatically sets whether the request is POST or GET depending on the data provided in the GetData, PostData, and Payload vars.
The request is GET by default, but turns into POST if PostData or Payload have anything in them.
The compileGetData and compilePostData mostly just bring back formatted strings with arrays of information combined, nothing special there.
But thats not where the problem is.
See, "Payload" overrides anything "PostData" had in it. If you had provided PostData elements into the class, it would just be overridden by a provided Payload if that does exist.
I needed to provide this to demonstrate the "workarea" as it exists right now, its not linearly provided information.
This is the area of interest:
// Override any post data if a payload is already defined.
if(Payload != nil) {
//const char *plbytes = [[NSString stringWithString:Payload] UTF8String]; // this didn't work
[request setHTTPBody:[Payload dataUsingEncoding:NSUTF8StringEncoding]]; // inline, doesn't work either
}
When I say "doesnt work", what I mean is, im getting back an error JSON array from the webservice that basically means "hey, wheres the payload?". If the request is not POST it comes back as a general error, so thats all working, the URL is then obviously correct.
I've used RESTConsole for Chrome to test the webservice to make sure its working properly, and it does.
I've also checked through the debugger the exact payload im sending, i copy+pasted that into RESTConsole, and it works there.
I'm.. honestly at a loss here...
Try using a web proxy like Charles or Wireshark (I personally preferr Charles due to it's ease of use, it's a 30-day trial though) and monitor the request you make from RESTConsole and the one you make from your app and see if they look the same.
Check any headers, line returns and anything else that looks different.
That's the best I can think of to start with