I have a set of array which I need to loop and identify each the url domain is passed 200 status code. IF the first index value is pass then I shall break the loop and save the url in local. here is my code:
for (int j = 0; j < items.count; j++){
NSString *urlStr2 = items[j];
[session GET:urlStr2 parameters:nil progress:^(NSProgress * _Nonnull uploadProgress) {
// nil
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
// The domain name request is successful, processing data
NSURL *url = [NSURL URLWithString:urlStr2];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection cancelPreviousPerformRequestsWithTarget:self];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
if ([httpResponse statusCode] == 200){
h5UrlStr = urlStr2;
break;
}
}];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
// Domain name request failed
if (error.code == -1003) {
}
}];
}
But the problem is when I place the break when the condition is meet, it will appear error saying that break statement not in loop or switch Objective C. How should I break the loop?
In this case break will not help you because you are calling it in lambda (anonymous function). It is completely different scope. As I can see in your code you are launching up to items.count parallel requests but you need results of only the first successful.
One possible solution is to assign names to callback functions, get rid of loop and make the same GET call with advanced element counter in case of each failure while j < items.count. In such case all request will be sequential, one after another until some of them succeed. It can take a time.
Another solution is to launch requests in parallel (probably not for all elements of items at once, but for some chunk) and on each successful call check if result was already obtained. If it was, simply ignore current and return. This is faster but wastes bandwidth and computing resources.
Related
My Problem :- NSURLSession does not release previous call API Memory of 5MB Chunk
I am calling APIs in do while loop to upload 500MB video. I have to send every 5MB chunk with different APIs not in one API.
For Example 500MB Video and create 100 chunks and send using NSURLSession so calls 100 times but NSURLSession does not release previous call API Memory of 5MB Chunk
(1) I have created 5MB Chunk.
(2) read File using NSFileHandle with 5MB Chunk using OffSet
(3) change URL for all chunk and call api (necessary to send all chunk at different URL)
I do not want convert video in NSData in (500MB) i want to send chunk through API
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
//dispatch_group_t group = dispatch_group_create();
__block NSUInteger counterFailure = 0; // PSM Anks calling blob by url fails 4 time, exit for funtion
arrBlobIds = [[NSMutableArray alloc]init];
__block NSInteger intBlockIdCount = 100000; // PSM Anks blobid to assign id to every blob
__block NSUInteger offset = 0; // PSM Anks offset to start posution to read data
NSUInteger length = [[[NSFileManager defaultManager] attributesOfItemAtPath:[urlOfGallaryVideo path] error:nil] fileSize]; // PSM anks total lenght of media
NSUInteger intChunkSize = (5000 * 1024); // PSM anks chunk size
while (offset < length){
//dispatch_group_enter(group);
NSLog(#"offset 1 : %lu",(unsigned long)offset);
// PSM Anks Creat Chunk from file according to length
NSUInteger intThisChunkSize = length - offset > intChunkSize ? intChunkSize : length - offset;
//NSData* chunk = [NSData dataWithBytesNoCopy:(char *)[myBlob bytes] + offset length:intThisChunkSize freeWhenDone:NO];
__block NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:[urlOfGallaryVideo path]];
[fileHandle seekToFileOffset:offset];
__block NSData *dataChunk = [fileHandle readDataOfLength:intThisChunkSize];
// PSM Anks Convert block id in Base 64 encode
NSData *dataBlockId = [[NSString stringWithFormat:#"%ld",intBlockIdCount] dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64BlockId = [dataBlockId base64EncodedStringWithOptions:0];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#&comp=block&blockid=%#",[dictAzureSAS objectForKey:#"uri"],base64BlockId]]];
[request setHTTPMethod:#"PUT"];
[request setHTTPBody:dataChunk];
//[request setValue:[NSString stringWithFormat:#"%lu",(unsigned long)dataChunk.length] forHTTPHeaderField:#"Content-Length"]; // Do not need
//[request setValue:strVideoMIMEType forHTTPHeaderField:#"Content-Type"]; // Do not need
[request addValue:#"BlockBlob" forHTTPHeaderField:#"x-ms-blob-type"];
//NSLog(#"request : %#",request);
//NSLog(#"dataChunk.length : %lu \n url for blob %# \n request %#",(unsigned long)dataChunk.length,[NSURL URLWithString:[NSString stringWithFormat:#"%#&comp=block&blockid=%#",[dictAzureSAS objectForKey:#"uri"],base64BlockId]],request);
NSLog(#"dataChunk.length : %lu",(unsigned long)dataChunk.length);
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.URLCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
config.timeoutIntervalForRequest = 20.0;
config.URLCredentialStorage = nil;
config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
///NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
config = nil;
//NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *dataTaskForUpload = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Finished with status code: %li", (long)[(NSHTTPURLResponse *)response statusCode]);
NSLog(#"response: %#", response);
NSLog(#"error: %# %#", error,error.description);
if(data != nil) // PSM anks Check Data is nil otherwise app crashed
{
NSMutableArray *jsonList = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSLog(#"jsonList: %#", jsonList);
}
dataChunk = nil;
fileHandle = nil;
if(error == nil)
{
/*
// PSM Anks First Add Then increment
[arrBlobIds addObject:base64BlockId];
intBlockIdCount++;
offset += intThisChunkSize;
counterFailure = 0;
*/
}
else
{
/*
counterFailure++;
offset = intThisChunkSize;
if(counterFailure >= 4)
{
NSLog(#"Enter counter Failure %lu",(unsigned long)counterFailure);
counterFailure = 0;
[self stopLoader];
[CommonAlertViewMsgs cannotConnectServer:self];
return ;
}
*/
}
//dispatch_group_leave(group);
dispatch_semaphore_signal(semaphore);
[session finishTasksAndInvalidate];
}];
[dataTaskForUpload resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(#"offset 2 : %lu",(unsigned long)offset);
}
Your problem is probably that your NSData objects are being put in an autorelease pool, which is never getting drained until after your main dispatch_async block completes. You can probably fix the immediate problem by adding an #autoreleasepool to your while loop; i.e.
while (offset < length) #autoreleasepool {
However, your dispatch_semaphore_wait at the end is blocking a dispatch queue, which is generally discouraged. What I would recommend would be, in addition to adding the #autoreleaspool to the while loop, to use a dispatch group instead of the semaphore, and to use dispatch_group_notify at the end instead of dispatch_group_wait. This will cause your main dispatch_async block to complete, which will release any autoreleased objects which have accumulated in it, and then the block you pass to dispatch_group_notify will be called once all your operations are complete.
EDIT: Knowing a little more about what you're trying to do, here is an alternative that will run the processes one at a time, while still not blocking the dispatch queue:
(pseudocode)
- (void)sendRequestWithOffset:length:otherParameters: {
send the url request {
do what you do
if newOffset < length {
[self sendRequestWithOffset:newOffset length:length otherParameters:whatever];
} else {
hooray, we're done
}
}
}
It's sort of like a recursive call (but not really, since we won't accumulate stack frames). Basically it's an asyncronous version of your while loop; your tasks occur one at a time, there's no blockage of dispatch queues, and since each dispatch queue has its own autorelease pool, you won't get buildup of autoreleased objects either and your memory usage should stay reasonable.
If you want to minimize your memory footprint while the uploads run, you should:
Try #autoreleasepool as advised by Charles;
Reuse one NSURLSession rather than recreating a new NSURLSession each time inside your loop; and
Use file-based upload tasks (e.g. uploadTaskWithRequest:fromFile:) rather than dataTask.
Note, with file-based upload tasks, you can still configure your NSURLRequest like you are now, but don't set httpBody, but instead supply that as the fromFile parameter of the above method.
Once you have this memory issue behind you, you can pursue other approaches to improve performance, too, but let's not get ahead of ourselves.
I have a networking class called: ITunesAlbumDataDownloader
#implementation AlbumDataDownloader
- (void)downloadDataWithURLString:(NSString *)urlString
completionHandler:(void (^)(NSArray *, NSError *))completionHandler
{
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
NSArray *albumsArray = [self parseJSONData:data];
completionHandler(albumsArray, error);
}];
[dataTask resume];
}
- (NSArray *)parseJSONData:(NSData *)data {
NSMutableArray *albums = [[NSMutableArray alloc] init];
...
...
// Return the array
return [NSArray arrayWithArray:albums];
}
#end
and i need to create a Unit Test for this which does the following:
The NSURLSession dataTaskWithRequest:completionHandler: response is mocked to contain the fake JSON data i have:
// Expected JSON response
NSData *jsonResponse = [self sampleJSONData];
The returned array from the public method downloadDataWithURLString:completionHandler: response should contain all the albums and nil error.
Other points to bare in mind is that i need to mock NSURLSession with the fake JSON data "jsonResponse" to the downloadDataWithURLString:completionHandler: method WITHOUT invoking an actual network request.
I have tried various different things but i just can not work it out, i think its the combination of faking the request and the blocks which is really confusing me.
Here is two examples of my test method that i tried (i actually tried a lot of other ways also but this is what i have remaining right now):
- (void)testValidJSONResponseGivesAlbumsAndNilError {
// Given a valid JSON response containing albums and an AlbumDataDownloaderTests instance
// Expected JSON response
NSData *jsonResponse = [self sampleJSONDataWithAlbums];
id myMock = [OCMockObject mockForClass:[NSURLSession class]];
[[myMock expect] dataTaskWithRequest:OCMOCK_ANY
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error)
{
}];
[myMock verify];
}
and
- (void)testValidJSONResponseGivesAlbumsAndNilError {
// Given a valid JSON response containing albums and an AlbumDataDownloaderTests instance
// Expected JSON response
NSData *jsonResponse = [self sampleJSONDataWithAlbums];
id myMock = [OCMockObject mockForClass:[AlbumDataDownloader class]];
[[[myMock stub] andReturn:jsonResponse] downloadDataWithURLString:OCMOCK_ANY
completionHandler:^(NSArray *response, NSError *error)
{
}];
[myMock verify];
}
}
I have a feeling that in both instances I'm probably way off the mark :(
I would really appreciate some help with this.
Thanks.
UPDATE 1:
Here is what i have now come up with but need to know if I'm on the right track or still making a mistake?
id mockSession = [OCMockObject mockForClass:[NSURLSession class]];
id mockDataTask = [OCMockObject mockForClass:[NSURLSessionDataTask class]];
[[mockSession stub] dataTaskWithRequest:OCMOCK_ANY
completionHandler:^(NSData _Nullable data, NSURLResponse Nullable response, NSError * Nullable error)
{
NSLog(#"Response: %#", response);
}];
[[mockDataTask stub] andDo:^(NSInvocation *invocation)
{
NSLog(#"invocation: %#", invocation);
}];
The trick with blocks is you need the test to call the block, with whatever arguments the test wants.
In OCMock, this can be done like this:
OCMStub([mock someMethodWithBlock:([OCMArg invokeBlockWithArgs:#"First arg", nil])]);
This is convenient. But…
The downside is that the block will be invoked immediately when someMethodWithBlock: is called. This often doesn't reflect the timing of production code.
If you want to defer calling the block until after the invoking method completes, then capture it. In OCMock, this can be done like this:
__block void (^capturedBlock)(id arg1);
OCMStub([mock someMethodWithBlock:[OCMArg checkWithBlock:^BOOL(id obj) {
capturedBlock = obj;
return YES;
}]]);
// ...Invoke the method that calls someMethodWithBlock:, then...
capturedBlock(#"First arg"); // Call the block with whatever you need
I prefer to use OCHamcrest's HCArgumentCaptor. OCMock supports OCHamcrest matchers, so I believe this should work:
HCArgumentCaptor *argument = [[HCArgumentCaptor alloc] init];
OCMStub([mock someMethodWithBlock:argument]);
// ...Invoke the method that calls someMethodWithBlock:, then...
void (^capturedBlock)(id arg1) = argument.value; // Cast generic captured argument to specific type
capturedBlock(#"First arg"); // Call the block with whatever you need
I'm making a URLRequest that sends attempted login information and waits for a response from the web service to find out if the user can/cannot log in.
The way I was hoping to do this was by having the user type in his username & password into two text fields and then press a button, which would call the function below. This function would start an NSURLSessionDataTask and construct a struct with the boolean success/failure of the login and an NSString with the corresponding error message (if any).
The problem is that my function returns the struct before my NSURLSessionDataTask's completion block has finished executing. Is there a way for me to force my program to wait until this task either times out or completes? Alternatively, can I push execution of the completion block onto the main thread & before the function returns?
Thanks! Please let me know if there are any clarifications I need to make!
(Also, I have seen some similar questions circulating around StackOverflow that mention GCD. Is this an overkill solution? None of those questions seem to be talking about quite the same thing, or do so on a level that is higher than my current understanding. I am still very new to Objective-C)
- (struct RequestReport) sendLoginRequest: (NSString*) username withPassword: (NSString *) password
... (creating the request & setting HTTP body)
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error){
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData: data options:0 error:nil];
success = (BOOL)jsonObject[#"success"];
statusText = (NSString *) jsonObject[#"errors"];
}];
[dataTask resume];
struct RequestReport rr;
rr.status = statusText;
rr.success = success;
return rr;
Your method should look like this:
- (void) sendLoginRequest:(NSString*) username withPassword:(NSString *) password callback:(void (^)(NSError *error, BOOL success))callback
{
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error){
if (error) {
// Handle error
}
else {
callback(error, YES);
}
}];
[dataTask resume];
}
Call this method like so:
[self sendLoginRequest:#"myUsername" password:#"password" callback:^(NSString *error, BOOL success) {
if (success) {
NSLog(#"My response back from the server after an unknown amount of time";
}
}
See Apple's Programming with Objective-C for more reading on blocks and fuckingblocksyntax.com for how to declare blocks.
I am a little bit confused about objective c programming with blocks.
for example Here is a method:
in the .h
- (void)downloadDataWithURLString:(NSString *)urlString
completionHandler:(void(^) (NSArray * response, NSError *error))completionHandler;
in the .m:
- (void)downloadedDataURLString:(NSString *)urlString
completionHandler:(void (^)(NSArray *, NSError *))completionHandler {
// some things get done here. But what!?
}
My main questions is.... how do I implement this completion handler? What variables would be returned with the array and error? it is one area for the code but how do I tell it what to do when it is completed?
It's up to the caller to supply code to be run by the method (the body of the block). It's up to the implementor to invoke that code.
To start with a simple example, say the caller just wanted you to form an array with the urlString and call back, then you would do this:
- (void)downloadedDataURLString:(NSString *)urlString
completionHandler:(void (^)(NSArray *, NSError *))completionHandler {
NSArray *callBackWithThis = #[urlString, #"Look ma, no hands"];
completionHandler(callBackWithThis, nil);
}
The caller would do this:
- (void)someMethodInTheSameClass {
// make an array
[self downloadedDataURLString:#"put me in an array"
completionHandler:^(NSArray *array, NSError *error) {
NSLog(#"called back with %#", array);
}];
}
The caller will log a two item array with #"put me in an array" and #"Look ma, no hands". In a more realistic example, say somebody asked you to call them back when you're finished downloading something:
- (void)downloadedDataURLString:(NSString *)urlString
completionHandler:(void (^)(NSArray *, NSError *))completionHandler {
// imagine your caller wants you to do a GET from a web api
// stripped down, that would look like this
// build a request
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// run it asynch
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
// imagine that the api answers a JSON array. parse it
NSError *parseError;
id parse = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&parseError];
// here's the part you care about: the completionHandler can be called like a function. the code the caller supplies will be run
if (!parseError) {
completionHandler(parse, nil);
} else {
NSLog(#"json parse error, error is %#", parseError);
completionHandler(nil, parseError);
}
} else {
NSLog(#"error making request %#", error);
completionHandler(nil, error);
}
}];
// remember, this launches the request and returns right away
// you are calling the block later, after the request has finished
}
While I can't be entirely sure without seeing any more details about the method, or its exact implementation I suspect this: this methods creates a new background thread, retrieves the data from the server and converts the JSON/XML to an NSArray response. If an error occurred, the error object contains a pointer to the NSError. After doing that, the completion handler is called on the main thread. The completion handler is the block in which you can specify which code should be executed after attempting to retrieve the data.
Here is some sample code on how to call this method to get you started:
[self downloadDataWithURLString:#"http://www.google.com"
completionHandler:^(NSArray *response, NSError *error) {
if (! error) {
// Do something awesome with the 'response' array
} else {
NSLog(#"An error occured while downloading data: %#", error);
}
}];
I am implementing an HTTP request using Objective-C using AFJSONRequestOperation for a mobile app and I do not know how to implement a loop until a condition is satisfied (i.e. the profiling_status key in JSON has the value 1). The request is run when a button is pressed. In the background the server does some calculations that take a while. Until the server finishes, the profiling_status value is 2. When it finishes the value is 1. So, I would like to stay in a loop until the value changes to 1 and then display the JSON.
Returning the JSON in the success block gives a pointer error.. and returning the JSON at the end of the method will return nil.
I have this code:
- (IBAction)getProfileInfo:(id)sender
{
profiling_status = 2;
NSDictionary *JSON;
while (profiling_status == 2){
JSON = [self getJSON];
profiling_status = [JSON objectForKey:#"profiling_status"];
}
NSLog(#"JSON: %#", JSON);
}
- (NSDictionary*)getJSON
{
__block NSDictionary* JSONResult = nil;
MyAPIClient *client = [MyAPIClient sharedClient];
NSString *path = [NSString stringWithFormat:#"/profile?json"];
NSURLRequest *request = [client requestWithMethod:#"GET" path:path parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
JSONResult = JSON;
//can’t do this ---- return JSONResult;
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"%#", [error userInfo]);
}];
[operation start];
return JSONResult; //will return nil
}
Any help?
Thank you.
You can't use a while loop for this (and shouldn't anyway as it will just kill the app by spawning connections). Instead you need to structure your methods with blocks so that the block running the request checks the result and either recursively calls the check method (preferably after a short delay) or calls a completion block.
Also think about keeping a count of the number of iterations or the time taken so you can abort the processing.