diff --git a/AFNetworking/AFURLSessionManager.h b/AFNetworking/AFURLSessionManager.h index e7ae0d8e1f..99e167a54b 100644 --- a/AFNetworking/AFURLSessionManager.h +++ b/AFNetworking/AFURLSessionManager.h @@ -378,6 +378,13 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)setTaskDidCompleteBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSError * _Nullable error))block; +/** + Sets a block to be executed when metrics are finalized related to a specific task, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didFinishCollectingMetrics:`. + + @param block A block object to be executed when a session task is completed. The block has no return value, and takes three arguments: the session, the task, and any metrics that were collected in the process of executing the task. + */ +- (void)setTaskDidFinishCollectingMetricsBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSURLSessionTaskMetrics * _Nullable metrics))block; + ///------------------------------------------- /// @name Setting Data Task Delegate Callbacks ///------------------------------------------- @@ -498,4 +505,9 @@ FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteAssetPathKey; */ FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteErrorKey; +/** + The session task metrics taken from the download task. Included in the userInfo dictionary of the `AFNetworkingTaskDidCompleteSessionTaskMetrics` + */ +FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteSessionTaskMetrics; + NS_ASSUME_NONNULL_END diff --git a/AFNetworking/AFURLSessionManager.m b/AFNetworking/AFURLSessionManager.m index 7a0529b700..cd0fdf7f97 100644 --- a/AFNetworking/AFURLSessionManager.m +++ b/AFNetworking/AFURLSessionManager.m @@ -82,6 +82,7 @@ static dispatch_group_t url_session_manager_completion_group() { NSString * const AFNetworkingTaskDidCompleteResponseDataKey = @"com.alamofire.networking.complete.finish.responsedata"; NSString * const AFNetworkingTaskDidCompleteErrorKey = @"com.alamofire.networking.task.complete.error"; NSString * const AFNetworkingTaskDidCompleteAssetPathKey = @"com.alamofire.networking.task.complete.assetpath"; +NSString * const AFNetworkingTaskDidCompleteSessionTaskMetrics = @"com.alamofire.networking.complete.sessiontaskmetrics"; static NSString * const AFURLSessionManagerLockName = @"com.alamofire.networking.session.manager.lock"; @@ -97,6 +98,7 @@ static dispatch_group_t url_session_manager_completion_group() { typedef NSInputStream * (^AFURLSessionTaskNeedNewBodyStreamBlock)(NSURLSession *session, NSURLSessionTask *task); typedef void (^AFURLSessionTaskDidSendBodyDataBlock)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend); typedef void (^AFURLSessionTaskDidCompleteBlock)(NSURLSession *session, NSURLSessionTask *task, NSError *error); +typedef void (^AFURLSessionTaskDidFinishCollectingMetricsBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLSessionTaskMetrics * metrics); typedef NSURLSessionResponseDisposition (^AFURLSessionDataTaskDidReceiveResponseBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response); typedef void (^AFURLSessionDataTaskDidBecomeDownloadTaskBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask); @@ -120,6 +122,7 @@ - (instancetype)initWithTask:(NSURLSessionTask *)task; @property (nonatomic, strong) NSProgress *uploadProgress; @property (nonatomic, strong) NSProgress *downloadProgress; @property (nonatomic, copy) NSURL *downloadFileURL; +@property (nonatomic, strong) NSURLSessionTaskMetrics *sessionTaskMetrics; @property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading; @property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock; @property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock; @@ -210,6 +213,14 @@ - (void)URLSession:(__unused NSURLSession *)session self.mutableData = nil; } +#if AF_CAN_USE_AT_AVAILABLE + if (@available(iOS 10, macOS 10.12, watchOS 3, tvOS 10, *)) { + if (self.sessionTaskMetrics) { + userInfo[AFNetworkingTaskDidCompleteSessionTaskMetrics] = self.sessionTaskMetrics; + } + } +#endif + if (self.downloadFileURL) { userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL; } else if (data) { @@ -258,6 +269,12 @@ - (void)URLSession:(__unused NSURLSession *)session } } +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics { + self.sessionTaskMetrics = metrics; +} + #pragma mark - NSURLSessionDataDelegate - (void)URLSession:(__unused NSURLSession *)session @@ -462,6 +479,7 @@ @interface AFURLSessionManager () @property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream; @property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData; @property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete; +@property (readwrite, nonatomic, copy) AFURLSessionTaskDidFinishCollectingMetricsBlock taskDidFinishCollectingMetrics; @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse; @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask; @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData; @@ -871,6 +889,10 @@ - (void)setTaskDidCompleteBlock:(void (^)(NSURLSession *session, NSURLSessionTas self.taskDidComplete = block; } +- (void)setTaskDidFinishCollectingMetricsBlock:(void (^)(NSURLSession * _Nonnull, NSURLSessionTask * _Nonnull, NSURLSessionTaskMetrics * _Nullable))block { + self.taskDidFinishCollectingMetrics = block; +} + #pragma mark - - (void)setDataTaskDidReceiveResponseBlock:(NSURLSessionResponseDisposition (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response))block { @@ -1077,6 +1099,21 @@ - (void)URLSession:(NSURLSession *)session } } +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics +{ + AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; + // Metrics may fire after URLSession:task:didCompleteWithError: is called, delegate may be nil + if (delegate) { + [delegate URLSession:session task:task didFinishCollectingMetrics:metrics]; + } + + if (self.taskDidFinishCollectingMetrics) { + self.taskDidFinishCollectingMetrics(session, task, metrics); + } +} + #pragma mark - NSURLSessionDataDelegate - (void)URLSession:(NSURLSession *)session diff --git a/Tests/Tests/AFURLSessionManagerTests.m b/Tests/Tests/AFURLSessionManagerTests.m index e4f2a6b199..deb067dfe2 100644 --- a/Tests/Tests/AFURLSessionManagerTests.m +++ b/Tests/Tests/AFURLSessionManagerTests.m @@ -133,6 +133,29 @@ - (void)testDownloadTaskDoesReportProgress { [self waitForExpectationsWithCommonTimeout]; } +- (void)testSessionTaskDoesReportMetrics { + [self expectationForNotification:AFNetworkingTaskDidCompleteNotification object:nil handler:^BOOL(NSNotification * _Nonnull notification) { +#if AF_CAN_USE_AT_AVAILABLE + if (@available(iOS 10, macOS 10.12, watchOS 3, tvOS 10, *)) { + return [notification userInfo][AFNetworkingTaskDidCompleteSessionTaskMetrics] != nil; + } +#endif + return YES; + }]; + + __weak XCTestExpectation *metricsBlock = [self expectationWithDescription:@"Metrics completion block is called"]; + [self.localManager setTaskDidFinishCollectingMetricsBlock:^(NSURLSession * _Nonnull session, NSURLSessionTask * _Nonnull task, NSURLSessionTaskMetrics * _Nullable metrics) { + [metricsBlock fulfill]; + }]; + + NSURLSessionTask *task = [self.localManager downloadTaskWithRequest:[self bigImageURLRequest] + progress:nil + destination:nil + completionHandler:nil]; + [task resume]; + [self waitForExpectationsWithCommonTimeout]; +} + // iOS 7 has a bug that may return nil for a session. To simulate that, nil out the // session and it will return nil itself. - (void)testFileUploadTaskReturnsNilWithBug {