Эх сурвалжийг харах

Merge pull request #2937 from dreampiggy/feature_URLSessionTaskMetrics

Added the URLSessionTaskMetrics support for downloader && operation, which can be used for network metrics
DreamPiggy 6 жил өмнө
parent
commit
bbfe690845

+ 16 - 4
Examples/SDWebImage Demo/MasterViewController.m

@@ -113,10 +113,22 @@
     }
     
     cell.customTextLabel.text = [NSString stringWithFormat:@"Image #%ld", (long)indexPath.row];
-    [cell.customImageView sd_setImageWithURL:[NSURL URLWithString:self.objects[indexPath.row]]
-                            placeholderImage:placeholderImage
-                                     options:indexPath.row == 0 ? SDWebImageRefreshCached : 0
-                                     context:@{SDWebImageContextImageThumbnailPixelSize : @(CGSizeMake(180, 120))}];
+    __weak SDAnimatedImageView *imageView = cell.customImageView;
+    [imageView sd_setImageWithURL:[NSURL URLWithString:self.objects[indexPath.row]]
+                 placeholderImage:placeholderImage
+                          options:indexPath.row == 0 ? SDWebImageRefreshCached : 0
+                          context:@{SDWebImageContextImageThumbnailPixelSize : @(CGSizeMake(180, 120))}
+                         progress:nil
+                        completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
+        SDWebImageCombinedOperation *operation = [imageView sd_imageLoadOperationForKey:imageView.sd_latestOperationKey];
+        SDWebImageDownloadToken *token = operation.loaderOperation;
+        if (@available(iOS 10.0, *)) {
+            NSURLSessionTaskMetrics *metrics = token.metrics;
+            if (metrics) {
+                printf("Metrics: %s download in (%f) seconds\n", [imageURL.absoluteString cStringUsingEncoding:NSUTF8StringEncoding], metrics.taskInterval.duration);
+            }
+        }
+    }];
     return cell;
 }
 

+ 5 - 0
SDWebImage/Core/SDWebImageDownloader.h

@@ -128,6 +128,11 @@ typedef SDImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock;
  */
 @property (nonatomic, strong, nullable, readonly) NSURLResponse *response;
 
+/**
+ The download's metrics. This will be nil if download operation does not support metrics.
+ */
+@property (nonatomic, strong, nullable, readonly) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+
 @end
 
 

+ 24 - 2
SDWebImage/Core/SDWebImageDownloader.m

@@ -24,6 +24,7 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
 @property (nonatomic, strong, nullable, readwrite) NSURL *url;
 @property (nonatomic, strong, nullable, readwrite) NSURLRequest *request;
 @property (nonatomic, strong, nullable, readwrite) NSURLResponse *response;
+@property (nonatomic, strong, nullable, readwrite) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
 @property (nonatomic, weak, nullable, readwrite) id downloadOperationCancelToken;
 @property (nonatomic, weak, nullable) NSOperation<SDWebImageDownloaderOperation> *downloadOperation;
 @property (nonatomic, assign, getter=isCancelled) BOOL cancelled;
@@ -498,6 +499,15 @@ didReceiveResponse:(NSURLResponse *)response
     }
 }
 
+- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) {
+    
+    // Identify the operation that runs this task and pass it the delegate method
+    NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
+    if ([dataOperation respondsToSelector:@selector(URLSession:task:didFinishCollectingMetrics:)]) {
+        [dataOperation URLSession:session task:task didFinishCollectingMetrics:metrics];
+    }
+}
+
 @end
 
 @implementation SDWebImageDownloadToken
@@ -510,18 +520,30 @@ didReceiveResponse:(NSURLResponse *)response
     self = [super init];
     if (self) {
         _downloadOperation = downloadOperation;
-        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadReceiveResponse:) name:SDWebImageDownloadReceiveResponseNotification object:downloadOperation];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadDidReceiveResponse:) name:SDWebImageDownloadReceiveResponseNotification object:downloadOperation];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadDidStop:) name:SDWebImageDownloadStopNotification object:downloadOperation];
     }
     return self;
 }
 
-- (void)downloadReceiveResponse:(NSNotification *)notification {
+- (void)downloadDidReceiveResponse:(NSNotification *)notification {
     NSOperation<SDWebImageDownloaderOperation> *downloadOperation = notification.object;
     if (downloadOperation && downloadOperation == self.downloadOperation) {
         self.response = downloadOperation.response;
     }
 }
 
+- (void)downloadDidStop:(NSNotification *)notification {
+    NSOperation<SDWebImageDownloaderOperation> *downloadOperation = notification.object;
+    if (downloadOperation && downloadOperation == self.downloadOperation) {
+        if ([downloadOperation respondsToSelector:@selector(metrics)]) {
+            if (@available(iOS 10.0, tvOS 10.0, macOS 10.12, watchOS 3.0, *)) {
+                self.metrics = downloadOperation.metrics;
+            }
+        }
+    }
+}
+
 - (void)cancel {
     @synchronized (self) {
         if (self.isCancelled) {

+ 7 - 0
SDWebImage/Core/SDWebImageDownloaderOperation.h

@@ -36,6 +36,7 @@
 
 @optional
 @property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
+@property (strong, nonatomic, readonly, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
 @property (strong, nonatomic, nullable) NSURLCredential *credential;
 @property (assign, nonatomic) double minimumProgressInterval;
 
@@ -62,6 +63,12 @@
  */
 @property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
 
+/**
+ * The collected metrics from `-URLSession:task:didFinishCollectingMetrics:`.
+ * This can be used to collect the network metrics like download duration, DNS lookup duration, SSL handshake dureation, etc. See Apple's documentation: https://developer.apple.com/documentation/foundation/urlsessiontaskmetrics
+ */
+@property (strong, nonatomic, readonly, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+
 /**
  * The credential used for authentication challenges in `-URLSession:task:didReceiveChallenge:completionHandler:`.
  *

+ 6 - 0
SDWebImage/Core/SDWebImageDownloaderOperation.m

@@ -52,6 +52,8 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
 
 @property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
 
+@property (strong, nonatomic, readwrite, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+
 @property (strong, nonatomic, nonnull) dispatch_queue_t coderQueue; // the queue to do image decoding
 #if SD_UIKIT
 @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
@@ -512,6 +514,10 @@ didReceiveResponse:(NSURLResponse *)response
     }
 }
 
+- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) {
+    self.metrics = metrics;
+}
+
 #pragma mark Helper methods
 + (SDWebImageOptions)imageOptionsFromDownloaderOptions:(SDWebImageDownloaderOptions)downloadOptions {
     SDWebImageOptions options = 0;

+ 7 - 0
SDWebImage/Core/UIView+WebCache.h

@@ -31,6 +31,13 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima
  */
 @property (nonatomic, strong, readonly, nullable) NSURL *sd_imageURL;
 
+/**
+ * Get the current image operation key. Operation key is used to identify the different queries for one view instance (like UIButton).
+ * See more about this in `SDWebImageContextSetImageOperationKey`.
+ * @note You can use method `UIView+WebCacheOperation` to invesigate different queries' operation.
+ */
+@property (nonatomic, strong, readonly, nullable) NSString *sd_latestOperationKey;
+
 /**
  * The current image loading progress associated to the view. The unit count is the received size and excepted size of download.
  * The `totalUnitCount` and `completedUnitCount` will be reset to 0 after a new image loading start (change from current queue). And they will be set to `SDWebImageProgressUnitCountUnknown` if the progressBlock not been called but the image loading success to mark the progress finished (change from main queue).

+ 31 - 0
Tests/Tests/SDWebImageDownloaderTests.m

@@ -642,6 +642,37 @@
     }];
 }
 
+- (void)test26DownloadURLSessionMetrics {
+    XCTestExpectation *expectation1 = [self expectationWithDescription:@"Download URLSessionMetrics works"];
+    
+    SDWebImageDownloader *downloader = [[SDWebImageDownloader alloc] init];
+    
+    __block SDWebImageDownloadToken *token;
+    token = [downloader downloadImageWithURL:[NSURL URLWithString:kTestJPEGURL] completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
+        expect(error).beNil();
+        if (@available(iOS 10.0, tvOS 10.0, macOS 10.12, *)) {
+            NSURLSessionTaskMetrics *metrics = token.metrics;
+            expect(metrics).notTo.beNil();
+            expect(metrics.redirectCount).equal(0);
+            expect(metrics.transactionMetrics.count).equal(1);
+            NSURLSessionTaskTransactionMetrics *metric = metrics.transactionMetrics.firstObject;
+            // Metrcis Test
+            expect(metric.fetchStartDate).notTo.beNil();
+            expect(metric.connectStartDate).notTo.beNil();
+            expect(metric.connectEndDate).notTo.beNil();
+            expect(metric.networkProtocolName).equal(@"http/1.1");
+            expect(metric.resourceFetchType).equal(NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad);
+            expect(metric.isProxyConnection).beFalsy();
+            expect(metric.isReusedConnection).beFalsy();
+        }
+        [expectation1 fulfill];
+    }];
+    
+    [self waitForExpectationsWithCommonTimeoutUsingHandler:^(NSError * _Nullable error) {
+        [downloader invalidateSessionAndCancel:YES];
+    }];
+}
+
 #pragma mark - SDWebImageLoader
 - (void)test30CustomImageLoaderWorks {
     XCTestExpectation *expectation = [self expectationWithDescription:@"Custom image not works"];