Skip to content

Commit fcb3143

Browse files
committed
Soft-deprecated URLSession.enableAutomaticRegistration
1 parent 4448379 commit fcb3143

File tree

6 files changed

+125
-132
lines changed

6 files changed

+125
-132
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2020-2024 Alexander Grebenyuk (github.com/kean).
4+
5+
import Foundation
6+
7+
@available(*, deprecated, renamed: "NetworkLogger.URLSessionProxyDelegate", message: "")
8+
public typealias URLSessionProxyDelegate = NetworkLogger.URLSessionProxyDelegate
9+
10+
extension NetworkLogger {
11+
/// Automates URLSession request tracking.
12+
///
13+
/// - important: On iOS 16.0, tvOS 16.0, macOS 13.0, watchOS 9.0, it automatically
14+
/// tracks new task creation using the `urlSession(_:didCreateTask:)` delegate
15+
/// method which allows the logger to start tracking network requests right
16+
/// after their creation. On earlier versions, you can (optionally) call
17+
/// ``NetworkLogger/logTaskCreated(_:)`` manually.
18+
public final class URLSessionProxyDelegate: NSObject, URLSessionTaskDelegate, URLSessionDataDelegate, URLSessionDownloadDelegate {
19+
private let actualDelegate: URLSessionDelegate?
20+
private let taskDelegate: URLSessionTaskDelegate?
21+
private let interceptedSelectors: Set<Selector>
22+
private var logger: NetworkLogger { _logger ?? .shared }
23+
private let _logger: NetworkLogger?
24+
25+
/// - parameter logger: By default, uses a shared logger
26+
/// - parameter delegate: The "actual" session delegate, strongly retained.
27+
public init(logger: NetworkLogger? = nil, delegate: URLSessionDelegate? = nil) {
28+
self.actualDelegate = delegate
29+
self.taskDelegate = delegate as? URLSessionTaskDelegate
30+
self._logger = logger
31+
32+
var interceptedSelectors: Set = [
33+
#selector(URLSessionDataDelegate.urlSession(_:dataTask:didReceive:)),
34+
#selector(URLSessionTaskDelegate.urlSession(_:task:didCompleteWithError:)),
35+
#selector(URLSessionTaskDelegate.urlSession(_:task:didFinishCollecting:)),
36+
#selector(URLSessionTaskDelegate.urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)),
37+
#selector(URLSessionDownloadDelegate.urlSession(_:downloadTask:didFinishDownloadingTo:)),
38+
#selector(URLSessionDownloadDelegate.urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:))
39+
]
40+
if #available(iOS 16.0, tvOS 16.0, macOS 13.0, watchOS 9.0, *) {
41+
interceptedSelectors.insert(#selector(URLSessionTaskDelegate.urlSession(_:didCreateTask:)))
42+
}
43+
self.interceptedSelectors = interceptedSelectors
44+
}
45+
46+
// MARK: URLSessionTaskDelegate
47+
48+
var createdTask: URLSessionTask?
49+
50+
public func urlSession(_ session: Foundation.URLSession, didCreateTask task: URLSessionTask) {
51+
createdTask = task
52+
logger.logTaskCreated(task)
53+
if #available(iOS 16.0, tvOS 16.0, macOS 13.0, watchOS 9.0, *) {
54+
taskDelegate?.urlSession?(session, didCreateTask: task)
55+
}
56+
}
57+
58+
public func urlSession(_ session: Foundation.URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
59+
logger.logTask(task, didCompleteWithError: error)
60+
taskDelegate?.urlSession?(session, task: task, didCompleteWithError: error)
61+
}
62+
63+
public func urlSession(_ session: Foundation.URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
64+
logger.logTask(task, didFinishCollecting: metrics)
65+
taskDelegate?.urlSession?(session, task: task, didFinishCollecting: metrics)
66+
}
67+
68+
public func urlSession(_ session: Foundation.URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
69+
if task is URLSessionUploadTask {
70+
logger.logTask(task, didUpdateProgress: (completed: totalBytesSent, total: totalBytesExpectedToSend))
71+
}
72+
(actualDelegate as? URLSessionTaskDelegate)?.urlSession?(session, task: task, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend)
73+
}
74+
75+
// MARK: URLSessionDataDelegate
76+
77+
public func urlSession(_ session: Foundation.URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
78+
logger.logDataTask(dataTask, didReceive: data)
79+
(actualDelegate as? URLSessionDataDelegate)?.urlSession?(session, dataTask: dataTask, didReceive: data)
80+
}
81+
82+
// MARK: URLSessionDownloadDelegate
83+
84+
public func urlSession(_ session: Foundation.URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
85+
(actualDelegate as? URLSessionDownloadDelegate)?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
86+
}
87+
88+
public func urlSession(_ session: Foundation.URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
89+
logger.logTask(downloadTask, didUpdateProgress: (completed: totalBytesWritten, total: totalBytesExpectedToWrite))
90+
(actualDelegate as? URLSessionDownloadDelegate)?.urlSession?(session, downloadTask: downloadTask, didWriteData: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
91+
}
92+
93+
// MARK: Proxy
94+
95+
public override func responds(to aSelector: Selector!) -> Bool {
96+
if interceptedSelectors.contains(aSelector) {
97+
return true
98+
}
99+
return (actualDelegate?.responds(to: aSelector) ?? false) || super.responds(to: aSelector)
100+
}
101+
102+
public override func forwardingTarget(for selector: Selector!) -> Any? {
103+
interceptedSelectors.contains(selector) ? nil : actualDelegate
104+
}
105+
}
106+
}

Sources/Pulse/NetworkLogger/URLSessionProxyDelegate+AutomaticRegistration.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ extension URLSessionProxyDelegate {
1717
/// for automatic logging or manually logging the requests using ``NetworkLogger``.
1818
///
1919
/// - parameter logger: The network logger to be used for recording the requests. By default, uses shared logger.
20+
///
21+
/// - warning: This method is soft-deprecated in Pulse 5.0.
2022
public static func enableAutomaticRegistration(logger: NetworkLogger? = nil) {
2123
guard Thread.isMainThread else {
2224
return DispatchQueue.main.async { _enableAutomaticRegistration(logger: logger) }

Sources/Pulse/NetworkLogger/URLSessionProxyDelegate.swift

Lines changed: 0 additions & 101 deletions
This file was deleted.

Sources/Pulse/Pulse.docc/Articles/GettingStarted.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,23 @@ Learn how to integrate Pulse.
44

55
## 1. Add Frameworks
66

7-
**Option 1 (Recommended)**. Add package to your project using SwiftPM.
7+
- **Option 1 (Recommended)**. Add package to your project using SwiftPM.
88

99
```
1010
https://github.com/kean/Pulse
1111
```
1212

1313
Add both **Pulse** and **PulseUI** libraries to your app. **PulseProxy** is optional and provides a quick, convenient way to capture network traffic to evaluate the framework.
1414

15-
**Option 2**. Use precompiled binary frameworks from the [latest release](https://github.com/kean/Pulse/releases).
15+
- **Option 2**. Use precompiled binary frameworks from the [latest release](https://github.com/kean/Pulse/releases).
1616

1717
## 2. Integrate Pulse Framework
1818

1919
**Pulse** framework contains APIs for logging, capturing, and mocking network requests, as well as connecting to the Pulse Pro apps.
2020

2121
### 2.1. Capture Network Requests
2222

23-
**Option 1 (Quickest)**. If you are evaluating the framework, the quickest way to get started is with a proxy from the **PulseProxy** module.
23+
- **Option 1 (Quickest)**. If you are evaluating the framework, the quickest way to get started is with a proxy from the **PulseProxy** module.
2424

2525
```swift
2626
import PulseProxy
@@ -30,9 +30,9 @@ NetworkLogger.enableProxy()
3030
#endif
3131
```
3232

33-
> Note: **PulseProxy** uses method swizzling and private APIs and it is not recommended to include it in the production builds of your app.
33+
> Note: **PulseProxy** uses method swizzling and private APIs and it is not recommended that you include it in the production builds of your app.
3434
35-
**Option 2 (Recommended)**. Use ``NetworkLogger/URLSession``, a thin wrapper on top of `URLSession`.
35+
- **Option 2 (Recommended)**. Use ``NetworkLogger/URLSession``, a thin wrapper on top of `URLSession`.
3636

3737
```swift
3838
import Pulse

Sources/Pulse/Pulse.docc/Articles/NetworkLogging-Article.md

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ Pulse works on the `URLSession` level, and it needs access to its callbacks to l
1111

1212
## Capture Network Requests
1313

14-
### Option 1 (Quickest): PulseProxy.
14+
The first step is to capture network traffic.
1515

16-
**Option 1 (Quickest)**. If you are evaluating the framework, the quickest way to get started is with a proxy from the **PulseProxy** module.
16+
- **Option 1 (Quickest)**. If you are evaluating the framework, the quickest way to get started is with a proxy from the **PulseProxy** module.
1717

1818
```swift
1919
import PulseProxy
@@ -23,9 +23,9 @@ NetworkLogger.enableProxy()
2323
#endif
2424
```
2525

26-
> Note: **PulseProxy** uses method swizzling and private APIs and it is not recommended to include it in the production builds of your app.
26+
> Important: **PulseProxy** uses method swizzling and private APIs, and it is not recommended that you include it in the production builds of your app. It is also not guaranteed to continue working with new versions of the system SDKs.
2727
28-
**Option 2 (Recommended)**. Use ``NetworkLogger/URLSession``, a thin wrapper on top of `URLSession`.
28+
- **Option 2 (Recommended)**. Use ``NetworkLogger/URLSession``, a thin wrapper on top of `URLSession`.
2929

3030
```swift
3131
import Pulse
@@ -38,42 +38,28 @@ session = URLSession(configuration: .default)
3838
#endif
3939
```
4040

41-
## Proxy Delegate
41+
``NetworkLogger/URLSession`` is the best way to integrate Pulse because it supports all `URLSession` APIs, including the new Async/Await methods. It also makes it easy to remove it conditionally.
4242

43-
The recommended option is to use ``URLSessionProxyDelegate`` which sits between [`URLSession`](https://developer.apple.com/documentation/foundation/urlsession) and your actual [`URLSessionDelegate`](https://developer.apple.com/documentation/foundation/urlsessiondelegate).
43+
- **Option 3.** Use ``URLSessionProxyDelegate``.
4444

45-
You can enable ``URLSessionProxyDelegate`` for all `URLSession` instances created by the app by using ``URLSessionProxyDelegate/enableAutomaticRegistration(logger:)`` (note that it uses Objective-C runtime to achieve that):
45+
If you use a delegate-based `URLSession` that doesn't rely on any of its convenience APIs, such as [Alamofire](https://github.com/Alamofire/Alamofire), you can record its traffic using a proxy delegate.
4646

4747
```swift
48-
// Call it anywhere in your code prior to instantiating a `URLSession`
49-
URLSessionProxyDelegate.enableAutomaticRegistration()
50-
51-
// Instantiate `URLSession` as usual
52-
let session = URLSession(configuration: .default, delegate: YourURLSessionDelegate(), delegateQueue: nil)
53-
```
54-
55-
And if you want to enable logging just for specific sessions, use ``URLSessionProxyDelegate`` directly:
56-
57-
```swift
58-
let delegate = URLSessionProxyDelegate(delegate: YourURLSessionDelegate())
48+
let delegate = URLSessionProxyDelegate(delegate: <#YourSessionDelegate#>)
5949
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
6050
```
6151

62-
> important: Both these options work only with sessions that use a delegate-based approach and won't work with `URLSession.shared`. In that can you can either log the requests manually, which is covered in the next section or try ``Experimental/URLSessionProxy``.
63-
64-
## Manual Logging
52+
- **Option 4 (Manual).** Use ``NetworkLogger`` directly.
6553

66-
Another option for capturing network requests is by using ``NetworkLogger`` directly. For example, here can you can use it with Alamofire's `EventMonitor`:
54+
If none of the convenience APIs described earlier work for you, you can use the underlying ``NetworkLogger`` directly. For example, here is how you can use it with Alamofire's `EventMonitor`:
6755

6856
```swift
6957
import Alamofire
7058

71-
// Don't forget to bootstrap the logging system first.
72-
73-
let session = Alamofire.Session(eventMonitors: [NetworkLoggerEventMonitor(logger: logger)])
59+
let session = Alamofire.Session(eventMonitors: [NetworkLoggerEventMonitor()])
7460

7561
struct NetworkLoggerEventMonitor: EventMonitor {
76-
let logger: NetworkLogger
62+
var logger: NetworkLogger = .shared
7763

7864
func request(_ request: Request, didCreateTask task: URLSessionTask) {
7965
logger.logTaskCreated(task)

0 commit comments

Comments
 (0)