From 210c260422b3d4226a141c616f14a8887b91a93f Mon Sep 17 00:00:00 2001 From: Kevin Harwood Date: Tue, 1 Dec 2015 09:17:32 -0600 Subject: [PATCH 1/4] New Progress Reporting API These changes expose a simpler, block based progress reporting API, while maintaining the ability for more advanced features using KVO on NSProgress. --- AFNetworking/AFHTTPSessionManager.h | 54 ++++ AFNetworking/AFHTTPSessionManager.m | 57 +++- AFNetworking/AFURLSessionManager.h | 50 ++-- AFNetworking/AFURLSessionManager.m | 271 ++++++++++-------- Tests/Tests/AFHTTPSessionManagerTests.m | 64 +++++ .../Tests/AFNetworkReachabilityManagerTests.m | 2 +- Tests/Tests/AFURLSessionManagerTests.m | 134 ++++++--- UIKit+AFNetworking/UIWebView+AFNetworking.m | 2 +- 8 files changed, 439 insertions(+), 195 deletions(-) diff --git a/AFNetworking/AFHTTPSessionManager.h b/AFNetworking/AFHTTPSessionManager.h index c124584be9..20045ee66e 100644 --- a/AFNetworking/AFHTTPSessionManager.h +++ b/AFNetworking/AFHTTPSessionManager.h @@ -144,6 +144,24 @@ NS_ASSUME_NONNULL_BEGIN success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + +/** + Creates and runs an `NSURLSessionDataTask` with a `GET` request. + + @param URLString The URL string used to create the request URL. + @param parameters The parameters to be encoded according to the client request serializer. + @param progress A block object to be executed when the download progress is updated. Note this block is called on the session queue, not the main queue. + @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer. + @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. + + @see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler: + */ +- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString + parameters:(nullable id)parameters + progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress + success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success + failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + /** Creates and runs an `NSURLSessionDataTask` with a `HEAD` request. @@ -174,6 +192,23 @@ NS_ASSUME_NONNULL_BEGIN success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; +/** + Creates and runs an `NSURLSessionDataTask` with a `POST` request. + + @param URLString The URL string used to create the request URL. + @param parameters The parameters to be encoded according to the client request serializer. + @param progress A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue. + @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer. + @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. + + @see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler: + */ +- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString + parameters:(nullable id)parameters + progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress + success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success + failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + /** Creates and runs an `NSURLSessionDataTask` with a multipart `POST` request. @@ -191,6 +226,25 @@ NS_ASSUME_NONNULL_BEGIN success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; +/** + Creates and runs an `NSURLSessionDataTask` with a multipart `POST` request. + + @param URLString The URL string used to create the request URL. + @param parameters The parameters to be encoded according to the client request serializer. + @param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol. + @param progress A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue. + @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer. + @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. + + @see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler: + */ +- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString + parameters:(nullable id)parameters + constructingBodyWithBlock:(nullable void (^)(id formData))block + progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress + success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success + failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + /** Creates and runs an `NSURLSessionDataTask` with a `PUT` request. diff --git a/AFNetworking/AFHTTPSessionManager.m b/AFNetworking/AFHTTPSessionManager.m index c92ccaa4b0..35a1b67b15 100644 --- a/AFNetworking/AFHTTPSessionManager.m +++ b/AFNetworking/AFHTTPSessionManager.m @@ -105,7 +105,24 @@ - (NSURLSessionDataTask *)GET:(NSString *)URLString success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { - NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure]; + + return [self GET:URLString parameters:parameters progress:nil success:success failure:failure]; +} + +- (NSURLSessionDataTask *)GET:(NSString *)URLString + parameters:(id)parameters + progress:(void (^)(NSProgress * _Nonnull))downloadProgress + success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success + failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure +{ + + NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" + URLString:URLString + parameters:parameters + uploadProgress:nil + downloadProgress:downloadProgress + success:success + failure:failure]; [dataTask resume]; @@ -117,7 +134,7 @@ - (NSURLSessionDataTask *)HEAD:(NSString *)URLString success:(void (^)(NSURLSessionDataTask *task))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { - NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"HEAD" URLString:URLString parameters:parameters success:^(NSURLSessionDataTask *task, __unused id responseObject) { + NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"HEAD" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:nil success:^(NSURLSessionDataTask *task, __unused id responseObject) { if (success) { success(task); } @@ -133,16 +150,35 @@ - (NSURLSessionDataTask *)POST:(NSString *)URLString success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { - NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure]; + return [self POST:URLString parameters:parameters progress:nil success:success failure:failure]; +} + +- (NSURLSessionDataTask *)POST:(NSString *)URLString + parameters:(id)parameters + progress:(void (^)(NSProgress * _Nonnull))uploadProgress + success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success + failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure +{ + NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure]; [dataTask resume]; return dataTask; } +- (NSURLSessionDataTask *)POST:(NSString *)URLString + parameters:(nullable id)parameters + constructingBodyWithBlock:(nullable void (^)(id _Nonnull))block + success:(nullable void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure +{ + return [self POST:URLString parameters:parameters constructingBodyWithBlock:block success:success failure:failure]; +} + - (NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(id)parameters constructingBodyWithBlock:(void (^)(id formData))block + progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { @@ -161,7 +197,7 @@ - (NSURLSessionDataTask *)POST:(NSString *)URLString return nil; } - __block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:nil completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { + __block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { if (error) { if (failure) { failure(task, error); @@ -183,7 +219,7 @@ - (NSURLSessionDataTask *)PUT:(NSString *)URLString success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { - NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PUT" URLString:URLString parameters:parameters success:success failure:failure]; + NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PUT" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:nil success:success failure:failure]; [dataTask resume]; @@ -195,7 +231,7 @@ - (NSURLSessionDataTask *)PATCH:(NSString *)URLString success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { - NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PATCH" URLString:URLString parameters:parameters success:success failure:failure]; + NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PATCH" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:nil success:success failure:failure]; [dataTask resume]; @@ -207,7 +243,7 @@ - (NSURLSessionDataTask *)DELETE:(NSString *)URLString success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { - NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"DELETE" URLString:URLString parameters:parameters success:success failure:failure]; + NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"DELETE" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:nil success:success failure:failure]; [dataTask resume]; @@ -217,6 +253,8 @@ - (NSURLSessionDataTask *)DELETE:(NSString *)URLString - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters + uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress + downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress success:(void (^)(NSURLSessionDataTask *, id))success failure:(void (^)(NSURLSessionDataTask *, NSError *))failure { @@ -236,7 +274,10 @@ - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method } __block NSURLSessionDataTask *dataTask = nil; - dataTask = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { + dataTask = [self dataTaskWithRequest:request + uploadProgress:uploadProgress + downloadProgress:downloadProgress + completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { if (error) { if (failure) { failure(dataTask, error); diff --git a/AFNetworking/AFURLSessionManager.h b/AFNetworking/AFURLSessionManager.h index bc31729d1f..be91828489 100644 --- a/AFNetworking/AFURLSessionManager.h +++ b/AFNetworking/AFURLSessionManager.h @@ -209,6 +209,19 @@ NS_ASSUME_NONNULL_BEGIN - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; +/** + Creates an `NSURLSessionDataTask` with the specified request. + + @param request The HTTP request for the request. + @param uploadProgress A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue. + @param downloadProgress A block object to be executed when the download progress is updated. Note this block is called on the session queue, not the main queue. + @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. + */ +- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request + uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock + downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock + completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; + ///--------------------------- /// @name Running Upload Tasks ///--------------------------- @@ -218,14 +231,14 @@ NS_ASSUME_NONNULL_BEGIN @param request The HTTP request for the request. @param fileURL A URL to the local file to be uploaded. - @param progress A progress object monitoring the current upload progress. + @param progress A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue. @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. @see `attemptsToRecreateUploadTasksForBackgroundSessions` */ - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL - progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress + progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; /** @@ -233,23 +246,23 @@ NS_ASSUME_NONNULL_BEGIN @param request The HTTP request for the request. @param bodyData A data object containing the HTTP body to be uploaded. - @param progress A progress object monitoring the current upload progress. + @param progress A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue. @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. */ - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(nullable NSData *)bodyData - progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress + progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; /** Creates an `NSURLSessionUploadTask` with the specified streaming request. @param request The HTTP request for the request. - @param progress A progress object monitoring the current upload progress. + @param progress A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue. @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. */ - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request - progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress + progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; ///----------------------------- @@ -260,14 +273,14 @@ NS_ASSUME_NONNULL_BEGIN Creates an `NSURLSessionDownloadTask` with the specified request. @param request The HTTP request for the request. - @param progress A progress object monitoring the current download progress. + @param progress A block object to be executed when the download progress is updated. Note this block is called on the session queue, not the main queue. @param destination A block object to be executed in order to determine the destination of the downloaded file. This block takes two arguments, the target path & the server response, and returns the desired file URL of the resulting download. The temporary file used during the download will be automatically deleted after being moved to the returned URL. @param completionHandler A block to be executed when a task finishes. This block has no return value and takes three arguments: the server response, the path of the downloaded file, and the error describing the network or parsing error that occurred, if any. @warning If using a background `NSURLSessionConfiguration` on iOS, these blocks will be lost when the app is terminated. Background sessions may prefer to use `-setDownloadTaskDidFinishDownloadingBlock:` to specify the URL for saving the downloaded file, rather than the destination block of this method. */ - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request - progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress + progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler; @@ -275,12 +288,12 @@ NS_ASSUME_NONNULL_BEGIN Creates an `NSURLSessionDownloadTask` with the specified resume data. @param resumeData The data used to resume downloading. - @param progress A progress object monitoring the current download progress. + @param progress A block object to be executed when the download progress is updated. Note this block is called on the session queue, not the main queue. @param destination A block object to be executed in order to determine the destination of the downloaded file. This block takes two arguments, the target path & the server response, and returns the desired file URL of the resulting download. The temporary file used during the download will be automatically deleted after being moved to the returned URL. @param completionHandler A block to be executed when a task finishes. This block has no return value and takes three arguments: the server response, the path of the downloaded file, and the error describing the network or parsing error that occurred, if any. */ - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData - progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress + progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler; @@ -288,32 +301,23 @@ NS_ASSUME_NONNULL_BEGIN /// @name Getting Progress for Tasks ///--------------------------------- -/** - Returns the response download progress of the specified task. - - @param dataTask The session data task. Must not be `nil`. - - @return An `NSProgress` object reporting the response download progress of a task, or `nil` if the progress is unavailable. - */ -- (nullable NSProgress *)progressForDataTask:(NSURLSessionDataTask *)dataTask; - /** Returns the upload progress of the specified task. - @param uploadTask The session upload task. Must not be `nil`. + @param task The session task. Must not be `nil`. @return An `NSProgress` object reporting the upload progress of a task, or `nil` if the progress is unavailable. */ -- (nullable NSProgress *)uploadProgressForTask:(NSURLSessionUploadTask *)uploadTask; +- (nullable NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task; /** Returns the download progress of the specified task. - @param downloadTask The session download task. Must not be `nil`. + @param task The session task. Must not be `nil`. @return An `NSProgress` object reporting the download progress of a task, or `nil` if the progress is unavailable. */ -- (nullable NSProgress *)downloadProgressForTask:(NSURLSessionDownloadTask *)downloadTask; +- (nullable NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task; ///----------------------------------------- /// @name Setting Session Delegate Callbacks diff --git a/AFNetworking/AFURLSessionManager.m b/AFNetworking/AFURLSessionManager.m index 19898aad62..0fee3987a6 100644 --- a/AFNetworking/AFURLSessionManager.m +++ b/AFNetworking/AFURLSessionManager.m @@ -89,17 +89,22 @@ static dispatch_group_t url_session_manager_completion_group() { typedef NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location); typedef void (^AFURLSessionDownloadTaskDidWriteDataBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite); typedef void (^AFURLSessionDownloadTaskDidResumeBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes); +typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *); typedef void (^AFURLSessionTaskCompletionHandler)(NSURLResponse *response, id responseObject, NSError *error); + #pragma mark - @interface AFURLSessionManagerTaskDelegate : NSObject @property (nonatomic, weak) AFURLSessionManager *manager; @property (nonatomic, strong) NSMutableData *mutableData; -@property (nonatomic, strong) NSProgress *progress; +@property (nonatomic, strong) NSProgress *uploadProgress; +@property (nonatomic, strong) NSProgress *downloadProgress; @property (nonatomic, copy) NSURL *downloadFileURL; @property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading; +@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock; +@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock; @property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler; @end @@ -112,24 +117,119 @@ - (instancetype)init { } self.mutableData = [NSMutableData data]; + self.uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil]; + self.uploadProgress.totalUnitCount = NSURLSessionTransferSizeUnknown; - self.progress = [NSProgress progressWithTotalUnitCount:0]; - + self.downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil]; + self.downloadProgress.totalUnitCount = NSURLSessionTransferSizeUnknown; return self; } -#pragma mark - NSURLSessionTaskDelegate +#pragma mark - NSProgress Tracking -- (void)URLSession:(__unused NSURLSession *)session - task:(__unused NSURLSessionTask *)task - didSendBodyData:(__unused int64_t)bytesSent - totalBytesSent:(int64_t)totalBytesSent -totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend -{ - self.progress.totalUnitCount = totalBytesExpectedToSend; - self.progress.completedUnitCount = totalBytesSent; +- (void)setupProgressForTask:(NSURLSessionTask *)task { + __weak __typeof__(task) weakTask = task; + + self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend; + self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive; + [self.uploadProgress setCancellable:YES]; + [self.uploadProgress setCancellationHandler:^{ + __typeof__(task) strongTask = weakTask; + [strongTask cancel]; + }]; + [self.uploadProgress setPausable:YES]; + [self.uploadProgress setPausingHandler:^{ + __typeof__(task) strongTask = weakTask; + [strongTask suspend]; + }]; + if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) { + [self.uploadProgress setResumingHandler:^{ + __typeof__(task) strongTask = weakTask; + [strongTask resume]; + }]; + } + + [self.downloadProgress setCancellable:YES]; + [self.downloadProgress setCancellationHandler:^{ + __typeof__(task) strongTask = weakTask; + [strongTask cancel]; + }]; + [self.downloadProgress setPausable:YES]; + [self.downloadProgress setPausingHandler:^{ + __typeof__(task) strongTask = weakTask; + [strongTask suspend]; + }]; + + if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) { + [self.downloadProgress setResumingHandler:^{ + __typeof__(task) strongTask = weakTask; + [strongTask resume]; + }]; + } + + [task addObserver:self + forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived)) + options:NSKeyValueObservingOptionNew + context:NULL]; + [task addObserver:self + forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive)) + options:NSKeyValueObservingOptionNew + context:NULL]; + + [task addObserver:self + forKeyPath:NSStringFromSelector(@selector(countOfBytesSent)) + options:NSKeyValueObservingOptionNew + context:NULL]; + [task addObserver:self + forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend)) + options:NSKeyValueObservingOptionNew + context:NULL]; + + [self.downloadProgress addObserver:self + forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) + options:NSKeyValueObservingOptionNew + context:NULL]; + [self.uploadProgress addObserver:self + forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) + options:NSKeyValueObservingOptionNew + context:NULL]; +} + +- (void)cleanUpProgressForTask:(NSURLSessionTask *)task { + [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))]; + [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]; + [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))]; + [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]; + [self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))]; + [self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if ([object isKindOfClass:[NSURLSessionTask class]]) { + if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) { + self.downloadProgress.completedUnitCount = [change[@"new"] longLongValue]; + } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) { + self.downloadProgress.totalUnitCount = [change[@"new"] longLongValue]; + } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) { + self.uploadProgress.completedUnitCount = [change[@"new"] longLongValue]; + } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) { + self.uploadProgress.totalUnitCount = [change[@"new"] longLongValue]; + } + } + else if ([object isEqual:self.downloadProgress]) { + if (self.downloadProgressBlock) { + self.downloadProgressBlock(object); + } + } + else if ([object isEqual:self.uploadProgress]) { + if (self.uploadProgressBlock) { + self.uploadProgressBlock(object); + } + } } +#pragma mark - NSURLSessionTaskDelegate + - (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error @@ -206,12 +306,6 @@ - (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { - NSUInteger length = data.length; - long long expectedLength = dataTask.response.expectedContentLength; - if(expectedLength != -1) { - self.progress.totalUnitCount = expectedLength; - self.progress.completedUnitCount += length; - } [self.mutableData appendData:data]; } @@ -236,24 +330,6 @@ - (void)URLSession:(NSURLSession *)session } } -- (void)URLSession:(__unused NSURLSession *)session - downloadTask:(__unused NSURLSessionDownloadTask *)downloadTask - didWriteData:(__unused int64_t)bytesWritten - totalBytesWritten:(int64_t)totalBytesWritten -totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite -{ - self.progress.totalUnitCount = totalBytesExpectedToWrite; - self.progress.completedUnitCount = totalBytesWritten; -} - -- (void)URLSession:(__unused NSURLSession *)session - downloadTask:(__unused NSURLSessionDownloadTask *)downloadTask - didResumeAtOffset:(int64_t)fileOffset -expectedTotalBytes:(int64_t)expectedTotalBytes { - self.progress.totalUnitCount = expectedTotalBytes; - self.progress.completedUnitCount = fileOffset; -} - @end #pragma mark - @@ -447,7 +523,7 @@ - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)config [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { for (NSURLSessionDataTask *task in dataTasks) { - [self addDelegateForDataTask:task completionHandler:nil]; + [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil]; } for (NSURLSessionUploadTask *uploadTask in uploadTasks) { @@ -515,10 +591,14 @@ - (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate [self.lock lock]; self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; + [delegate setupProgressForTask:task]; + [self addNotificationObserverForTask:task]; [self.lock unlock]; } - (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask + uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock + downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init]; @@ -527,48 +607,28 @@ - (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask dataTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:dataTask]; + + delegate.uploadProgressBlock = uploadProgressBlock; + delegate.downloadProgressBlock = downloadProgressBlock; } - (void)addDelegateForUploadTask:(NSURLSessionUploadTask *)uploadTask - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init]; delegate.manager = self; delegate.completionHandler = completionHandler; - int64_t totalUnitCount = uploadTask.countOfBytesExpectedToSend; - if(totalUnitCount == NSURLSessionTransferSizeUnknown) { - NSString *contentLength = [uploadTask.originalRequest valueForHTTPHeaderField:@"Content-Length"]; - if(contentLength) { - totalUnitCount = (int64_t)[contentLength longLongValue]; - } - } - - if (delegate.progress) { - delegate.progress.totalUnitCount = totalUnitCount; - } else { - delegate.progress = [NSProgress progressWithTotalUnitCount:totalUnitCount]; - } - - delegate.progress.pausingHandler = ^{ - [uploadTask suspend]; - }; - delegate.progress.cancellationHandler = ^{ - [uploadTask cancel]; - }; - - if (progress) { - *progress = delegate.progress; - } - uploadTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:uploadTask]; + + delegate.uploadProgressBlock = uploadProgressBlock; } - (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler { @@ -582,29 +642,24 @@ - (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask }; } - if (progress) { - *progress = delegate.progress; - } - downloadTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:downloadTask]; + + delegate.downloadProgressBlock = downloadProgressBlock; } - (void)removeDelegateForTask:(NSURLSessionTask *)task { NSParameterAssert(task); + AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; [self.lock lock]; + [delegate cleanUpProgressForTask:task]; + [self removeNotificationObserverForTask:task]; [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; [self.lock unlock]; } -- (void)removeAllDelegates { - [self.lock lock]; - [self.mutableTaskDelegatesKeyedByTaskIdentifier removeAllObjects]; - [self.lock unlock]; -} - #pragma mark - - (NSArray *)tasksForKeyPath:(NSString *)keyPath { @@ -681,14 +736,20 @@ - (void)removeNotificationObserverForTask:(NSURLSessionTask *)task { - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { + return [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler]; +} + +- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request + uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock + downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock + completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler { + __block NSURLSessionDataTask *dataTask = nil; dispatch_sync(url_session_manager_creation_queue(), ^{ dataTask = [self.session dataTaskWithRequest:request]; }); - [self addNotificationObserverForTask:dataTask]; - - [self addDelegateForDataTask:dataTask completionHandler:completionHandler]; + [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler]; return dataTask; } @@ -697,7 +758,7 @@ - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { __block NSURLSessionUploadTask *uploadTask = nil; @@ -705,22 +766,20 @@ - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL]; }); - [self addNotificationObserverForTask:uploadTask]; - if (!uploadTask && self.attemptsToRecreateUploadTasksForBackgroundSessions && self.session.configuration.identifier) { for (NSUInteger attempts = 0; !uploadTask && attempts < AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask; attempts++) { uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL]; } } - [self addDelegateForUploadTask:uploadTask progress:progress completionHandler:completionHandler]; + [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler]; return uploadTask; } - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { __block NSURLSessionUploadTask *uploadTask = nil; @@ -728,15 +787,13 @@ - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData]; }); - [self addNotificationObserverForTask:uploadTask]; - - [self addDelegateForUploadTask:uploadTask progress:progress completionHandler:completionHandler]; + [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler]; return uploadTask; } - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { __block NSURLSessionUploadTask *uploadTask = nil; @@ -744,9 +801,7 @@ - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)reques uploadTask = [self.session uploadTaskWithStreamedRequest:request]; }); - [self addNotificationObserverForTask:uploadTask]; - - [self addDelegateForUploadTask:uploadTask progress:progress completionHandler:completionHandler]; + [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler]; return uploadTask; } @@ -754,7 +809,7 @@ - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)reques #pragma mark - - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler { @@ -763,15 +818,13 @@ - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request downloadTask = [self.session downloadTaskWithRequest:request]; }); - [self addNotificationObserverForTask:downloadTask]; - - [self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler]; + [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler]; return downloadTask; } - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler { @@ -780,24 +833,18 @@ - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData downloadTask = [self.session downloadTaskWithResumeData:resumeData]; }); - [self addNotificationObserverForTask:downloadTask]; - - [self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler]; + [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler]; return downloadTask; } #pragma mark - -- (NSProgress *)progressForDataTask:(NSURLSessionDataTask *)dataTask { - return [[self delegateForTask:dataTask] progress]; +- (NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task { + return [[self delegateForTask:task] uploadProgress]; } -- (NSProgress *)uploadProgressForTask:(NSURLSessionUploadTask *)uploadTask { - return [[self delegateForTask:uploadTask] progress]; -} - -- (NSProgress *)downloadProgressForTask:(NSURLSessionDownloadTask *)downloadTask { - return [[self delegateForTask:downloadTask] progress]; +- (NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task { + return [[self delegateForTask:task] downloadProgress]; } #pragma mark - @@ -897,7 +944,6 @@ - (void)URLSession:(NSURLSession *)session self.sessionDidBecomeInvalid(session, error); } - [self removeAllDelegates]; [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session]; } @@ -1011,9 +1057,6 @@ - (void)URLSession:(NSURLSession *)session } } - AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; - [delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalUnitCount]; - if (self.taskDidSendBodyData) { self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount); } @@ -1035,8 +1078,6 @@ - (void)URLSession:(NSURLSession *)session if (self.taskDidComplete) { self.taskDidComplete(session, task, error); } - - [self removeNotificationObserverForTask:task]; } #pragma mark - NSURLSessionDataDelegate @@ -1067,9 +1108,6 @@ - (void)URLSession:(NSURLSession *)session [self setDelegate:delegate forTask:downloadTask]; } - [self removeNotificationObserverForTask:dataTask]; - [self addNotificationObserverForTask:downloadTask]; - if (self.dataTaskDidBecomeDownloadTask) { self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask); } @@ -1136,8 +1174,6 @@ - (void)URLSession:(NSURLSession *)session if (delegate) { [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location]; } - - [self removeNotificationObserverForTask:downloadTask]; } - (void)URLSession:(NSURLSession *)session @@ -1146,9 +1182,6 @@ - (void)URLSession:(NSURLSession *)session totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { - AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask]; - [delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite]; - if (self.downloadTaskDidWriteData) { self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); } diff --git a/Tests/Tests/AFHTTPSessionManagerTests.m b/Tests/Tests/AFHTTPSessionManagerTests.m index 343ab2d411..04c825e26a 100644 --- a/Tests/Tests/AFHTTPSessionManagerTests.m +++ b/Tests/Tests/AFHTTPSessionManagerTests.m @@ -186,6 +186,70 @@ - (void)testThatSerializationErrorGeneratesErrorAndNullTaskForGET { } +#pragma mark - Progress + +- (void)testDownloadProgressIsReportedForGET { + XCTestExpectation *expectation = [self expectationWithDescription:@"Progress Should equal 1.0"]; + [self.manager + GET:@"image" + parameters:nil + progress:^(NSProgress * _Nonnull downloadProgress) { + if (downloadProgress.fractionCompleted == 1.0) { + [expectation fulfill]; + } + } + success:nil + failure:nil]; + [self waitForExpectationsWithCommonTimeoutUsingHandler:nil]; +} + +- (void)testUploadProgressIsReportedForPOST { + NSMutableString *payload = [NSMutableString stringWithString:@"AFNetworking"]; + while ([payload lengthOfBytesUsingEncoding:NSUTF8StringEncoding] < 20000) { + [payload appendString:@"AFNetworking"]; + } + + __weak __block XCTestExpectation *expectation = [self expectationWithDescription:@"Progress Should equal 1.0"]; + + [self.manager + POST:@"post" + parameters:payload + progress:^(NSProgress * _Nonnull uploadProgress) { + if (uploadProgress.fractionCompleted == 1.0) { + [expectation fulfill]; + expectation = nil; + } + } + success:nil + failure:nil]; + [self waitForExpectationsWithCommonTimeoutUsingHandler:nil]; +} + +- (void)testUploadProgressIsReportedForStreamingPost { + NSMutableString *payload = [NSMutableString stringWithString:@"AFNetworking"]; + while ([payload lengthOfBytesUsingEncoding:NSUTF8StringEncoding] < 20000) { + [payload appendString:@"AFNetworking"]; + } + + __block __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Progress Should equal 1.0"]; + + [self.manager + POST:@"post" + parameters:nil + constructingBodyWithBlock:^(id _Nonnull formData) { + [formData appendPartWithFileData:[payload dataUsingEncoding:NSUTF8StringEncoding] name:@"AFNetworking" fileName:@"AFNetworking" mimeType:@"text/html"]; + } + progress:^(NSProgress * _Nonnull uploadProgress) { + if (uploadProgress.fractionCompleted == 1.0) { + [expectation fulfill]; + expectation = nil; + } + } + success:nil + failure:nil]; + [self waitForExpectationsWithCommonTimeoutUsingHandler:nil]; +} + # pragma mark - Rest Interface - (void)testThatSuccessBlockIsCalledFor200 { diff --git a/Tests/Tests/AFNetworkReachabilityManagerTests.m b/Tests/Tests/AFNetworkReachabilityManagerTests.m index b2bff5043c..47ea093049 100644 --- a/Tests/Tests/AFNetworkReachabilityManagerTests.m +++ b/Tests/Tests/AFNetworkReachabilityManagerTests.m @@ -93,7 +93,7 @@ - (void)testAddressReachabilityNotification { - (void)verifyReachabilityStatusBlockGetsCalledWithManager:(AFNetworkReachabilityManager *)manager { - XCTestExpectation *expectation = [self expectationWithDescription:@"reachability status change block gets called"]; + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"reachability status change block gets called"]; typeof(manager) __weak weakManager = manager; [manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { diff --git a/Tests/Tests/AFURLSessionManagerTests.m b/Tests/Tests/AFURLSessionManagerTests.m index efb1a11b90..860b1036a3 100644 --- a/Tests/Tests/AFURLSessionManagerTests.m +++ b/Tests/Tests/AFURLSessionManagerTests.m @@ -33,6 +33,12 @@ @interface AFURLSessionManagerTests : AFTestCase @implementation AFURLSessionManagerTests +- (NSURLRequest *)bigImageURLRequest { + NSURL *url = [NSURL URLWithString:@"http://scitechdaily.com/images/New-Image-of-the-Galaxy-Messier-94-also-Known-as-NGC-4736.jpg"]; + NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0]; + return request; +} + - (void)setUp { [super setUp]; self.localManager = [[AFURLSessionManager alloc] init]; @@ -62,67 +68,109 @@ - (void)tearDown { #pragma mark Progress - -- (void)testDataTaskDoesReportProgress { - NSURL *url = [NSURL URLWithString:@"http://i.space.com/images/i/8123/original/saturn-moon-mimas-death-star.jpg"]; - NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url]; - XCTestExpectation *expectation = [self expectationWithDescription:@"Request should succeed"]; +- (void)testDataTaskDoesReportDownloadProgress { NSURLSessionDataTask *task; + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Progress should equal 1.0"]; task = [self.localManager - dataTaskWithRequest:request - completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { - [expectation fulfill]; - }]; - - NSProgress *progress = [self.localManager progressForDataTask:task]; - [self keyValueObservingExpectationForObject:progress keyPath:@"fractionCompleted" - handler:^BOOL(NSProgress *observedProgress, NSDictionary * _Nonnull change) { - double new = [change[@"new"] doubleValue]; - double old = [change[@"old"] doubleValue]; - return new == 1.0 && old != 0.0; - }]; + dataTaskWithRequest:[self bigImageURLRequest] + uploadProgress:nil + downloadProgress:^(NSProgress * _Nonnull downloadProgress) { + if (downloadProgress.fractionCompleted == 1.0) { + [expectation fulfill]; + } + } + completionHandler:nil]; + [task resume]; [self waitForExpectationsWithCommonTimeoutUsingHandler:nil]; } -- (void)testUploadTasksProgressBecomesPartOfCurrentProgress { - NSProgress *overallProgress = [NSProgress progressWithTotalUnitCount:100]; +- (void)testDataTaskDownloadProgressCanBeKVOd { + NSURLSessionDataTask *task; - [overallProgress becomeCurrentWithPendingUnitCount:80]; - NSProgress *uploadProgress = nil; + task = [self.localManager + dataTaskWithRequest:[self bigImageURLRequest] + uploadProgress:nil + downloadProgress:nil + completionHandler:nil]; + + NSProgress *progress = [self.localManager downloadProgressForTask:task]; + [self keyValueObservingExpectationForObject:progress keyPath:@"fractionCompleted" + handler:^BOOL(NSProgress *observedProgress, NSDictionary * _Nonnull change) { + double new = [change[@"new"] doubleValue]; + double old = [change[@"old"] doubleValue]; + return new == 1.0 && old != 0.0; + }]; + [task resume]; + [self waitForExpectationsWithCommonTimeoutUsingHandler:nil]; +} + +- (void)testDownloadTaskDoesReportProgress { + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Progress should equal 1.0"]; + NSURLSessionTask *task; + task = [self.localManager + downloadTaskWithRequest:[self bigImageURLRequest] + progress:^(NSProgress * _Nonnull downloadProgress) { + if (downloadProgress.fractionCompleted == 1.0) { + [expectation fulfill]; + } + } + destination:nil + completionHandler:nil]; + [task resume]; + [self waitForExpectationsWithCommonTimeoutUsingHandler:nil]; +} - [self.localManager uploadTaskWithRequest:[NSURLRequest requestWithURL:self.baseURL] - fromData:[NSData data] - progress:&uploadProgress - completionHandler:nil]; - [overallProgress resignCurrent]; +- (void)testUploadTaskDoesReportProgress { + NSMutableString *payload = [NSMutableString stringWithString:@"AFNetworking"]; + while ([payload lengthOfBytesUsingEncoding:NSUTF8StringEncoding] < 20000) { + [payload appendString:@"AFNetworking"]; + } - XCTAssertTrue(overallProgress.fractionCompleted == 0); + NSURL *url = [NSURL URLWithString:[[self.baseURL absoluteString] stringByAppendingString:@"/post"]]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0]; + [request setHTTPMethod:@"POST"]; - uploadProgress.totalUnitCount = 1; - uploadProgress.completedUnitCount = 1; + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Progress should equal 1.0"]; - XCTAssertTrue(overallProgress.fractionCompleted == 0.8); + NSURLSessionTask *task; + task = [self.localManager + uploadTaskWithRequest:request + fromData:[payload dataUsingEncoding:NSUTF8StringEncoding] + progress:^(NSProgress * _Nonnull uploadProgress) { + NSLog(@"%@", uploadProgress.localizedDescription); + if ([uploadProgress fractionCompleted] == 1.0) { + [expectation fulfill]; + } + } + completionHandler:nil]; + [task resume]; + [self waitForExpectationsWithCommonTimeoutUsingHandler:nil]; } -- (void)testDownloadTasksProgressBecomesPartOfCurrentProgress { - NSProgress *overallProgress = [NSProgress progressWithTotalUnitCount:100]; - - [overallProgress becomeCurrentWithPendingUnitCount:80]; - NSProgress *downloadProgress = nil; +- (void)testUploadProgressCanBeKVOd { + NSMutableString *payload = [NSMutableString stringWithString:@"AFNetworking"]; + while ([payload lengthOfBytesUsingEncoding:NSUTF8StringEncoding] < 20000) { + [payload appendString:@"AFNetworking"]; + } - [self.localManager downloadTaskWithRequest:[NSURLRequest requestWithURL:self.baseURL] - progress:&downloadProgress - destination:nil - completionHandler:nil]; - [overallProgress resignCurrent]; + NSURL *url = [NSURL URLWithString:[[self.baseURL absoluteString] stringByAppendingString:@"/post"]]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0]; + [request setHTTPMethod:@"POST"]; - XCTAssertTrue(overallProgress.fractionCompleted == 0.0); + NSURLSessionTask *task; + task = [self.localManager + uploadTaskWithRequest:request + fromData:[payload dataUsingEncoding:NSUTF8StringEncoding] + progress:nil + completionHandler:nil]; - downloadProgress.totalUnitCount = 1; - downloadProgress.completedUnitCount = 1; + NSProgress *uploadProgress = [self.localManager uploadProgressForTask:task]; + [self keyValueObservingExpectationForObject:uploadProgress keyPath:NSStringFromSelector(@selector(fractionCompleted)) expectedValue:@(1.0)]; - XCTAssertTrue(overallProgress.fractionCompleted == 0.8); + [task resume]; + [self waitForExpectationsWithCommonTimeoutUsingHandler:nil]; } #pragma mark - Issue #2702 Tests diff --git a/UIKit+AFNetworking/UIWebView+AFNetworking.m b/UIKit+AFNetworking/UIWebView+AFNetworking.m index 8c36e793e4..5b9a969dfa 100644 --- a/UIKit+AFNetworking/UIWebView+AFNetworking.m +++ b/UIKit+AFNetworking/UIWebView+AFNetworking.m @@ -146,7 +146,7 @@ - (void)loadRequest:(NSURLRequest *)request } }]; self.af_URLSessionTask = dataTask; - *progress = [self.sessionManager progressForDataTask:dataTask]; + *progress = [self.sessionManager downloadProgressForTask:dataTask]; [self.af_URLSessionTask resume]; if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { From 86ade4e13fc0c8985004dc4ae414b35acccee763 Mon Sep 17 00:00:00 2001 From: Kevin Harwood Date: Tue, 1 Dec 2015 10:06:10 -0600 Subject: [PATCH 2/4] Fixed issue where task was being captured in a block --- AFNetworking/AFURLSessionManager.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AFNetworking/AFURLSessionManager.m b/AFNetworking/AFURLSessionManager.m index 0fee3987a6..0d53a60eb6 100644 --- a/AFNetworking/AFURLSessionManager.m +++ b/AFNetworking/AFURLSessionManager.m @@ -134,35 +134,35 @@ - (void)setupProgressForTask:(NSURLSessionTask *)task { self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive; [self.uploadProgress setCancellable:YES]; [self.uploadProgress setCancellationHandler:^{ - __typeof__(task) strongTask = weakTask; + __typeof__(weakTask) strongTask = weakTask; [strongTask cancel]; }]; [self.uploadProgress setPausable:YES]; [self.uploadProgress setPausingHandler:^{ - __typeof__(task) strongTask = weakTask; + __typeof__(weakTask) strongTask = weakTask; [strongTask suspend]; }]; if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) { [self.uploadProgress setResumingHandler:^{ - __typeof__(task) strongTask = weakTask; + __typeof__(weakTask) strongTask = weakTask; [strongTask resume]; }]; } [self.downloadProgress setCancellable:YES]; [self.downloadProgress setCancellationHandler:^{ - __typeof__(task) strongTask = weakTask; + __typeof__(weakTask) strongTask = weakTask; [strongTask cancel]; }]; [self.downloadProgress setPausable:YES]; [self.downloadProgress setPausingHandler:^{ - __typeof__(task) strongTask = weakTask; + __typeof__(weakTask) strongTask = weakTask; [strongTask suspend]; }]; if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) { [self.downloadProgress setResumingHandler:^{ - __typeof__(task) strongTask = weakTask; + __typeof__(weakTask) strongTask = weakTask; [strongTask resume]; }]; } From 2adeda5d44a2374fa2d51f26a9a8f4620297fd8b Mon Sep 17 00:00:00 2001 From: Kevin Harwood Date: Thu, 3 Dec 2015 16:25:36 -0600 Subject: [PATCH 3/4] Added a `deprecated` flag to older methods in the API --- AFNetworking/AFHTTPSessionManager.h | 6 +++--- Example/Classes/Models/Post.m | 2 +- Tests/Tests/AFHTTPSessionManagerTests.m | 4 ++++ Tests/Tests/AFNetworkActivityManagerTests.m | 5 +++++ UIKit+AFNetworking/UIWebView+AFNetworking.m | 1 + 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/AFNetworking/AFHTTPSessionManager.h b/AFNetworking/AFHTTPSessionManager.h index 20045ee66e..d391e552b1 100644 --- a/AFNetworking/AFHTTPSessionManager.h +++ b/AFNetworking/AFHTTPSessionManager.h @@ -142,7 +142,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; /** @@ -190,7 +190,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; /** Creates and runs an `NSURLSessionDataTask` with a `POST` request. @@ -224,7 +224,7 @@ NS_ASSUME_NONNULL_BEGIN parameters:(nullable id)parameters constructingBodyWithBlock:(nullable void (^)(id formData))block success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; /** Creates and runs an `NSURLSessionDataTask` with a multipart `POST` request. diff --git a/Example/Classes/Models/Post.m b/Example/Classes/Models/Post.m index 6c1580e8bc..55dbcbba27 100644 --- a/Example/Classes/Models/Post.m +++ b/Example/Classes/Models/Post.m @@ -44,7 +44,7 @@ - (instancetype)initWithAttributes:(NSDictionary *)attributes { #pragma mark - + (NSURLSessionDataTask *)globalTimelinePostsWithBlock:(void (^)(NSArray *posts, NSError *error))block { - return [[AFAppDotNetAPIClient sharedClient] GET:@"stream/0/posts/stream/global" parameters:nil success:^(NSURLSessionDataTask * __unused task, id JSON) { + return [[AFAppDotNetAPIClient sharedClient] GET:@"stream/0/posts/stream/global" parameters:nil progress:nil success:^(NSURLSessionDataTask * __unused task, id JSON) { NSArray *postsFromResponse = [JSON valueForKeyPath:@"data"]; NSMutableArray *mutablePosts = [NSMutableArray arrayWithCapacity:[postsFromResponse count]]; for (NSDictionary *attributes in postsFromResponse) { diff --git a/Tests/Tests/AFHTTPSessionManagerTests.m b/Tests/Tests/AFHTTPSessionManagerTests.m index 04c825e26a..d170270107 100644 --- a/Tests/Tests/AFHTTPSessionManagerTests.m +++ b/Tests/Tests/AFHTTPSessionManagerTests.m @@ -176,6 +176,7 @@ - (void)testThatSerializationErrorGeneratesErrorAndNullTaskForGET { nilTask = [self.manager GET:@"test" parameters:@{@"key":@"value"} + progress:nil success:nil failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { XCTAssertNil(task); @@ -257,6 +258,7 @@ - (void)testThatSuccessBlockIsCalledFor200 { [self.manager GET:@"status/200" parameters:nil + progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { [expectation fulfill]; } @@ -269,6 +271,7 @@ - (void)testThatFailureBlockIsCalledFor404 { [self.manager GET:@"status/404" parameters:nil + progress:nil success:nil failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error) { [expectation fulfill]; @@ -282,6 +285,7 @@ - (void)testThatResponseObjectIsEmptyFor204 { [self.manager GET:@"status/204" parameters:nil + progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { urlResponseObject = responseObject; [expectation fulfill]; diff --git a/Tests/Tests/AFNetworkActivityManagerTests.m b/Tests/Tests/AFNetworkActivityManagerTests.m index 104b24c0c9..00900ab45a 100644 --- a/Tests/Tests/AFNetworkActivityManagerTests.m +++ b/Tests/Tests/AFNetworkActivityManagerTests.m @@ -69,6 +69,7 @@ - (void)testThatNetworkActivityIndicatorTurnsOnAndOffIndicatorWhenRequestSucceed [self.sessionManager GET:@"/delay/1" parameters:nil + progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { [requestExpectation fulfill]; } @@ -94,6 +95,7 @@ - (void)testThatNetworkActivityIndicatorTurnsOnAndOffIndicatorWhenRequestFails { [self.sessionManager GET:@"/status/404" parameters:nil + progress:nil success:nil failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { [requestExpectation fulfill]; @@ -126,6 +128,7 @@ - (void)testThatVisibilityDelaysAreApplied { [self.sessionManager GET:@"/delay/2" parameters:nil + progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { requestEndTime = CACurrentMediaTime(); [requestExpectation fulfill]; @@ -154,6 +157,7 @@ - (void)testThatIndicatorBlockIsOnlyCalledOnceEachForStartAndEndForMultipleReque [self.sessionManager GET:@"/delay/4" parameters:nil + progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { [requestExpectation fulfill]; } @@ -163,6 +167,7 @@ - (void)testThatIndicatorBlockIsOnlyCalledOnceEachForStartAndEndForMultipleReque [self.sessionManager GET:@"/delay/2" parameters:nil + progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { [secondRequestExpectation fulfill]; diff --git a/UIKit+AFNetworking/UIWebView+AFNetworking.m b/UIKit+AFNetworking/UIWebView+AFNetworking.m index 5b9a969dfa..ac089da15f 100644 --- a/UIKit+AFNetworking/UIWebView+AFNetworking.m +++ b/UIKit+AFNetworking/UIWebView+AFNetworking.m @@ -129,6 +129,7 @@ - (void)loadRequest:(NSURLRequest *)request dataTask = [self.sessionManager GET:request.URL.absoluteString parameters:nil + progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { __strong __typeof(weakSelf) strongSelf = weakSelf; if (success) { From 423d83ed5e1a1ddea84243d5ec914f2e25b8b780 Mon Sep 17 00:00:00 2001 From: Kevin Harwood Date: Thu, 3 Dec 2015 16:28:35 -0600 Subject: [PATCH 4/4] Changed __nullable to _Nullable --- AFNetworking/AFHTTPSessionManager.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/AFNetworking/AFHTTPSessionManager.h b/AFNetworking/AFHTTPSessionManager.h index d391e552b1..55ed92ec52 100644 --- a/AFNetworking/AFHTTPSessionManager.h +++ b/AFNetworking/AFHTTPSessionManager.h @@ -142,7 +142,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; /** @@ -160,7 +160,7 @@ NS_ASSUME_NONNULL_BEGIN parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `HEAD` request. @@ -175,7 +175,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `POST` request. @@ -190,7 +190,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; /** Creates and runs an `NSURLSessionDataTask` with a `POST` request. @@ -207,7 +207,7 @@ NS_ASSUME_NONNULL_BEGIN parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a multipart `POST` request. @@ -224,7 +224,7 @@ NS_ASSUME_NONNULL_BEGIN parameters:(nullable id)parameters constructingBodyWithBlock:(nullable void (^)(id formData))block success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; /** Creates and runs an `NSURLSessionDataTask` with a multipart `POST` request. @@ -243,7 +243,7 @@ NS_ASSUME_NONNULL_BEGIN constructingBodyWithBlock:(nullable void (^)(id formData))block progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `PUT` request. @@ -258,7 +258,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `PATCH` request. @@ -273,7 +273,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `DELETE` request. @@ -288,7 +288,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; @end