Skip to content

Commit

Permalink
Fix Reachability Safety (Alamofire#3684)
Browse files Browse the repository at this point in the history
### Issue Link 🔗
Alamofire#3676

### Goals ⚽
This PR updates our integration of `SCNetworkReachabilityContext` to use
a weak box class and fully integrate with the framework's retain and
release closures. This follows the same logic as
[Reachability.swift](https://github.com/ashleymills/Reachability.swift/blob/a81b7367f2c46875f29577e03a60c39cdfad0c8d/Sources/Reachability.swift#L190).

### Implementation Details 🚧
This PR adds a `WeakManager` type to wrap a weak reference to the
`NetworkReachabilityManager` and updates the context memory management
closures to properly retain and release the values.

### Testing Details 🔍
No tests added, wasn't able to find a way to trigger the reported crash.
Since this logic matches that of the other popular reachability library
I'm confident it's correct.
  • Loading branch information
jshier authored Feb 7, 2023
1 parent 728de0e commit 337b29a
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 17 deletions.
6 changes: 3 additions & 3 deletions .swiftformat
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@
--nospaceoperators ..<, ...
--selfrequired validate
--stripunusedargs closure-only
--wraparguments after-first
--wrapcollections after-first
--wrapparameters after-first
--wraparguments preserve
--wrapcollections preserve
--wrapparameters preserve
39 changes: 32 additions & 7 deletions Source/NetworkReachabilityManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,16 +173,33 @@ open class NetworkReachabilityManager {
state.listener = listener
}

var context = SCNetworkReachabilityContext(version: 0,
info: Unmanaged.passUnretained(self).toOpaque(),
retain: nil,
release: nil,
copyDescription: nil)
let weakManager = WeakManager(manager: self)

var context = SCNetworkReachabilityContext(
version: 0,
info: Unmanaged.passUnretained(weakManager).toOpaque(),
retain: { info in
let unmanaged = Unmanaged<WeakManager>.fromOpaque(info)
_ = unmanaged.retain()

return UnsafeRawPointer(unmanaged.toOpaque())
},
release: { info in
let unmanaged = Unmanaged<WeakManager>.fromOpaque(info)
unmanaged.release()
},
copyDescription: { info in
let unmanaged = Unmanaged<WeakManager>.fromOpaque(info)
let weakManager = unmanaged.takeUnretainedValue()
let description = weakManager.manager?.flags?.readableDescription ?? "nil"

return Unmanaged.passRetained(description as CFString)
})
let callback: SCNetworkReachabilityCallBack = { _, flags, info in
guard let info = info else { return }

let instance = Unmanaged<NetworkReachabilityManager>.fromOpaque(info).takeUnretainedValue()
instance.notifyListener(flags)
let weakManager = Unmanaged<WeakManager>.fromOpaque(info).takeUnretainedValue()
weakManager.manager?.notifyListener(flags)
}

let queueAdded = SCNetworkReachabilitySetDispatchQueue(reachability, reachabilityQueue)
Expand Down Expand Up @@ -228,6 +245,14 @@ open class NetworkReachabilityManager {
state.listenerQueue?.async { listener?(newStatus) }
}
}

private final class WeakManager {
weak var manager: NetworkReachabilityManager?

init(manager: NetworkReachabilityManager?) {
self.manager = manager
}
}
}

// MARK: -
Expand Down
2 changes: 1 addition & 1 deletion Source/RetryPolicy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ open class RetryPolicy: RequestInterceptor {
]

/// The default URL error codes to retry.
public static let defaultRetryableURLErrorCodes: Set<URLError.Code> = [// [Security] App Transport Security disallowed a connection because there is no secure network connection.
public static let defaultRetryableURLErrorCodes: Set<URLError.Code> = [ // [Security] App Transport Security disallowed a connection because there is no secure network connection.
// - [Disabled] ATS settings do not change at runtime.
// .appTransportSecurityRequiresSecureConnection,

Expand Down
5 changes: 3 additions & 2 deletions Tests/ParameterEncodingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ final class URLParameterEncodingTestCase: ParameterEncodingTestCase {
func testURLParameterLiteralBoolEncodingWorksAndDoesNotAffectNumbers() throws {
// Given
let encoding = URLEncoding(boolEncoding: .literal)
let parameters: [String: Any] = [// Must still encode to numbers
let parameters: [String: Any] = [ // Must still encode to numbers
"a": 1,
"b": 0,
"c": 1.0,
Expand All @@ -250,7 +250,8 @@ final class URLParameterEncodingTestCase: ParameterEncodingTestCase {
"i": true,
"j": false,
"k": NSNumber(value: true),
"l": NSNumber(value: false)]
"l": NSNumber(value: false)
]

// When
let urlRequest = try encoding.encode(urlRequest, with: parameters)
Expand Down
10 changes: 6 additions & 4 deletions Tests/ValidationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ final class ContentTypeValidationTestCase: BaseTestCase {
var requestError: AFError?
var downloadError: AFError?

let acceptableContentTypes = [// Sorted in a random order, not alphabetically
let acceptableContentTypes = [ // Sorted in a random order, not alphabetically
"application/octet-stream",
"image/gif",
"image/x-xbitmap",
Expand All @@ -271,7 +271,8 @@ final class ContentTypeValidationTestCase: BaseTestCase {
"image/ico",
"image/bmp",
"image/x-ms-bmp",
"image/x-win-bitmap"]
"image/x-win-bitmap"
]

// When
AF.request(endpoint)
Expand All @@ -294,7 +295,7 @@ final class ContentTypeValidationTestCase: BaseTestCase {
XCTAssertNotNil(requestError)
XCTAssertNotNil(downloadError)

let expectedAcceptableContentTypes = [// Sorted in a specific order, alphabetically
let expectedAcceptableContentTypes = [ // Sorted in a specific order, alphabetically
"application/octet-stream",
"image/bmp",
"image/gif",
Expand All @@ -308,7 +309,8 @@ final class ContentTypeValidationTestCase: BaseTestCase {
"image/x-icon",
"image/x-ms-bmp",
"image/x-win-bitmap",
"image/x-xbitmap"]
"image/x-xbitmap"
]

for error in [requestError, downloadError] {
XCTAssertEqual(error?.isUnacceptableContentType, true)
Expand Down

0 comments on commit 337b29a

Please sign in to comment.