How to track usage of data sent and received through my app?
I just want to record bytes sent and received when my app is running. If I can get separate info for Wifi and Cellular network then it would be great but its not a priority.
I know how to find total usage of device - https://stackoverflow.com/a/8014012/427969
Also, I know I can use Instruments to collect network activity data but I want to record this data in my app so need a programmatic way to do this.
I tried to search this but all I find is device's network usage and not a particular app's usage.
The following is the screenshot of Whatsapp's Settings -> Usage page, which would give a better idea of what I am trying to do:
I am using AFNetworking for HTTP request and response as follows:
NSData* requestData = [NSJSONSerialization dataWithJSONObject:info options: NSJSONWritingPrettyPrinted error:&error];
if(error != nil) {
NSLog(#"Error: converting JSON: %#, %#", error, error.userInfo);
}
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded; charset=UTF-8" forHTTPHeaderField:#"Content-Type"];
[request setValue:[NSString stringWithFormat:#"%d", [requestData length]] forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody: requestData];
/*
################### ----------------- ###################
WILL [requestData length] BE THE NUMBER OF BYTES SEND ???
################### ----------------- ###################
*/
NSLog(#"data bytes: %d", [requestData length]);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL .....];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest: request
success: ^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
} failure: ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
}];
[operation start];
I have updated my question.
Can somebody please answer: Is [requestData length] the number of bytes SEND for one request?
There are several Methods depending on which class you are using to Download with AFNetworking
AFHTTPRequestOperation for example has the following method:
setDownloadProgressBlock:^(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead)
For the other way around, there is a method like this:
setUploadProgressBlock:^(NSInteger bytesWritten,long long totalBytesWritten,long long totalBytesExpectedToWrite)
With this to methods you should try to keep track of all your uploaded and downloaded data.
AFJSONRequestOperation is a subclass of AFHTTPRequestOperation, so those method should work in either class.
And please be aware that only sending a "json request" to a webserver doesn't mean that you are not downloading. For sure you have to get the content - the json - which would be your data downloaded.
Further you qre questioning if [requestData length]is telling you the proper size sent to the server. That's not the exact size, because within your requestData you do not have the additional Headers for the HTTP request, therefore your size will be a little bit smaller than the original bytes sent.
Every time one of the methods above is executed you should add the result of bytes read and add it to the bytes you read before this method was executed. For sure you have to save the current bytesRead permanently in coredata for example or any other persistence store.
I used the methods mentioned by Alexander as follows:
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest: request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// success
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// failure
}];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
// bytes received - saved bytesRead variable to NSUserDefaults
}];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
// bytes sent - saved bytesWritten variable to NSUserDefaults
}];
[operation start];
Related
I am building an app that downloads a large number of images, sometimes 1500-5000 images depending on what the user requests. To do this, I am using AFNetworking 2. At first, I was just looping through all of my URLs and then making a request for each one.
for (NSString *url in urls) {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
requestOperation.responseSerializer = [AFImageResponseSerializer serializer];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
completion(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
}];
[requestOperation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
double percentDone = (double)totalBytesRead / (double)totalBytesExpectedToRead;
progress(percentDone);
}];
[requestOperation start];
}
But, after I got to about 900 downloads/requests I would start to get the following error:
The request timed out
I am assuming this error came directly from AFNetworking.
What is the best and most efficient way to make a large number of download requests like this without timing out? Should I be using dispatch_group to batch the requests as outlined here?
Or, should I use a recursive method that will download one image at a time, and only start the next request once the first one finishes?
Try this
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setTimeoutInterval:600]; /* 10 minutes */
But it would be best solution if you just download an archive of images and then unpack it.
I'm looping through an array which contains few strings and making a request to a web server for each
strings in the array.
I would like each request to be processed completely before the subsequent request is sent to the server. Because each request sends me a response which I will send with next request and so on.
The problem I am having is that my NSURLConnection is set up using the standard asynchronous call. This results in requests not blocking any subsequent requests. But I need to block other requests in the loop before first completes.
The request URL is same always , only JSON data changes with every request in the loop.
Here is my code
for (int i = 0; i < array.count; i++)
{
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:finalJSON options:NSJSONWritingPrettyPrinted error:&error];
if (!jsonData) {
NSLog(#"Error creating JSON object: %#", [error localizedDescription]);
}
NSString *url = [NSString stringWithFormat:#“abc.com/folders”];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[request setValue:#"application/json;charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setValue:APIKEY forHTTPHeaderField:#"X_API_KEY"];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:jsonData];
//I am adding all connections to NSDictionary so that later I can process request.
NSURLConnection *connection = [self connectionForRequest:request];
[connection start];
}
I thought of 2 solutions for your problem:
AFNetworking - U can use AFNetworking and maintain a counter in the success block. The counter will count the requests and when all done, will do your next task.
GCD - Dispatch Groups - Grand Central Dispatch provide u the option to make group or requests and do something at the end (when all the requests finished). For that, u need to read nice tutorial (2nd part of "Ray Wenderlich". If U r not familiar with GCD, jump to the tutorial 1st part).
Anyway, With your code above U can't achieve your task. U don't have any async block which run at the end of the requests.
Edit:
Use AFNetworking:
U must remove your for loop first, and then do like this:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = #{#"foo": #"bar"};
[manager POST:#"http://example.com/resources.json" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) { // HERE u can do your second request which uses the first response
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters_new = <USE_YOUR_DATA_FROM_responseObject>;
[manager POST:#"http://example.com/resources.json" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) { // HERE u can do your third request which uses the first and second response
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
A simple way to do this is with recursion. Create a method that sends the url connection, and, once the connection is complete, calls itself to send it again.
Here's the key to the solution: make a method that can be called recursively, which sends requests and collects results. By calling itself recursively in the completion block, this method sees to it that each request starts after the previous one finishes...
// note - edited per the comments to get a new NSURLRequest each time
- (void)makeRequests:(NSInteger)count
results:(NSMutableArray *)results
completion:(void (^)(NSError *))completion {
// complete recursion
if (count == 0) return completion(nil);
NSURLRequest *request = [self nextRequest];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
[results addObject:data];
[self makeRequests:count-1 results:results completion:completion];
} else {
completion(error);
}
}];
}
To call it, allocate an array that will carry the results...
- (void)makeManyRequests {
NSMutableArray *resultsArray = [NSMutableArray array];
[self makeRequests:10 results:resultsArray completion:^(NSError *error) {
NSLog(#"done. results are %#", resultsArray);
}];
}
EDIT - Its unclear in the OP how this request changes each time, but it sounds like you have that figured out. This is just your originally posted code in its own method. Its a good idea to factor this out so your code can be clear on how it forms a different JSON payload each time...
- (NSURLRequest *)nextRequest {
id finalJSON = // your app supplies...
// somehow, this changes each time nextRequest is called
NSString *APIKEY = // your app supplies
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:finalJSON options:NSJSONWritingPrettyPrinted error:&error];
if (!jsonData) {
NSLog(#"Error creating JSON object: %#", [error localizedDescription]);
}
// your request creation code, copied from the OP
NSString *url = [NSString stringWithFormat:#"abc.com/folders"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[request setValue:#"application/json;charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setValue:APIKEY forHTTPHeaderField:#"X_API_KEY"];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:jsonData];
return request;
}
I am using AFNetwoking v 2.2.4 as a networking library on iOS. It assumes non http 2xx (http 200 as success ) response as error and throws on error block like this:
NSError *error= nil;
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:user options:NSJSONWritingPrettyPrinted error:&error];
NSString *connectionString = [NSString stringWithFormat:#"%#%#",SERVER_URL, RESOURCE_URL_USER_SIGNUP];
NSURL *url = [NSURL URLWithString:connectionString];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[urlRequest setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:jsonData];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"response object: %#", (NSDictionary *)responseObject);
[delegate userSignUpCompletedWithResponse:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
[delegate userSignUpFailedWithError:error];
NSString *errorBodyMessage = [[error userInfo] objectForKey:#"NSLocalizedRecoverySuggestion"];
NSLog(#"error message: %#", errorBodyMessage);
}];
[operation start];
Here, everything is working as expected when http response is 200. I have a REST API on server side which is written in ROR. For the user sign up, if all fields have valid values then it sends 200 status, but if data are invalid, such as email already taken; In this case server sends 422 status with error message in JSON. I need to trace error message and show it to mobile user but I am unable to get the message object in error block. I have logged entire error but did not find the object returned form the server. How can I get the JSON object while there is non 2xx http response using AFNetworking?
Access the responseObject property of the operation passed into the failure block.
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"JSON: %#", operation.responseObject);
}];
I'm impementing an application in iOS7, it's kind of a social network app with posts with images and a backend that saves all of the data sent form the client. The iOS client is sending the information of the post via json and after the info is sent, it starts to send the image via multipart form using AFNetworking.
I need to be notified when the image is sent, so that I can refresh the main view of the app with the new posts, including the recently posted by the client. In the practice if I request the backend for the last posts and the multipart hasn't finished, the sending of the image gets interruped and fails to send the image.
The backend is develop in WCF and is a RESTful JSON web service.
Here is the method that sends the post to the backend:
+(void)addPostToServerAddtext:(NSString *)text addimage:(UIImage *)image addbeach:(NSString *)beach location:(NSString*)location;
{
NSLog(#"entro a addPost");
NSString *urlBackend = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"URLBackend"];
NSData* dataImage = UIImageJPEGRepresentation(image, 1.0);
NSString* ImageName = [NSString stringWithFormat:#"%#_%#.jpg",idUser ,dateToServer];
NSString *jsonRequest = [NSString stringWithFormat:#"{\"Date\":\"%#\"...."];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#newPost",urlBackend]];
NSMutableURLRequest *request = [ [NSMutableURLRequest alloc] initWithURL:url];
NSData *requestData = [NSData dataWithBytes:[jsonRequest UTF8String] length:[jsonRequest length]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setValue:[NSString stringWithFormat:#"%d", [requestData length]] forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:requestData];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
if (image != nil) {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager POST:[NSString stringWithFormat:#"%#FileUpload",urlBackend]
parameters:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:dataImage name:#"image" fileName:ImageName mimeType:#"image/jpg" ];
}
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success: %#", responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
}
A couple of thoughts:
You say:
The iOS client is sending the information of the post via json and after the info is sent, it starts to send the image via multipart form using AFNetworking.
Technically, you're not waiting for the information to be sent, but you're doing these concurrently. Do you want these to be concurrent? Or sequential? Or why not just a single request that posts the information as well as the image?
I'd suggest using AFNetworking for both requests. You've got a powerful framework for managing network requests, and it feels awkward to see hairy NSURLConnection code in there.
If you keep the NSURLConnection code in there, note that you do not want to start a NSURLConnection, unless you used initWithRequest:delegate:startImmediately: with NO for that last parameter. You're effectively starting it twice, which can cause problems. I'd suggest removing the start call.
Setting all of that aside, what you want to do is to add a completion block parameter to your method, e.g., something like:
+ (void)addPostToServerAddtext:(NSString *)text addimage:(UIImage *)image addbeach:(NSString *)beach location:(NSString*)location completion:(void (^)(id responseObject, NSError *error))completion
{
// ...
if (image != nil) {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager POST:[NSString stringWithFormat:#"%#FileUpload",urlBackend] parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:dataImage name:#"image" fileName:ImageName mimeType:#"image/jpg" ];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (completion) completion(responseObject, nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completion) completion(nil, error);
}];
}
}
You'd then invoke that like so:
[Persistence addPostToServerAddtext:text addimage:image addbeach:nil location:annotation completion:^(id responseObject, NSError *error) {
if (error) {
// handle error
return
}
// otherwise use the responseObject
}];
Now, I don't know what parameters you want to return in your completion block (I'm assuming you wanted to return what the AFHTTPRequestOperationManager did), but just change the parameters for that completion block as suits your needs.
Unrelated to your original question, but I notice that you're building jsonRequest like so:
NSString *jsonRequest = [NSString stringWithFormat:#"{\"Date\":\"%#\"...."];
That's a little risky if any of those fields include user supplied information (e.g. what if the user used double quotes in the information provided). I'd suggest you build a dictionary, and then build the jsonRequest from that. It will be more robust. Thus:
NSDictionary *dictionary = #{#"Date" : date,
#"Message" : message};
NSError *error = nil;
NSData *request = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&error];
if (error)
NSLog(#"%s: dataWithJSONObject error: %#", __FUNCTION__, error);
Or, if you use AFNetworking, I believe it will do this JSON conversion of your dictionary for you. But, bottom line, be very wary about creating JSON strings yourself, at least if the request might include any user supplied information.
I need to check the size of file from a URL. I get the file size perfectly when downloading file with AFNetworking.
AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request
success:^(AFHTTPRequestOperation *operation, id responseObject) {
// Success Callback
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Failure Callback
}];
and get file size in another block
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
}];
But i need to check file size before initiating download request, so that i can prompt to user. I have also tried another method
NSURL *url = [NSURL URLWithString:#"http://lasp.colorado.edu/home/wp-content/uploads/2011/03/suncombo1.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSLog(#"original = %d", [data length]);
But it blocks the UI, coz it download all data to calculate its size.
Is there any way to check file size before downloading? Any help is appreciated.
If the server supports it you can make a request to just get the headers (HEAD, as opposed to GET) without the actual data payload and this should include the Content-Length.
If you can't do that then you need to start the download and use expectedContentLength of the NSURLResponse.
Basically, create an instance of NSMutableURLRequest and call setHTTPMethod: with the method parameter set to #"HEAD" (to replace the default which is GET). Then send that to the server as you currently request for the full set of data (same URL).
here is the code:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:candidateURL];
[request setHTTPMethod:#"HEAD"];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"Content-lent: %lld", [operation.response expectedContentLength]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];