AFNetworking 1.x to 3.x migration for NSURLSessionTask? - objective-c

I have a old project which is not working in iOS 9. I've read official documentation on AFNetworking and completed most of the migration.
NetworkManager:
_requestManager = [[AFHTTPSessionManager alloc]initWithBaseURL:[NSURL URLWithString:baseURL ]];
//here we can set the request header as the access token once we have logged in.
AFHTTPRequestSerializer *requestSerializer = [AFHTTPRequestSerializer serializer];
AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer];
[_requestManager setRequestSerializer:requestSerializer];
[_requestManager setResponseSerializer:responseSerializer];
Earlier Version:
// 2. Create an `NSMutableURLRequest`.
NSMutableURLRequest *request =
[[NetworkManager sharedInstance].requestManager.requestSerializer multipartFormRequestWithMethod:#"POST" URLString:fullPath
parameters:nil
constructingBodyWithBlock: ^(id <AFMultipartFormData> formData) {
NSString *fileName = [NSString stringWithFormat:#"%#.caf", theAudioItem.media_item_name];
[formData appendPartWithFileData:audioData name:theAudioItem.media_item_name fileName:fileName mimeType:#"audio/caf"];
}];
// 3. Create and use `AFHTTPRequestOperationManager` to create an `AFHTTPRequestOperation` from the `NSMutableURLRequest` that we just created.
AFHTTPRequestOperation *operation =
[[NetworkManager sharedInstance].requestManager HTTPRequestOperationWithRequest:request
success: ^(AFHTTPRequestOperation *operation, id responseObject) {
result(YES, #"");
} failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
if (operation.responseData) {
NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:operation.responseData options:NSJSONReadingMutableContainers error:nil];
result(NO, [responseDict valueForKey:#"Message"]);
[self deleteTmpFilesFromParts:formParts];
} else {
result(NO, [NSString stringWithFormat:#"Failed to upload media to %#!", gallery.gallery_name]);
[self deleteTmpFilesFromParts:formParts];
}
result(NO, errorMessage);
}];
// 4. Set the progress block of the operation.
[operation setUploadProgressBlock: ^(NSUInteger __unused bytesWritten,
long long totalBytesWritten,
long long totalBytesExpectedToWrite) {
DLog(#"progress is %i %lld %lld", bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
progress((float)totalBytesWritten / (float)totalBytesExpectedToWrite);
}];
// 5. Begin!
[operation start];
Converted Response: ( updated with comments what gives errors )
//No visible #interface for 'AFHTTPRequestSerializer<AFURLRequestSerialization>' declares the selector 'multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:'
NSMutableURLRequest *request =
[[NetworkManager sharedInstance].requestManager.requestSerializer multipartFormRequestWithMethod:#"POST" URLString:fullPath parameters:nil
constructingBodyWithBlock: ^(id <AFMultipartFormData> formData) {
NSString *fileName = [NSString stringWithFormat:#"%#.caf", theAudioItem.media_item_name];
[formData appendPartWithFileData:audioData name:theAudioItem.media_item_name fileName:fileName mimeType:#"audio/caf"];
}];
// 3. Create and use `AFHTTPRequestOperationManager` to create an `AFHTTPRequestOperation` from the `NSMutableURLRequest` that we just created.
//No visible #interface for 'AFHTTPSessionManager' declares the selector 'HTTPRequestOperationWithRequest:success:failure:'
NSURLSessionTask *operation =
[[NetworkManager sharedInstance].requestManager HTTPRequestOperationWithRequest:request
success: ^(NSURLSessionTask *operation, id responseObject) {
result(YES, #"");
} failure: ^(NSURLSessionTask *operation, NSError *error) {
//Error for operation doesn't have responseData
if (operation.responseData) {
NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:operation.responseData options:NSJSONReadingMutableContainers error:nil];
result(NO, [responseDict valueForKey:#"Message"]);
[self deleteTmpFilesFromParts:formParts];
} else {
result(NO, [NSString stringWithFormat:#"Failed to upload media to %#!", gallery.gallery_name]);
[self deleteTmpFilesFromParts:formParts];
}
// get the response
result(NO, errorMessage);
}];
// 4. Set the progress block of the operation.
//No visible #interface for 'NSURLSessionTask' declares the selector 'setUploadProgressBlock:'
[operation setUploadProgressBlock: ^(NSUInteger __unused bytesWritten,
long long totalBytesWritten,
long long totalBytesExpectedToWrite) {
DLog(#"progress is %i %lld %lld", bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
progress((float)totalBytesWritten / (float)totalBytesExpectedToWrite);
}];
// 5. Begin!
//No visible #interface for 'NSURLSessionTask' declares the selector 'start'
[operation start];

The equivalent code for AFHTTPSessionManager is simply:
NSURLSessionTask *task = [[[NetworkManager sharedInstance] requestManager] POST:fullPath parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
NSString *fileName = [NSString stringWithFormat:#"%#.caf", theAudioItem.media_item_name];
[formData appendPartWithFileData:audioData name:theAudioItem.media_item_name fileName:fileName mimeType:#"audio/caf"];
} progress:^(NSProgress * _Nonnull uploadProgress) {
NSLog(#"%.3f", uploadProgress.fractionCompleted);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
// success
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
// failure
// if you need to process the `NSData` associated with this error (if any), you'd do:
NSData *data = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
if (data) { ... }
}];
This features two major changes from your v1.x code: First, with 2.x they introduced GET/POST/etc. methods that saved you from manually starting the operation (or adding it to your own queue). Second, with 3.x, they retired NSOperation framework of AFHTTPRequestOperationManager, and now use AFHTTPSessionManager class, where GET/POST/etc. don't return an NSOperation subclass, but rather a NSURLSessionTask reference.

Related

return data from AFHTTPRequestOperation?

I'm moving my app code to an MVC model and so I created a method to retrieve some data from an API.
+ (NSMutableArray *)loadFromFeed {
NSString *feed = #"https://api.test.com";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:feedUrl]];
request = [mutableRequest copy];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [JSONResponseSerializerWithData serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonArray = (NSArray *)[responseObject objectForKey:#"items"];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
CLS_LOG(#"Error");
}];
}
Now, ideally, I'd like to return jsonArray as part of this method. However, since AFHTTPRequestOperation is asynchronous, I don't know how to solve this and still be able to call [Data loadFromFeed]; anywhere in the app. How can I do this?
You could pass two block named success and failure to loadFromFeed ,
and then call the two block from your setCompletionBlockWithSuccess success and failure block, passing jsonArray to the success block:
typedef void (^Success)(id data);
typedef void (^Failure)(NSError *error);
- (void)loadFromFeed:(Success)success failure:(Failure)failure;
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonArray = (NSArray *)[responseObject objectForKey:#"items"];
success?success(jsonArray):nil;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure?failure(error):nil;
}];
then use in this way:
[Data loadFromFeed:^(id data) {
NSLog(#"%#",data)
} failure:^(NSError *error) {
NSLog(#"%#",error)
}];];

asynchronous function doesn't return data

I wrote a function to fetch an NSDictionary from a URL.
[Data loadFromAPI:#"http://example.com/api" withSuccess:^(id data) {
NSMutableDictionary *result = (NSDictionary *)data;
CLS_LOG(#"result: %#", result);
} failure:^(NSError *error) {
CLS_LOG(#"Error: %#", error);
}];
The function it calls is the one below:
typedef void (^Success)(id data);
typedef void (^Failure)(NSError *error);
+ (void)loadFromAPI:(NSString *)apiURL withSuccess:(Success)success failure:(Failure)failure {
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:apiURL]];
NSMutableURLRequest *mutableRequest = [request mutableCopy];
request = [mutableRequest copy];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [JSONResponseSerializerWithData serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonArray = (NSArray *)responseObject ;
NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys:
[self now], #"time",
jsonArray, #"response",
nil];
CLS_LOG(#"Result is: %#", result);
success(result);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure?failure(error):nil;
}];
[operationQueue setMaxConcurrentOperationCount:1];
[operationQueue addOperations:#[operation] waitUntilFinished:NO];
}
Inside the function CLS_LOG(#"Result is: %#", result); returns the data returned. However, where I call the function CLS_LOG(#"result: %#", result); returns null. What am I doing wrong?
As it turns out [self now] returned null, so the NSDictionary wasn't being set properly.
The problem is
NSMutableDictionary *result = (NSDictionary *)data;
you cant directly do that, i wonder why you dont have a warning for that..
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithDictionary:(NSDictionary *)data];
will probably solve the problem...

AFNetworking: success and failure blocks not called!! (POST to mysql backend)

I am still trying to get a hang of objective-c and AFNetworking. I am doing a POST (through UI button) on mysql backend which returns a json response. I can see the download progress, but my success and failure blocks are never called. Code snippet below. I only see 1 and 4 printed out.
NSMutableURLRequest *apiRequest = [self multipartFormRequestWithMethod:#"POST" path:kAPIPath parameters:params constructingBodyWithBlock:^(id <AFMultipartFormData>formData) {
NSLog(#"Success in calling");
}];
AFHTTPRequestOperation* operation = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
NSLog(#"1");
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//success!
NSLog(#"Response: %#", [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]);
NSLog(#"2");
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure :(
NSLog(#"3");
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
});
NSLog(#"4");
[operation start];
[operation setDownloadProgressBlock:^( NSUInteger bytesRead , long long totalBytesRead , long long totalBytesExpectedToRead )
{
NSLog(#"%lld of %lld", totalBytesRead, totalBytesExpectedToRead);
}
];

setDownloadProgressBlock in AFHTTPClient subclass

I created subclass of AFHTTPClient.
It works, but progress block return only final value (1.0). What should I change to get all the calculated values from setDownloadProgressBlock?
- (void)exploreVenuesCenter:(CLLocationCoordinate2D)coordinate inRadius:(int)radiusMeters success:(void (^)(NSArray *venues))success failure:(void (^)(NSError *error))failure progress:(void (^)(float progress))progress{
NSDictionary *parameters = #{#"limit": #"10",
#"client_id":kFoursquareClientID,
#"client_secret":kFoursquareClientSecret,
#"radius":#(radiusMeters).stringValue,
#"ll":[NSString stringWithFormat:#"%g,%g",coordinate.latitude, coordinate.longitude],
#"v":kFoursquareVersion};
NSMutableURLRequest *request = [self requestWithMethod:#"GET" path:#"venues/explore" parameters:parameters];
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *items = responseObject[#"response"][#"groups"][0][#"items"];
if (success)
{
success(items);
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) { if (failure) {
failure(error);
NSLog(error.localizedDescription);
} }];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
//
float progressNo = ((float)totalBytesRead) / totalBytesExpectedToRead;
if (progress) {
progress(progressNo);
}
}];
[self enqueueHTTPRequestOperation:operation];
}

AFNetworking - 6 operations at a time consume 80% of CPU

Can anybody answer to me, how come below code eats up whole core of a C2D 2.6GHz CPU?
It just downloads files in 10MB chunks, there can be like 600 of them, but the NSOperationQueue has a limit of 6 concurrent tasks.
How come the same app on Windows (written in C# eats only 2%, not 80%!), it is just a simple HTTP request!
for (DownloadFile *downloadFile in [download filesInTheDownload])
{
for (DownloadChunk *downloadChunk in [downloadFile chunksInTheFile])
{
NSString *downloadPath = [[NSString stringWithFormat:#"%#/%#", [download downloadFolder], [download escapedTitle]] stringByExpandingTildeInPath];
NSString *chunkPath = [downloadPath stringByAppendingFormat:#"/%#.%i", [downloadFile fileName], [downloadChunk chunkId]];
NSError *attributesError = nil;
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:chunkPath error:&attributesError];
NSNumber *fileSizeNumber = [fileAttributes objectForKey:NSFileSize];
uint64_t fileSize = [fileSizeNumber longLongValue];
NSLog(#"Chunk file size: %lli", fileSize);
uint64_t expectedSize = ([downloadChunk endingByte] - [downloadChunk startingByte]) + 1;
NSLog(#"Chunk expected size: %lli", expectedSize);
uint64_t newStartingByte = [downloadChunk startingByte] + fileSize;
if (fileSize == expectedSize)
{
NSLog(#"Chunk complete: %#.%i", [downloadFile fileName], [downloadChunk chunkId]);
}
else
{
NSURL *fileURL = [[NSURL alloc] initWithString:[downloadFile filePath]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:fileURL];
NSLog(#"Normal range: %lli-%lli", [downloadChunk startingByte], [downloadChunk endingByte]);
NSString *range = [NSString stringWithFormat:#"bytes=%lli-%lli", newStartingByte, [downloadChunk endingByte]];
[request setValue:range forHTTPHeaderField:#"Range"];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:chunkPath append:YES];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"%#", [NSString stringWithFormat:#"Chunk complete: %#.%i", [downloadFile fileName], [downloadChunk chunkId]]);
if (download.downloadedBytes == download.size)
[[NSNotificationCenter defaultCenter] postNotificationName:#"downloadFinished" object:download];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation setDownloadProgressBlock:^(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
download.downloadedBytes += bytesRead;
}];
[queue addOperation:operation];
}
}
}
Here are some time profiler screenshots, seems everything is because RunLoops if I read it correctly.
Seems like downgrading most of the properties to nonatomic seems to lower the usage from 80% to 20-30%. Now only downloadedBytes are atomic. What can I do also to make improvements?
In assuming the last progress block completes the download, you're sort of shoving a round peg down a square hole. I'm not sure if that's causing your high CPU usage or not, but I'd certainly fix that. AFNetworking has a function just for this, [AFHTTPRequestOperation setCompletionBlockWithSuccess:failure:], documented here. The correct block will be called based on the HTTP status code returned.
In other words, instead of this:
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:chunkPath append:YES];
[operation setDownloadProgressBlock:^(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
download.downloadedBytes += bytesRead;
if (download.downloadedBytes == download.size)
[[NSNotificationCenter defaultCenter] postNotificationName:#"downloadFinished" object:download];
}];
[queue addOperation:operation];
chunkId++;
Use this:
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:chunkPath append:YES];
[output setCompletionBlockWithSuccess: ^(AFHTTPRequestOperation *operation, id responseObject) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"downloadFinished" object:download];
}
failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
// you should probably do something here
}];
After that, I doubt you'd even need downloadedBytes. That removes your last atomic property, which should improve things.