服务商获取权限失败的话retry,更换了首页的背景图

This commit is contained in:
DDIsFriend
2023-11-21 13:47:33 +08:00
parent cfd5d93c71
commit 11d838906f
28 changed files with 921 additions and 372 deletions

View File

@@ -3569,22 +3569,6 @@
landmarkType = "7"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "13D4EEB2-43B5-4060-973F-6B3853959DB1"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "OrderScheduling/Global/User/UserPermission.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "37"
endingLineNumber = "37"
landmarkName = "init()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy <BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent <BreakpointContent
@@ -4845,22 +4829,6 @@
</Locations> </Locations>
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "DD1381E7-616D-4126-9D53-127EE8F3C58D"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "OrderScheduling/Global/User/UserPermission.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "45"
endingLineNumber = "45"
landmarkName = "init()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy <BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent <BreakpointContent
@@ -4873,23 +4841,7 @@
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "35" startingLineNumber = "35"
endingLineNumber = "35" endingLineNumber = "35"
landmarkName = "post(urlString:parameters:encoding:headers:responseType:)" landmarkName = "post(urlString:parameters:encoding:headers:responseType:completionHandler:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "A9FCC6E5-032F-4920-BD1A-EE1CA080F8C3"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "OrderScheduling/Global/User/UserPermission.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "118"
endingLineNumber = "118"
landmarkName = "init()"
landmarkType = "7"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
@@ -4903,8 +4855,8 @@
filePath = "OrderScheduling/Global/User/UserPermission.swift" filePath = "OrderScheduling/Global/User/UserPermission.swift"
startingColumnNumber = "9223372036854775807" startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "36" startingLineNumber = "33"
endingLineNumber = "36" endingLineNumber = "33"
landmarkName = "init()" landmarkName = "init()"
landmarkType = "7"> landmarkType = "7">
<Locations> <Locations>
@@ -4941,5 +4893,101 @@
</Locations> </Locations>
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "CDB1B968-1B96-47AF-8588-145C7DCA8DDA"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "OrderScheduling/Global/User/UserPermission.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "46"
endingLineNumber = "46"
landmarkName = "init()"
landmarkType = "7">
<Locations>
<Location
uuid = "CDB1B968-1B96-47AF-8588-145C7DCA8DDA - 57f4233fc3fbba6c"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "OrderScheduling.UserPermission.init() -&gt; OrderScheduling.UserPermission"
moduleName = "OrderScheduling"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/zd/Desktop/%E4%B8%AD%E9%81%93%E6%95%91%E6%8F%B4/OrderScheduling/OrderScheduling/Global/User/UserPermission.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "47"
endingLineNumber = "47"
offsetFromSymbolStart = "1476">
</Location>
<Location
uuid = "CDB1B968-1B96-47AF-8588-145C7DCA8DDA - f350e9cccc58bb60"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #5 (Swift.Optional&lt;OrderScheduling.ResponseModel&lt;Swift.Array&lt;Swift.String&gt;&gt;&gt;) -&gt; () in OrderScheduling.UserPermission.init() -&gt; OrderScheduling.UserPermission"
moduleName = "OrderScheduling"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/zd/Desktop/%E4%B8%AD%E9%81%93%E6%95%91%E6%8F%B4/OrderScheduling/OrderScheduling/Global/User/UserPermission.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "47"
endingLineNumber = "47"
offsetFromSymbolStart = "44">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "B9BCBB10-2C21-4C80-952B-A48BD5461765"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "OrderScheduling/Global/User/UserPermission.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "115"
endingLineNumber = "115"
landmarkName = "init()"
landmarkType = "7">
<Locations>
<Location
uuid = "B9BCBB10-2C21-4C80-952B-A48BD5461765 - f350e9cccc5880e2"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #5 (Swift.Optional&lt;OrderScheduling.ResponseModel&lt;Swift.Array&lt;Swift.String&gt;&gt;&gt;) -&gt; () in OrderScheduling.UserPermission.init() -&gt; OrderScheduling.UserPermission"
moduleName = "OrderScheduling"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/zd/Desktop/%E4%B8%AD%E9%81%93%E6%95%91%E6%8F%B4/OrderScheduling/OrderScheduling/Global/User/UserPermission.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "113"
endingLineNumber = "113"
offsetFromSymbolStart = "5256">
</Location>
<Location
uuid = "B9BCBB10-2C21-4C80-952B-A48BD5461765 - 866a31bf155da6"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #8 (Swift.String) -&gt; Swift.Bool in closure #5 (Swift.Optional&lt;OrderScheduling.ResponseModel&lt;Swift.Array&lt;Swift.String&gt;&gt;&gt;) -&gt; () in OrderScheduling.UserPermission.init() -&gt; OrderScheduling.UserPermission"
moduleName = "OrderScheduling"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/zd/Desktop/%E4%B8%AD%E9%81%93%E6%95%91%E6%8F%B4/OrderScheduling/OrderScheduling/Global/User/UserPermission.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "113"
endingLineNumber = "113"
offsetFromSymbolStart = "92">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
</Breakpoints> </Breakpoints>
</Bucket> </Bucket>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 KiB

After

Width:  |  Height:  |  Size: 373 KiB

View File

@@ -25,103 +25,94 @@ open class UserPermission {
public var canSupplierRemarkBtn : Bool = false public var canSupplierRemarkBtn : Bool = false
public var canSupplierAuditEditCostBtn : Bool = false public var canSupplierAuditEditCostBtn : Bool = false
public var canSupplierAuditUploadPhotoBtn : Bool = false public var canSupplierAuditUploadPhotoBtn : Bool = false
lazy private var alert = CommonAlertView()
private let maxRetryCount = 120
init() { init() {
Observable.combineLatest(relay, USER.refreshTokenSub) Observable.combineLatest(relay, USER.refreshTokenSub)
.observe(on: MainScheduler.instance)
.do(onNext: { (_,_) in
UIApplication.shared.dd_keyWindow.rootViewController?.view.dd_showHUD()
})
.flatMapLatest { (_,_) in .flatMapLatest { (_,_) in
return RQ.userOperationPermissions() return RQ.userOperationPermissions()
} }
.observe(on: MainScheduler.instance) .retry(when: { (rxError: Observable<Error>) -> Observable<Int> in
.do(onNext: { _ in return rxError.enumerated().flatMap({[weak self] (index,error) -> Observable<Int> in
UIApplication.shared.dd_keyWindow.rootViewController?.view.dd_hideHUD() guard index < (self?.maxRetryCount ?? 0) else {
return Observable.error(error)
}
return Observable.timer(RxTimeInterval.seconds(5), scheduler: MainScheduler.instance)
})
}) })
.observe(on: MainScheduler.instance) .observe(on: MainScheduler.instance)
.subscribe(onNext: {[weak self] response in .subscribe(onNext: {[weak self] response in
if response?.success == true { if response?.success == true {
self?.canRejectDispatchHandle = false
if response?.data?.contains(where: { value in if response?.data?.contains(where: { value in
value == UserOperationPermissionsDataModel.DataEnum.rejectDispatchHandle.rawValue value == UserOperationPermissionsDataModel.DataEnum.rejectDispatchHandle.rawValue
}) == true { }) == true {
self?.canRejectDispatchHandle = true self?.canRejectDispatchHandle = true
}else{
self?.canRejectDispatchHandle = false
} }
self?.canAcceptDispatchHandle = false
if response?.data?.contains(where: { value in if response?.data?.contains(where: { value in
value == UserOperationPermissionsDataModel.DataEnum.acceptDispatchHandle.rawValue value == UserOperationPermissionsDataModel.DataEnum.acceptDispatchHandle.rawValue
}) == true { }) == true {
self?.canAcceptDispatchHandle = true self?.canAcceptDispatchHandle = true
}else{
self?.canAcceptDispatchHandle = false
} }
self?.canWaitdispatchBtn = false
if response?.data?.contains(where: { value in if response?.data?.contains(where: { value in
value == UserOperationPermissionsDataModel.DataEnum.waitdispatchBtn.rawValue value == UserOperationPermissionsDataModel.DataEnum.waitdispatchBtn.rawValue
}) == true { }) == true {
self?.canWaitdispatchBtn = true self?.canWaitdispatchBtn = true
}else{
self?.canWaitdispatchBtn = false
} }
self?.canWaitModifyDispatchBtn = false
if response?.data?.contains(where: { value in if response?.data?.contains(where: { value in
value == UserOperationPermissionsDataModel.DataEnum.waitModifyDispatchBtn.rawValue value == UserOperationPermissionsDataModel.DataEnum.waitModifyDispatchBtn.rawValue
}) == true { }) == true {
self?.canWaitModifyDispatchBtn = true self?.canWaitModifyDispatchBtn = true
}else{
self?.canWaitModifyDispatchBtn = false
} }
self?.canDealWith = false
if response?.data?.contains(where: { value in if response?.data?.contains(where: { value in
value == UserOperationPermissionsDataModel.DataEnum.dealWith.rawValue value == UserOperationPermissionsDataModel.DataEnum.dealWith.rawValue
}) == true { }) == true {
self?.canDealWith = true self?.canDealWith = true
}else{
self?.canDealWith = false
} }
self?.canSupplierRemarkBtn = false
if response?.data?.contains(where: { value in if response?.data?.contains(where: { value in
value == UserOperationPermissionsDataModel.DataEnum.supplierRemarkBtn.rawValue value == UserOperationPermissionsDataModel.DataEnum.supplierRemarkBtn.rawValue
}) == true { }) == true {
self?.canSupplierRemarkBtn = true self?.canSupplierRemarkBtn = true
}else{
self?.canSupplierRemarkBtn = false
} }
self?.canSupplierAuditEditCostBtn = false
if response?.data?.contains(where: { value in if response?.data?.contains(where: { value in
value == UserOperationPermissionsDataModel.DataEnum.supplierAuditEditCostBtn.rawValue value == UserOperationPermissionsDataModel.DataEnum.supplierAuditEditCostBtn.rawValue
}) == true { }) == true {
self?.canSupplierAuditEditCostBtn = true self?.canSupplierAuditEditCostBtn = true
}else{
self?.canSupplierAuditEditCostBtn = false
} }
self?.canSupplierAuditUploadPhotoBtn = false
if response?.data?.contains(where: { value in if response?.data?.contains(where: { value in
value == UserOperationPermissionsDataModel.DataEnum.supplierAuditUploadPhotoBtn.rawValue value == UserOperationPermissionsDataModel.DataEnum.supplierAuditUploadPhotoBtn.rawValue
}) == true { }) == true {
self?.canSupplierAuditUploadPhotoBtn = true self?.canSupplierAuditUploadPhotoBtn = true
}else{
self?.canSupplierAuditUploadPhotoBtn = false
} }
self?.userPermissionRelay.accept(response?.data) self?.userPermissionRelay.accept(response?.data)
}else{
if let alert = self?.alert {
alert.contentLabel.text = notObtainUserPermissions
ENTRY.showUserPermissionsEntry(view: alert,name: notObtainUserPermissionsEntry)
}
} }
}) },onError: { error in
.disposed(by: disposeBag)
alert.sureButton.rx.tap
.observe(on: MainScheduler.instance)
.subscribe(onNext: {[weak self] _ in
ENTRY.dismiss(name: notObtainUserPermissionsEntry) {[weak self] in
self?.relay.accept(nil)
}
})
.disposed(by: disposeBag)
alert.cancelButton.rx.tap
.observe(on: MainScheduler.instance)
.subscribe(onNext: { _ in
exit(0)
}) })
.disposed(by: disposeBag) .disposed(by: disposeBag)
} }

View File

@@ -15,6 +15,9 @@ public let RQ = RequestList.default
open class RequestList { open class RequestList {
public static let `default` = RequestList() public static let `default` = RequestList()
struct DDError : Error {
}
func tokenHeader() -> HTTPHeader { func tokenHeader() -> HTTPHeader {
let httpHeader = HTTPHeader.init(name: "Authorization", value: USER.token ?? "") let httpHeader = HTTPHeader.init(name: "Authorization", value: USER.token ?? "")
return httpHeader return httpHeader
@@ -109,7 +112,13 @@ open class RequestList {
} }
func userOperationPermissions() -> Single<ResponseModel<[String]>?> { func userOperationPermissions() -> Single<ResponseModel<[String]>?> {
return DDAF.post(urlString: HOST+API.userOperationPermissions,encoding: URLEncodedFormParameterEncoder.default,headers: [tokenHeader()],responseType: ResponseModel<[String]>.self) return DDAF.post(urlString: HOST+API.userOperationPermissions,encoding: URLEncodedFormParameterEncoder.default,headers: [tokenHeader()],responseType: ResponseModel<[String]>.self) { single, response in
if response.value?.success == true {
single(.success(response.value))
}else{
single(.failure(DDError()))
}
}
} }
func vehicleLogout<P:Encodable>(parameters:P) -> Single<ResponseModel<String>?> { func vehicleLogout<P:Encodable>(parameters:P) -> Single<ResponseModel<String>?> {

View File

@@ -1,5 +1,5 @@
PODS: PODS:
- Alamofire (5.7.1) - Alamofire (5.8.1)
- AMapFoundation-NO-IDFA (1.8.2) - AMapFoundation-NO-IDFA (1.8.2)
- AMapLocation-NO-IDFA (2.9.0): - AMapLocation-NO-IDFA (2.9.0):
- AMapFoundation-NO-IDFA (>= 1.7.0) - AMapFoundation-NO-IDFA (>= 1.7.0)
@@ -270,7 +270,7 @@ PODS:
- DDLogKit_Private - DDLogKit_Private
- DDMAMapKit_Private/DDMAUtil (0.1.5): - DDMAMapKit_Private/DDMAUtil (0.1.5):
- DDMAMapKit_Private/DDMAMap - DDMAMapKit_Private/DDMAMap
- DDNetworkingOfAlamofireKit_Private (0.1.8): - DDNetworkingOfAlamofireKit_Private (0.2.1):
- Alamofire - Alamofire
- DDLogKit_Private - DDLogKit_Private
- RxSwift - RxSwift
@@ -412,7 +412,7 @@ SPEC REPOS:
- ZLPhotoBrowser - ZLPhotoBrowser
SPEC CHECKSUMS: SPEC CHECKSUMS:
Alamofire: 0123a34370cb170936ae79a8df46cc62b2edeb88 Alamofire: 3ca42e259043ee0dc5c0cdd76c4bc568b8e42af7
AMapFoundation-NO-IDFA: 6ce0ef596d4eb8d934ff498e56747b6de1247b05 AMapFoundation-NO-IDFA: 6ce0ef596d4eb8d934ff498e56747b6de1247b05
AMapLocation-NO-IDFA: 6839d1543b3138ae594ddd36ab72741dc87df66f AMapLocation-NO-IDFA: 6839d1543b3138ae594ddd36ab72741dc87df66f
AMapNavi-NO-IDFA: 70c724400376bfadcb8ec08b9761f526096cfdb6 AMapNavi-NO-IDFA: 70c724400376bfadcb8ec08b9761f526096cfdb6
@@ -430,7 +430,7 @@ SPEC CHECKSUMS:
DDFontKit_Private: 7b8f4ebf0f60622874036202734d8460dc7b3806 DDFontKit_Private: 7b8f4ebf0f60622874036202734d8460dc7b3806
DDLogKit_Private: 1ed442cc7be004bd05f27bfda9b525e113df54e0 DDLogKit_Private: 1ed442cc7be004bd05f27bfda9b525e113df54e0
DDMAMapKit_Private: b378d69f693d6998d136155cd5c81be2e4545fae DDMAMapKit_Private: b378d69f693d6998d136155cd5c81be2e4545fae
DDNetworkingOfAlamofireKit_Private: d65c96f99bc59311d374e7b7a7e8a9e042d9b5ea DDNetworkingOfAlamofireKit_Private: 652eb70a7d8bac81d77d036fabeb52f807120f6d
DDPersistenceKit_Private: c150822543ffa6ece3900178629812f64902ed90 DDPersistenceKit_Private: c150822543ffa6ece3900178629812f64902ed90
DDProgressHUDKit_Private: 1e219062ddeb7801a4bb13b367efa1f3fbf17f1e DDProgressHUDKit_Private: 1e219062ddeb7801a4bb13b367efa1f3fbf17f1e
DDTimerSwiftKit_Private: cce3fe58b1b581fe4cddb3fb84fcde31b4e83541 DDTimerSwiftKit_Private: cce3fe58b1b581fe4cddb3fb84fcde31b4e83541

View File

@@ -1,11 +1,10 @@
![Alamofire: Elegant Networking in Swift](https://raw.githubusercontent.com/Alamofire/Alamofire/master/Resources/AlamofireLogo.png) ![Alamofire: Elegant Networking in Swift](https://raw.githubusercontent.com/Alamofire/Alamofire/master/Resources/AlamofireLogo.png)
[![Swift](https://img.shields.io/badge/Swift-5.5_5.6_5.7_5.8-orange?style=flat-square)](https://img.shields.io/badge/Swift-5.5_5.6_5.7_5.8-Orange?style=flat-square) [![Swift](https://img.shields.io/badge/Swift-5.6_5.7_5.8_5.9-orange?style=flat-square)](https://img.shields.io/badge/Swift-5.6_5.7_5.8_5.9-Orange?style=flat-square)
[![Platforms](https://img.shields.io/badge/Platforms-macOS_iOS_tvOS_watchOS_Linux_Windows-yellowgreen?style=flat-square)](https://img.shields.io/badge/Platforms-macOS_iOS_tvOS_watchOS_Linux_Windows-Green?style=flat-square) [![Platforms](https://img.shields.io/badge/Platforms-macOS_iOS_tvOS_watchOS_visionOS_Linux_Windows_Android-yellowgreen?style=flat-square)](https://img.shields.io/badge/Platforms-macOS_iOS_tvOS_watchOS_vision_OS_Linux_Windows_Android-Green?style=flat-square)
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Alamofire.svg?style=flat-square)](https://img.shields.io/cocoapods/v/Alamofire.svg) [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Alamofire.svg?style=flat-square)](https://img.shields.io/cocoapods/v/Alamofire.svg)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat-square)](https://github.com/Carthage/Carthage) [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat-square)](https://github.com/Carthage/Carthage)
[![Swift Package Manager](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square)](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square) [![Swift Package Manager](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square)](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square)
[![Twitter](https://img.shields.io/badge/twitter-@AlamofireSF-blue.svg?style=flat-square)](https://twitter.com/AlamofireSF)
[![Swift Forums](https://img.shields.io/badge/Swift_Forums-Alamofire-orange?style=flat-square)](https://forums.swift.org/c/related-projects/alamofire/37) [![Swift Forums](https://img.shields.io/badge/Swift_Forums-Alamofire-orange?style=flat-square)](https://forums.swift.org/c/related-projects/alamofire/37)
Alamofire is an HTTP networking library written in Swift. Alamofire is an HTTP networking library written in Swift.
@@ -52,6 +51,33 @@ Alamofire is an HTTP networking library written in Swift.
- [x] Comprehensive Unit and Integration Test Coverage - [x] Comprehensive Unit and Integration Test Coverage
- [x] [Complete Documentation](https://alamofire.github.io/Alamofire) - [x] [Complete Documentation](https://alamofire.github.io/Alamofire)
## Write Requests Fast!
Alamofire's compact syntax and extensive feature set allow requests with powerful features like automatic retry to be written in just a few lines of code.
```swift
// Automatic String to URL conversion, Swift concurrency support, and automatic retry.
let response = await AF.request("https://httpbin.org/get", interceptor: .retryPolicy)
// Automatic HTTP Basic Auth.
.authenticate(username: "user", password: "pass")
// Caching customization.
.cacheResponse(using: .cache)
// Redirect customization.
.redirect(using: .follow)
// Validate response code and Content-Type.
.validate()
// Produce a cURL command for the request.
.cURLDescription { description in
print(description)
}
// Automatic Decodable support with background parsing.
.serializingDecodable(DecodableType.self)
// Await the full response with metrics and a parsed body.
.response
// Detailed response description for easy debugging.
debugPrint(response)
```
## Component Libraries ## Component Libraries
In order to keep Alamofire focused specifically on core networking implementations, additional component libraries have been created by the [Alamofire Software Foundation](https://github.com/Alamofire/Foundation) to bring additional functionality to the Alamofire ecosystem. In order to keep Alamofire focused specifically on core networking implementations, additional component libraries have been created by the [Alamofire Software Foundation](https://github.com/Alamofire/Foundation) to bring additional functionality to the Alamofire ecosystem.
@@ -61,21 +87,23 @@ In order to keep Alamofire focused specifically on core networking implementatio
## Requirements ## Requirements
| Platform | Minimum Swift Version | Installation | Status | | Platform | Minimum Swift Version | Installation | Status |
| --- | --- | --- | --- | | ---------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+ | 5.5 | [CocoaPods](#cocoapods), [Carthage](#carthage), [Swift Package Manager](#swift-package-manager), [Manual](#manually) | Fully Tested | | iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+ | 5.6 | [CocoaPods](#cocoapods), [Carthage](#carthage), [Swift Package Manager](#swift-package-manager), [Manual](#manually) | Fully Tested |
| Linux | Latest Only | [Swift Package Manager](#swift-package-manager) | Building But Unsupported | | Linux | Latest Only | [Swift Package Manager](#swift-package-manager) | Building But Unsupported |
| Windows | Latest Only | [Swift Package Manager](#swift-package-manager) | Building But Unsupported | | Windows | Latest Only | [Swift Package Manager](#swift-package-manager) | Building But Unsupported |
| Android | Latest Only | [Swift Package Manager](#swift-package-manager) | Building But Unsupported |
#### Known Issues on Linux and Windows #### Known Issues on Linux and Windows
Alamofire builds on Linux and Windows but there are missing features and many issues in the underlying `swift-corelibs-foundation` that prevent full functionality and may cause crashes. These include: Alamofire builds on Linux, Windows, and Android but there are missing features and many issues in the underlying `swift-corelibs-foundation` that prevent full functionality and may cause crashes. These include:
- `ServerTrustManager` and associated certificate functionality is unavailable, so there is no certificate pinning and no client certificate support. - `ServerTrustManager` and associated certificate functionality is unavailable, so there is no certificate pinning and no client certificate support.
- Various methods of HTTP authentication may crash, including HTTP Basic and HTTP Digest. Crashes may occur if responses contain server challenges. - Various methods of HTTP authentication may crash, including HTTP Basic and HTTP Digest. Crashes may occur if responses contain server challenges.
- Cache control through `CachedResponseHandler` and associated APIs is unavailable, as the underlying delegate methods aren't called. - Cache control through `CachedResponseHandler` and associated APIs is unavailable, as the underlying delegate methods aren't called.
- `URLSessionTaskMetrics` are never gathered. - `URLSessionTaskMetrics` are never gathered.
Due to these issues, Alamofire is unsupported on Linux and Windows. Please report any crashes to the [Swift bug reporter](https://bugs.swift.org). Due to these issues, Alamofire is unsupported on Linux, Windows, and Android. Please report any crashes to the [Swift bug reporter](https://bugs.swift.org).
## Migration Guides ## Migration Guides
@@ -85,6 +113,7 @@ Due to these issues, Alamofire is unsupported on Linux and Windows. Please repor
- [Alamofire 2.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%202.0%20Migration%20Guide.md) - [Alamofire 2.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%202.0%20Migration%20Guide.md)
## Communication ## Communication
- If you **need help with making network requests** using Alamofire, use [Stack Overflow](https://stackoverflow.com/questions/tagged/alamofire) and tag `alamofire`. - If you **need help with making network requests** using Alamofire, use [Stack Overflow](https://stackoverflow.com/questions/tagged/alamofire) and tag `alamofire`.
- If you need to **find or understand an API**, check [our documentation](http://alamofire.github.io/Alamofire/) or [Apple's documentation for `URLSession`](https://developer.apple.com/documentation/foundation/url_loading_system), on top of which Alamofire is built. - If you need to **find or understand an API**, check [our documentation](http://alamofire.github.io/Alamofire/) or [Apple's documentation for `URLSession`](https://developer.apple.com/documentation/foundation/url_loading_system), on top of which Alamofire is built.
- If you need **help with an Alamofire feature**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). - If you need **help with an Alamofire feature**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire).
@@ -118,7 +147,7 @@ Once you have your Swift package set up, adding Alamofire as a dependency is as
```swift ```swift
dependencies: [ dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.6.4")) .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.8.1"))
] ]
``` ```
@@ -142,7 +171,7 @@ If you prefer not to use any of the aforementioned dependency managers, you can
- Open the new `Alamofire` folder, and drag the `Alamofire.xcodeproj` into the Project Navigator of your application's Xcode project. - Open the new `Alamofire` folder, and drag the `Alamofire.xcodeproj` into the Project Navigator of your application's Xcode project.
> It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter. > It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter.
- Select the `Alamofire.xcodeproj` in the Project Navigator and verify the deployment target matches that of your application target. - Select the `Alamofire.xcodeproj` in the Project Navigator and verify the deployment target matches that of your application target.
- Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar. - Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar.
@@ -150,11 +179,11 @@ If you prefer not to use any of the aforementioned dependency managers, you can
- Click on the `+` button under the "Embedded Binaries" section. - Click on the `+` button under the "Embedded Binaries" section.
- You will see two different `Alamofire.xcodeproj` folders each with two different versions of the `Alamofire.framework` nested inside a `Products` folder. - You will see two different `Alamofire.xcodeproj` folders each with two different versions of the `Alamofire.framework` nested inside a `Products` folder.
> It does not matter which `Products` folder you choose from, but it does matter whether you choose the top or bottom `Alamofire.framework`. > It does not matter which `Products` folder you choose from, but it does matter whether you choose the top or bottom `Alamofire.framework`.
- Select the top `Alamofire.framework` for iOS and the bottom one for macOS. - Select the top `Alamofire.framework` for iOS and the bottom one for macOS.
> You can verify which one you selected by inspecting the build log for your project. The build target for `Alamofire` will be listed as `Alamofire iOS`, `Alamofire macOS`, `Alamofire tvOS`, or `Alamofire watchOS`. > You can verify which one you selected by inspecting the build log for your project. The build target for `Alamofire` will be listed as `Alamofire iOS`, `Alamofire macOS`, `Alamofire tvOS`, or `Alamofire watchOS`.
- And that's it! - And that's it!

View File

@@ -24,6 +24,10 @@
import Foundation import Foundation
#if canImport(Security)
import Security
#endif
/// `AFError` is the error type returned by Alamofire. It encompasses a few different types of errors, each with /// `AFError` is the error type returned by Alamofire. It encompasses a few different types of errors, each with
/// their own associated reasons. /// their own associated reasons.
public enum AFError: Error { public enum AFError: Error {
@@ -129,7 +133,7 @@ public enum AFError: Error {
case invalidEmptyResponse(type: String) case invalidEmptyResponse(type: String)
} }
#if !(os(Linux) || os(Windows)) #if canImport(Security)
/// Underlying reason a server trust evaluation error occurred. /// Underlying reason a server trust evaluation error occurred.
public enum ServerTrustFailureReason { public enum ServerTrustFailureReason {
/// The output of a server trust evaluation. /// The output of a server trust evaluation.
@@ -211,7 +215,7 @@ public enum AFError: Error {
case responseValidationFailed(reason: ResponseValidationFailureReason) case responseValidationFailed(reason: ResponseValidationFailureReason)
/// Response serialization failed. /// Response serialization failed.
case responseSerializationFailed(reason: ResponseSerializationFailureReason) case responseSerializationFailed(reason: ResponseSerializationFailureReason)
#if !(os(Linux) || os(Windows)) #if canImport(Security)
/// `ServerTrustEvaluating` instance threw an error during trust evaluation. /// `ServerTrustEvaluating` instance threw an error during trust evaluation.
case serverTrustEvaluationFailed(reason: ServerTrustFailureReason) case serverTrustEvaluationFailed(reason: ServerTrustFailureReason)
#endif #endif
@@ -314,7 +318,7 @@ extension AFError {
return false return false
} }
#if !(os(Linux) || os(Windows)) #if canImport(Security)
/// Returns whether the instance is `.serverTrustEvaluationFailed`. When `true`, the `underlyingError` property will /// Returns whether the instance is `.serverTrustEvaluationFailed`. When `true`, the `underlyingError` property will
/// contain the associated value. /// contain the associated value.
public var isServerTrustEvaluationError: Bool { public var isServerTrustEvaluationError: Bool {
@@ -393,7 +397,7 @@ extension AFError {
return reason.underlyingError return reason.underlyingError
case let .responseSerializationFailed(reason): case let .responseSerializationFailed(reason):
return reason.underlyingError return reason.underlyingError
#if !(os(Linux) || os(Windows)) #if canImport(Security)
case let .serverTrustEvaluationFailed(reason): case let .serverTrustEvaluationFailed(reason):
return reason.underlyingError return reason.underlyingError
#endif #endif
@@ -451,7 +455,7 @@ extension AFError {
return destination return destination
} }
#if !(os(Linux) || os(Windows)) #if canImport(Security)
/// The download resume data of any underlying network error. Only produced by `DownloadRequest`s. /// The download resume data of any underlying network error. Only produced by `DownloadRequest`s.
public var downloadResumeData: Data? { public var downloadResumeData: Data? {
(underlyingError as? URLError)?.userInfo[NSURLSessionDownloadTaskResumeData] as? Data (underlyingError as? URLError)?.userInfo[NSURLSessionDownloadTaskResumeData] as? Data
@@ -610,7 +614,7 @@ extension AFError.ResponseSerializationFailureReason {
} }
} }
#if !(os(Linux) || os(Windows)) #if canImport(Security)
extension AFError.ServerTrustFailureReason { extension AFError.ServerTrustFailureReason {
var output: AFError.ServerTrustFailureReason.Output? { var output: AFError.ServerTrustFailureReason.Output? {
switch self { switch self {
@@ -688,7 +692,7 @@ extension AFError: LocalizedError {
""" """
case let .sessionInvalidated(error): case let .sessionInvalidated(error):
return "Session was invalidated with error: \(error?.localizedDescription ?? "No description.")" return "Session was invalidated with error: \(error?.localizedDescription ?? "No description.")"
#if !(os(Linux) || os(Windows)) #if canImport(Security)
case let .serverTrustEvaluationFailed(reason): case let .serverTrustEvaluationFailed(reason):
return "Server trust evaluation failed due to reason: \(reason.localizedDescription)" return "Server trust evaluation failed due to reason: \(reason.localizedDescription)"
#endif #endif
@@ -822,7 +826,7 @@ extension AFError.ResponseValidationFailureReason {
} }
} }
#if !(os(Linux) || os(Windows)) #if canImport(Security)
extension AFError.ServerTrustFailureReason { extension AFError.ServerTrustFailureReason {
var localizedDescription: String { var localizedDescription: String {
switch self { switch self {

View File

@@ -37,4 +37,4 @@ import Foundation
public let AF = Session.default public let AF = Session.default
/// Current Alamofire version. Necessary since SPM doesn't use dynamic libraries. Plus this will be more accurate. /// Current Alamofire version. Necessary since SPM doesn't use dynamic libraries. Plus this will be more accurate.
let version = "5.7.1" let version = "5.8.0"

View File

@@ -217,15 +217,14 @@ public class AuthenticationInterceptor<AuthenticatorType>: RequestInterceptor wh
/// The `Credential` used to authenticate requests. /// The `Credential` used to authenticate requests.
public var credential: Credential? { public var credential: Credential? {
get { $mutableState.credential } get { mutableState.credential }
set { $mutableState.credential = newValue } set { mutableState.credential = newValue }
} }
let authenticator: AuthenticatorType let authenticator: AuthenticatorType
let queue = DispatchQueue(label: "org.alamofire.authentication.inspector") let queue = DispatchQueue(label: "org.alamofire.authentication.inspector")
@Protected private let mutableState: Protected<MutableState>
private var mutableState: MutableState
// MARK: Initialization // MARK: Initialization
@@ -242,13 +241,13 @@ public class AuthenticationInterceptor<AuthenticatorType>: RequestInterceptor wh
credential: Credential? = nil, credential: Credential? = nil,
refreshWindow: RefreshWindow? = RefreshWindow()) { refreshWindow: RefreshWindow? = RefreshWindow()) {
self.authenticator = authenticator self.authenticator = authenticator
mutableState = MutableState(credential: credential, refreshWindow: refreshWindow) mutableState = Protected(MutableState(credential: credential, refreshWindow: refreshWindow))
} }
// MARK: Adapt // MARK: Adapt
public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) { public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
let adaptResult: AdaptResult = $mutableState.write { mutableState in let adaptResult: AdaptResult = mutableState.write { mutableState in
// Queue the adapt operation if a refresh is already in place. // Queue the adapt operation if a refresh is already in place.
guard !mutableState.isRefreshing else { guard !mutableState.isRefreshing else {
let operation = AdaptOperation(urlRequest: urlRequest, session: session, completion: completion) let operation = AdaptOperation(urlRequest: urlRequest, session: session, completion: completion)
@@ -316,7 +315,7 @@ public class AuthenticationInterceptor<AuthenticatorType>: RequestInterceptor wh
return return
} }
$mutableState.write { mutableState in mutableState.write { mutableState in
mutableState.requestsToRetry.append(completion) mutableState.requestsToRetry.append(completion)
guard !mutableState.isRefreshing else { return } guard !mutableState.isRefreshing else { return }
@@ -340,7 +339,7 @@ public class AuthenticationInterceptor<AuthenticatorType>: RequestInterceptor wh
// Dispatch to queue to hop out of the lock in case authenticator.refresh is implemented synchronously. // Dispatch to queue to hop out of the lock in case authenticator.refresh is implemented synchronously.
queue.async { queue.async {
self.authenticator.refresh(credential, for: session) { result in self.authenticator.refresh(credential, for: session) { result in
self.$mutableState.write { mutableState in self.mutableState.write { mutableState in
switch result { switch result {
case let .success(credential): case let .success(credential):
self.handleRefreshSuccess(credential, insideLock: &mutableState) self.handleRefreshSuccess(credential, insideLock: &mutableState)

View File

@@ -22,7 +22,7 @@
// THE SOFTWARE. // THE SOFTWARE.
// //
#if !((os(iOS) && (arch(i386) || arch(arm))) || os(Windows) || os(Linux)) #if !((os(iOS) && (arch(i386) || arch(arm))) || os(Windows) || os(Linux) || os(Android))
import Combine import Combine
import Dispatch import Dispatch
@@ -91,23 +91,22 @@ public struct DataResponsePublisher<Value>: Publisher {
where Downstream.Input == Output { where Downstream.Input == Output {
typealias Failure = Downstream.Failure typealias Failure = Downstream.Failure
@Protected private let downstream: Protected<Downstream?>
private var downstream: Downstream?
private let request: DataRequest private let request: DataRequest
private let responseHandler: Handler private let responseHandler: Handler
init(request: DataRequest, responseHandler: @escaping Handler, downstream: Downstream) { init(request: DataRequest, responseHandler: @escaping Handler, downstream: Downstream) {
self.request = request self.request = request
self.responseHandler = responseHandler self.responseHandler = responseHandler
self.downstream = downstream self.downstream = Protected(downstream)
} }
func request(_ demand: Subscribers.Demand) { func request(_ demand: Subscribers.Demand) {
assert(demand > 0) assert(demand > 0)
guard let downstream = downstream else { return } guard let downstream = downstream.read({ $0 }) else { return }
self.downstream = nil self.downstream.write(nil)
responseHandler { response in responseHandler { response in
_ = downstream.receive(response) _ = downstream.receive(response)
downstream.receive(completion: .finished) downstream.receive(completion: .finished)
@@ -116,7 +115,7 @@ public struct DataResponsePublisher<Value>: Publisher {
func cancel() { func cancel() {
request.cancel() request.cancel()
downstream = nil downstream.write(nil)
} }
} }
} }
@@ -312,23 +311,22 @@ public struct DataStreamPublisher<Value>: Publisher {
where Downstream.Input == Output { where Downstream.Input == Output {
typealias Failure = Downstream.Failure typealias Failure = Downstream.Failure
@Protected private let downstream: Protected<Downstream?>
private var downstream: Downstream?
private let request: DataStreamRequest private let request: DataStreamRequest
private let streamHandler: Handler private let streamHandler: Handler
init(request: DataStreamRequest, streamHandler: @escaping Handler, downstream: Downstream) { init(request: DataStreamRequest, streamHandler: @escaping Handler, downstream: Downstream) {
self.request = request self.request = request
self.streamHandler = streamHandler self.streamHandler = streamHandler
self.downstream = downstream self.downstream = Protected(downstream)
} }
func request(_ demand: Subscribers.Demand) { func request(_ demand: Subscribers.Demand) {
assert(demand > 0) assert(demand > 0)
guard let downstream = downstream else { return } guard let downstream = downstream.read({ $0 }) else { return }
self.downstream = nil self.downstream.write(nil)
streamHandler { stream in streamHandler { stream in
_ = downstream.receive(stream) _ = downstream.receive(stream)
if case .complete = stream.event { if case .complete = stream.event {
@@ -339,7 +337,7 @@ public struct DataStreamPublisher<Value>: Publisher {
func cancel() { func cancel() {
request.cancel() request.cancel()
downstream = nil downstream.write(nil)
} }
} }
} }
@@ -462,23 +460,22 @@ public struct DownloadResponsePublisher<Value>: Publisher {
where Downstream.Input == Output { where Downstream.Input == Output {
typealias Failure = Downstream.Failure typealias Failure = Downstream.Failure
@Protected private let downstream: Protected<Downstream?>
private var downstream: Downstream?
private let request: DownloadRequest private let request: DownloadRequest
private let responseHandler: Handler private let responseHandler: Handler
init(request: DownloadRequest, responseHandler: @escaping Handler, downstream: Downstream) { init(request: DownloadRequest, responseHandler: @escaping Handler, downstream: Downstream) {
self.request = request self.request = request
self.responseHandler = responseHandler self.responseHandler = responseHandler
self.downstream = downstream self.downstream = Protected(downstream)
} }
func request(_ demand: Subscribers.Demand) { func request(_ demand: Subscribers.Demand) {
assert(demand > 0) assert(demand > 0)
guard let downstream = downstream else { return } guard let downstream = downstream.read({ $0 }) else { return }
self.downstream = nil self.downstream.write(nil)
responseHandler { response in responseHandler { response in
_ = downstream.receive(response) _ = downstream.receive(response)
downstream.receive(completion: .finished) downstream.receive(completion: .finished)
@@ -487,7 +484,7 @@ public struct DownloadResponsePublisher<Value>: Publisher {
func cancel() { func cancel() {
request.cancel() request.cancel()
downstream = nil downstream.write(nil)
} }
} }
} }

View File

@@ -37,7 +37,7 @@ extension Request {
/// - Returns: The `StreamOf<Progress>`. /// - Returns: The `StreamOf<Progress>`.
public func uploadProgress(bufferingPolicy: StreamOf<Progress>.BufferingPolicy = .unbounded) -> StreamOf<Progress> { public func uploadProgress(bufferingPolicy: StreamOf<Progress>.BufferingPolicy = .unbounded) -> StreamOf<Progress> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
uploadProgress(queue: .singleEventQueue) { progress in uploadProgress(queue: underlyingQueue) { progress in
continuation.yield(progress) continuation.yield(progress)
} }
} }
@@ -50,7 +50,7 @@ extension Request {
/// - Returns: The `StreamOf<Progress>`. /// - Returns: The `StreamOf<Progress>`.
public func downloadProgress(bufferingPolicy: StreamOf<Progress>.BufferingPolicy = .unbounded) -> StreamOf<Progress> { public func downloadProgress(bufferingPolicy: StreamOf<Progress>.BufferingPolicy = .unbounded) -> StreamOf<Progress> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
downloadProgress(queue: .singleEventQueue) { progress in downloadProgress(queue: underlyingQueue) { progress in
continuation.yield(progress) continuation.yield(progress)
} }
} }
@@ -63,7 +63,7 @@ extension Request {
/// - Returns: The `StreamOf<URLRequest>`. /// - Returns: The `StreamOf<URLRequest>`.
public func urlRequests(bufferingPolicy: StreamOf<URLRequest>.BufferingPolicy = .unbounded) -> StreamOf<URLRequest> { public func urlRequests(bufferingPolicy: StreamOf<URLRequest>.BufferingPolicy = .unbounded) -> StreamOf<URLRequest> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
onURLRequestCreation(on: .singleEventQueue) { request in onURLRequestCreation(on: underlyingQueue) { request in
continuation.yield(request) continuation.yield(request)
} }
} }
@@ -76,7 +76,7 @@ extension Request {
/// - Returns: The `StreamOf<URLSessionTask>`. /// - Returns: The `StreamOf<URLSessionTask>`.
public func urlSessionTasks(bufferingPolicy: StreamOf<URLSessionTask>.BufferingPolicy = .unbounded) -> StreamOf<URLSessionTask> { public func urlSessionTasks(bufferingPolicy: StreamOf<URLSessionTask>.BufferingPolicy = .unbounded) -> StreamOf<URLSessionTask> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
onURLSessionTaskCreation(on: .singleEventQueue) { task in onURLSessionTaskCreation(on: underlyingQueue) { task in
continuation.yield(task) continuation.yield(task)
} }
} }
@@ -89,15 +89,15 @@ extension Request {
/// - Returns: The `StreamOf<String>`. /// - Returns: The `StreamOf<String>`.
public func cURLDescriptions(bufferingPolicy: StreamOf<String>.BufferingPolicy = .unbounded) -> StreamOf<String> { public func cURLDescriptions(bufferingPolicy: StreamOf<String>.BufferingPolicy = .unbounded) -> StreamOf<String> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
cURLDescription(on: .singleEventQueue) { description in cURLDescription(on: underlyingQueue) { description in
continuation.yield(description) continuation.yield(description)
} }
} }
} }
private func stream<T>(of type: T.Type = T.self, fileprivate func stream<T>(of type: T.Type = T.self,
bufferingPolicy: StreamOf<T>.BufferingPolicy = .unbounded, bufferingPolicy: StreamOf<T>.BufferingPolicy = .unbounded,
yielder: @escaping (StreamOf<T>.Continuation) -> Void) -> StreamOf<T> { yielder: @escaping (StreamOf<T>.Continuation) -> Void) -> StreamOf<T> {
StreamOf<T>(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in StreamOf<T>(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
yielder(continuation) yielder(continuation)
// Must come after serializers run in order to catch retry progress. // Must come after serializers run in order to catch retry progress.
@@ -168,18 +168,83 @@ public struct DataTask<Value> {
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension DataRequest { extension DataRequest {
/// Creates a `StreamOf<HTTPURLResponse>` for the instance's responses.
///
/// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default.
///
/// - Returns: The `StreamOf<HTTPURLResponse>`.
public func httpResponses(bufferingPolicy: StreamOf<HTTPURLResponse>.BufferingPolicy = .unbounded) -> StreamOf<HTTPURLResponse> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
onHTTPResponse(on: underlyingQueue) { response in
continuation.yield(response)
}
}
}
#if swift(>=5.7)
/// Sets an async closure returning a `Request.ResponseDisposition`, called whenever the `DataRequest` produces an
/// `HTTPURLResponse`.
///
/// - Note: Most requests will only produce a single response for each outgoing attempt (initial + retries).
/// However, some types of response may trigger multiple `HTTPURLResponse`s, such as multipart streams,
/// where responses after the first will contain the part headers.
///
/// - Parameters:
/// - handler: Async closure executed when a new `HTTPURLResponse` is received and returning a
/// `ResponseDisposition` value. This value determines whether to continue the request or cancel it as
/// if `cancel()` had been called on the instance. Note, this closure is called on an arbitrary thread,
/// so any synchronous calls in it will execute in that context.
///
/// - Returns: The instance.
@_disfavoredOverload
@discardableResult
public func onHTTPResponse(
perform handler: @escaping @Sendable (_ response: HTTPURLResponse) async -> ResponseDisposition
) -> Self {
onHTTPResponse(on: underlyingQueue) { response, completionHandler in
Task {
let disposition = await handler(response)
completionHandler(disposition)
}
}
return self
}
/// Sets an async closure called whenever the `DataRequest` produces an `HTTPURLResponse`.
///
/// - Note: Most requests will only produce a single response for each outgoing attempt (initial + retries).
/// However, some types of response may trigger multiple `HTTPURLResponse`s, such as multipart streams,
/// where responses after the first will contain the part headers.
///
/// - Parameters:
/// - handler: Async closure executed when a new `HTTPURLResponse` is received. Note, this closure is called on an
/// arbitrary thread, so any synchronous calls in it will execute in that context.
///
/// - Returns: The instance.
@discardableResult
public func onHTTPResponse(perform handler: @escaping @Sendable (_ response: HTTPURLResponse) async -> Void) -> Self {
onHTTPResponse { response in
await handler(response)
return .allow
}
return self
}
#endif
/// Creates a `DataTask` to `await` a `Data` value. /// Creates a `DataTask` to `await` a `Data` value.
/// ///
/// - Parameters: /// - Parameters:
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DataTask`'s async /// enclosing async context is cancelled. Only applies to `DataTask`'s async
/// properties. `false` by default. /// properties. `true` by default.
/// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before completion. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before completion.
/// - emptyResponseCodes: HTTP response codes for which empty responses are allowed. `[204, 205]` by default. /// - emptyResponseCodes: HTTP response codes for which empty responses are allowed. `[204, 205]` by default.
/// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default.
/// ///
/// - Returns: The `DataTask`. /// - Returns: The `DataTask`.
public func serializingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = false, public func serializingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = true,
dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor,
emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes, emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods) -> DataTask<Data> { emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods) -> DataTask<Data> {
@@ -195,7 +260,7 @@ extension DataRequest {
/// - type: `Decodable` type to decode from response data. /// - type: `Decodable` type to decode from response data.
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DataTask`'s async /// enclosing async context is cancelled. Only applies to `DataTask`'s async
/// properties. `false` by default. /// properties. `true` by default.
/// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer.
/// `PassthroughPreprocessor()` by default. /// `PassthroughPreprocessor()` by default.
/// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default. /// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default.
@@ -204,7 +269,7 @@ extension DataRequest {
/// ///
/// - Returns: The `DataTask`. /// - Returns: The `DataTask`.
public func serializingDecodable<Value: Decodable>(_ type: Value.Type = Value.self, public func serializingDecodable<Value: Decodable>(_ type: Value.Type = Value.self,
automaticallyCancelling shouldAutomaticallyCancel: Bool = false, automaticallyCancelling shouldAutomaticallyCancel: Bool = true,
dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<Value>.defaultDataPreprocessor, dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<Value>.defaultDataPreprocessor,
decoder: DataDecoder = JSONDecoder(), decoder: DataDecoder = JSONDecoder(),
emptyResponseCodes: Set<Int> = DecodableResponseSerializer<Value>.defaultEmptyResponseCodes, emptyResponseCodes: Set<Int> = DecodableResponseSerializer<Value>.defaultEmptyResponseCodes,
@@ -221,7 +286,7 @@ extension DataRequest {
/// - Parameters: /// - Parameters:
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DataTask`'s async /// enclosing async context is cancelled. Only applies to `DataTask`'s async
/// properties. `false` by default. /// properties. `true` by default.
/// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer.
/// `PassthroughPreprocessor()` by default. /// `PassthroughPreprocessor()` by default.
/// - encoding: `String.Encoding` to use during serialization. Defaults to `nil`, in which case /// - encoding: `String.Encoding` to use during serialization. Defaults to `nil`, in which case
@@ -231,7 +296,7 @@ extension DataRequest {
/// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default.
/// ///
/// - Returns: The `DataTask`. /// - Returns: The `DataTask`.
public func serializingString(automaticallyCancelling shouldAutomaticallyCancel: Bool = false, public func serializingString(automaticallyCancelling shouldAutomaticallyCancel: Bool = true,
dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
encoding: String.Encoding? = nil, encoding: String.Encoding? = nil,
emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes, emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
@@ -249,16 +314,16 @@ extension DataRequest {
/// - serializer: `ResponseSerializer` responsible for serializing the request, response, and data. /// - serializer: `ResponseSerializer` responsible for serializing the request, response, and data.
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DataTask`'s async /// enclosing async context is cancelled. Only applies to `DataTask`'s async
/// properties. `false` by default. /// properties. `true` by default.
/// ///
/// - Returns: The `DataTask`. /// - Returns: The `DataTask`.
public func serializingResponse<Serializer: ResponseSerializer>(using serializer: Serializer, public func serializingResponse<Serializer: ResponseSerializer>(using serializer: Serializer,
automaticallyCancelling shouldAutomaticallyCancel: Bool = false) automaticallyCancelling shouldAutomaticallyCancel: Bool = true)
-> DataTask<Serializer.SerializedObject> { -> DataTask<Serializer.SerializedObject> {
dataTask(automaticallyCancelling: shouldAutomaticallyCancel) { dataTask(automaticallyCancelling: shouldAutomaticallyCancel) { [self] in
self.response(queue: .singleEventQueue, response(queue: underlyingQueue,
responseSerializer: serializer, responseSerializer: serializer,
completionHandler: $0) completionHandler: $0)
} }
} }
@@ -269,16 +334,16 @@ extension DataRequest {
/// response, and data. /// response, and data.
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DataTask`'s async /// enclosing async context is cancelled. Only applies to `DataTask`'s async
/// properties. `false` by default. /// properties. `true` by default.
/// ///
/// - Returns: The `DataTask`. /// - Returns: The `DataTask`.
public func serializingResponse<Serializer: DataResponseSerializerProtocol>(using serializer: Serializer, public func serializingResponse<Serializer: DataResponseSerializerProtocol>(using serializer: Serializer,
automaticallyCancelling shouldAutomaticallyCancel: Bool = false) automaticallyCancelling shouldAutomaticallyCancel: Bool = true)
-> DataTask<Serializer.SerializedObject> { -> DataTask<Serializer.SerializedObject> {
dataTask(automaticallyCancelling: shouldAutomaticallyCancel) { dataTask(automaticallyCancelling: shouldAutomaticallyCancel) { [self] in
self.response(queue: .singleEventQueue, response(queue: underlyingQueue,
responseSerializer: serializer, responseSerializer: serializer,
completionHandler: $0) completionHandler: $0)
} }
} }
@@ -366,13 +431,13 @@ extension DownloadRequest {
/// - Parameters: /// - Parameters:
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DownloadTask`'s async /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async
/// properties. `false` by default. /// properties. `true` by default.
/// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before completion. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before completion.
/// - emptyResponseCodes: HTTP response codes for which empty responses are allowed. `[204, 205]` by default. /// - emptyResponseCodes: HTTP response codes for which empty responses are allowed. `[204, 205]` by default.
/// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default.
/// ///
/// - Returns: The `DownloadTask`. /// - Returns: The `DownloadTask`.
public func serializingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = false, public func serializingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = true,
dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor,
emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes, emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods) -> DownloadTask<Data> { emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods) -> DownloadTask<Data> {
@@ -390,7 +455,7 @@ extension DownloadRequest {
/// - type: `Decodable` type to decode from response data. /// - type: `Decodable` type to decode from response data.
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DownloadTask`'s async /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async
/// properties. `false` by default. /// properties. `true` by default.
/// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer.
/// `PassthroughPreprocessor()` by default. /// `PassthroughPreprocessor()` by default.
/// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default. /// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default.
@@ -399,7 +464,7 @@ extension DownloadRequest {
/// ///
/// - Returns: The `DownloadTask`. /// - Returns: The `DownloadTask`.
public func serializingDecodable<Value: Decodable>(_ type: Value.Type = Value.self, public func serializingDecodable<Value: Decodable>(_ type: Value.Type = Value.self,
automaticallyCancelling shouldAutomaticallyCancel: Bool = false, automaticallyCancelling shouldAutomaticallyCancel: Bool = true,
dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<Value>.defaultDataPreprocessor, dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<Value>.defaultDataPreprocessor,
decoder: DataDecoder = JSONDecoder(), decoder: DataDecoder = JSONDecoder(),
emptyResponseCodes: Set<Int> = DecodableResponseSerializer<Value>.defaultEmptyResponseCodes, emptyResponseCodes: Set<Int> = DecodableResponseSerializer<Value>.defaultEmptyResponseCodes,
@@ -416,10 +481,10 @@ extension DownloadRequest {
/// - Parameters: /// - Parameters:
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DownloadTask`'s async /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async
/// properties. `false` by default. /// properties. `true` by default.
/// ///
/// - Returns: The `DownloadTask`. /// - Returns: The `DownloadTask`.
public func serializingDownloadedFileURL(automaticallyCancelling shouldAutomaticallyCancel: Bool = false) -> DownloadTask<URL> { public func serializingDownloadedFileURL(automaticallyCancelling shouldAutomaticallyCancel: Bool = true) -> DownloadTask<URL> {
serializingDownload(using: URLResponseSerializer(), serializingDownload(using: URLResponseSerializer(),
automaticallyCancelling: shouldAutomaticallyCancel) automaticallyCancelling: shouldAutomaticallyCancel)
} }
@@ -429,7 +494,7 @@ extension DownloadRequest {
/// - Parameters: /// - Parameters:
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DownloadTask`'s async /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async
/// properties. `false` by default. /// properties. `true` by default.
/// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the
/// serializer. `PassthroughPreprocessor()` by default. /// serializer. `PassthroughPreprocessor()` by default.
/// - encoding: `String.Encoding` to use during serialization. Defaults to `nil`, in which case /// - encoding: `String.Encoding` to use during serialization. Defaults to `nil`, in which case
@@ -439,7 +504,7 @@ extension DownloadRequest {
/// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default.
/// ///
/// - Returns: The `DownloadTask`. /// - Returns: The `DownloadTask`.
public func serializingString(automaticallyCancelling shouldAutomaticallyCancel: Bool = false, public func serializingString(automaticallyCancelling shouldAutomaticallyCancel: Bool = true,
dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
encoding: String.Encoding? = nil, encoding: String.Encoding? = nil,
emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes, emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
@@ -457,16 +522,16 @@ extension DownloadRequest {
/// - serializer: `ResponseSerializer` responsible for serializing the request, response, and data. /// - serializer: `ResponseSerializer` responsible for serializing the request, response, and data.
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DownloadTask`'s async /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async
/// properties. `false` by default. /// properties. `true` by default.
/// ///
/// - Returns: The `DownloadTask`. /// - Returns: The `DownloadTask`.
public func serializingDownload<Serializer: ResponseSerializer>(using serializer: Serializer, public func serializingDownload<Serializer: ResponseSerializer>(using serializer: Serializer,
automaticallyCancelling shouldAutomaticallyCancel: Bool = false) automaticallyCancelling shouldAutomaticallyCancel: Bool = true)
-> DownloadTask<Serializer.SerializedObject> { -> DownloadTask<Serializer.SerializedObject> {
downloadTask(automaticallyCancelling: shouldAutomaticallyCancel) { downloadTask(automaticallyCancelling: shouldAutomaticallyCancel) { [self] in
self.response(queue: .singleEventQueue, response(queue: underlyingQueue,
responseSerializer: serializer, responseSerializer: serializer,
completionHandler: $0) completionHandler: $0)
} }
} }
@@ -478,16 +543,16 @@ extension DownloadRequest {
/// response, and data. /// response, and data.
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DownloadTask`'s async /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async
/// properties. `false` by default. /// properties. `true` by default.
/// ///
/// - Returns: The `DownloadTask`. /// - Returns: The `DownloadTask`.
public func serializingDownload<Serializer: DownloadResponseSerializerProtocol>(using serializer: Serializer, public func serializingDownload<Serializer: DownloadResponseSerializerProtocol>(using serializer: Serializer,
automaticallyCancelling shouldAutomaticallyCancel: Bool = false) automaticallyCancelling shouldAutomaticallyCancel: Bool = true)
-> DownloadTask<Serializer.SerializedObject> { -> DownloadTask<Serializer.SerializedObject> {
downloadTask(automaticallyCancelling: shouldAutomaticallyCancel) { downloadTask(automaticallyCancelling: shouldAutomaticallyCancel) { [self] in
self.response(queue: .singleEventQueue, response(queue: underlyingQueue,
responseSerializer: serializer, responseSerializer: serializer,
completionHandler: $0) completionHandler: $0)
} }
} }
@@ -625,6 +690,69 @@ public struct DataStreamTask {
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension DataStreamRequest { extension DataStreamRequest {
/// Creates a `StreamOf<HTTPURLResponse>` for the instance's responses.
///
/// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default.
///
/// - Returns: The `StreamOf<HTTPURLResponse>`.
public func httpResponses(bufferingPolicy: StreamOf<HTTPURLResponse>.BufferingPolicy = .unbounded) -> StreamOf<HTTPURLResponse> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
onHTTPResponse(on: underlyingQueue) { response in
continuation.yield(response)
}
}
}
#if swift(>=5.7)
/// Sets an async closure returning a `Request.ResponseDisposition`, called whenever the `DataStreamRequest`
/// produces an `HTTPURLResponse`.
///
/// - Note: Most requests will only produce a single response for each outgoing attempt (initial + retries).
/// However, some types of response may trigger multiple `HTTPURLResponse`s, such as multipart streams,
/// where responses after the first will contain the part headers.
///
/// - Parameters:
/// - handler: Async closure executed when a new `HTTPURLResponse` is received and returning a
/// `ResponseDisposition` value. This value determines whether to continue the request or cancel it as
/// if `cancel()` had been called on the instance. Note, this closure is called on an arbitrary thread,
/// so any synchronous calls in it will execute in that context.
///
/// - Returns: The instance.
@_disfavoredOverload
@discardableResult
public func onHTTPResponse(perform handler: @escaping @Sendable (HTTPURLResponse) async -> ResponseDisposition) -> Self {
onHTTPResponse(on: underlyingQueue) { response, completionHandler in
Task {
let disposition = await handler(response)
completionHandler(disposition)
}
}
return self
}
/// Sets an async closure called whenever the `DataStreamRequest` produces an `HTTPURLResponse`.
///
/// - Note: Most requests will only produce a single response for each outgoing attempt (initial + retries).
/// However, some types of response may trigger multiple `HTTPURLResponse`s, such as multipart streams,
/// where responses after the first will contain the part headers.
///
/// - Parameters:
/// - handler: Async closure executed when a new `HTTPURLResponse` is received. Note, this closure is called on an
/// arbitrary thread, so any synchronous calls in it will execute in that context.
///
/// - Returns: The instance.
@discardableResult
public func onHTTPResponse(perform handler: @escaping @Sendable (HTTPURLResponse) async -> Void) -> Self {
onHTTPResponse { response in
await handler(response)
return .allow
}
return self
}
#endif
/// Creates a `DataStreamTask` used to `await` streams of serialized values. /// Creates a `DataStreamTask` used to `await` streams of serialized values.
/// ///
/// - Returns: The `DataStreamTask`. /// - Returns: The `DataStreamTask`.

View File

@@ -69,6 +69,9 @@ public protocol EventMonitor {
// MARK: URLSessionDataDelegate Events // MARK: URLSessionDataDelegate Events
/// Event called during `URLSessionDataDelegate`'s `urlSession(_:dataTask:didReceive:completionHandler:)` method.
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse)
/// Event called during `URLSessionDataDelegate`'s `urlSession(_:dataTask:didReceive:)` method. /// Event called during `URLSessionDataDelegate`'s `urlSession(_:dataTask:didReceive:)` method.
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data)
@@ -244,6 +247,7 @@ extension EventMonitor {
didFinishCollecting metrics: URLSessionTaskMetrics) {} didFinishCollecting metrics: URLSessionTaskMetrics) {}
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {} public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {}
public func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {} public func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse) {}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {} public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {}
public func urlSession(_ session: URLSession, public func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask, dataTask: URLSessionDataTask,
@@ -380,6 +384,10 @@ public final class CompositeEventMonitor: EventMonitor {
performEvent { $0.urlSession(session, taskIsWaitingForConnectivity: task) } performEvent { $0.urlSession(session, taskIsWaitingForConnectivity: task) }
} }
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse) {
performEvent { $0.urlSession(session, dataTask: dataTask, didReceive: response) }
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
performEvent { $0.urlSession(session, dataTask: dataTask, didReceive: data) } performEvent { $0.urlSession(session, dataTask: dataTask, didReceive: data) }
} }
@@ -593,6 +601,9 @@ open class ClosureEventMonitor: EventMonitor {
/// Closure called on the `urlSession(_:taskIsWaitingForConnectivity:)` event. /// Closure called on the `urlSession(_:taskIsWaitingForConnectivity:)` event.
open var taskIsWaitingForConnectivity: ((URLSession, URLSessionTask) -> Void)? open var taskIsWaitingForConnectivity: ((URLSession, URLSessionTask) -> Void)?
/// Closure called on the `urlSession(_:dataTask:didReceive:completionHandler:)` event.
open var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> Void)?
/// Closure that receives the `urlSession(_:dataTask:didReceive:)` event. /// Closure that receives the `urlSession(_:dataTask:didReceive:)` event.
open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)? open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
@@ -741,6 +752,10 @@ open class ClosureEventMonitor: EventMonitor {
taskIsWaitingForConnectivity?(session, task) taskIsWaitingForConnectivity?(session, task)
} }
open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse) {
dataTaskDidReceiveResponse?(session, dataTask, response)
}
open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
dataTaskDidReceiveData?(session, dataTask, data) dataTaskDidReceiveData?(session, dataTask, data)
} }

View File

@@ -34,16 +34,12 @@ public struct HTTPHeaders {
/// Creates an instance from an array of `HTTPHeader`s. Duplicate case-insensitive names are collapsed into the last /// Creates an instance from an array of `HTTPHeader`s. Duplicate case-insensitive names are collapsed into the last
/// name and value encountered. /// name and value encountered.
public init(_ headers: [HTTPHeader]) { public init(_ headers: [HTTPHeader]) {
self.init()
headers.forEach { update($0) } headers.forEach { update($0) }
} }
/// Creates an instance from a `[String: String]`. Duplicate case-insensitive names are collapsed into the last name /// Creates an instance from a `[String: String]`. Duplicate case-insensitive names are collapsed into the last name
/// and value encountered. /// and value encountered.
public init(_ dictionary: [String: String]) { public init(_ dictionary: [String: String]) {
self.init()
dictionary.forEach { update(HTTPHeader(name: $0.key, value: $0.value)) } dictionary.forEach { update(HTTPHeader(name: $0.key, value: $0.value)) }
} }
@@ -145,8 +141,6 @@ public struct HTTPHeaders {
extension HTTPHeaders: ExpressibleByDictionaryLiteral { extension HTTPHeaders: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, String)...) { public init(dictionaryLiteral elements: (String, String)...) {
self.init()
elements.forEach { update(name: $0.0, value: $0.1) } elements.forEach { update(name: $0.0, value: $0.1) }
} }
} }
@@ -405,6 +399,8 @@ extension HTTPHeader {
return "Linux" return "Linux"
#elseif os(Windows) #elseif os(Windows)
return "Windows" return "Windows"
#elseif os(Android)
return "Android"
#else #else
return "Unknown" return "Unknown"
#endif #endif

View File

@@ -24,9 +24,9 @@
import Foundation import Foundation
#if os(iOS) || os(watchOS) || os(tvOS) #if canImport(MobileCoreServices)
import MobileCoreServices import MobileCoreServices
#elseif os(macOS) #elseif canImport(CoreServices)
import CoreServices import CoreServices
#endif #endif
@@ -213,7 +213,7 @@ open class MultipartFormData {
// Check 2 - is file URL reachable? // Check 2 - is file URL reachable?
//============================================================ //============================================================
#if !(os(Linux) || os(Windows)) #if !(os(Linux) || os(Windows) || os(Android))
do { do {
let isReachable = try fileURL.checkPromisedItemIsReachable() let isReachable = try fileURL.checkPromisedItemIsReachable()
guard isReachable else { guard isReachable else {
@@ -455,9 +455,11 @@ open class MultipartFormData {
inputStream.open() inputStream.open()
defer { inputStream.close() } defer { inputStream.close() }
while inputStream.hasBytesAvailable { var bytesLeftToRead = bodyPart.bodyContentLength
var buffer = [UInt8](repeating: 0, count: streamBufferSize) while inputStream.hasBytesAvailable && bytesLeftToRead > 0 {
let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize) let bufferSize = min(streamBufferSize, Int(bytesLeftToRead))
var buffer = [UInt8](repeating: 0, count: bufferSize)
let bytesRead = inputStream.read(&buffer, maxLength: bufferSize)
if let streamError = inputStream.streamError { if let streamError = inputStream.streamError {
throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: streamError)) throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: streamError))
@@ -469,6 +471,7 @@ open class MultipartFormData {
} }
try write(&buffer, to: outputStream) try write(&buffer, to: outputStream)
bytesLeftToRead -= UInt64(bytesRead)
} else { } else {
break break
} }
@@ -549,6 +552,19 @@ extension MultipartFormData {
// MARK: - Private - Mime Type // MARK: - Private - Mime Type
private func mimeType(forPathExtension pathExtension: String) -> String { private func mimeType(forPathExtension pathExtension: String) -> String {
#if swift(>=5.9)
if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, visionOS 1, *) {
return UTType(filenameExtension: pathExtension)?.preferredMIMEType ?? "application/octet-stream"
} else {
if
let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(),
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() {
return contentType as String
}
return "application/octet-stream"
}
#else
if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) { if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) {
return UTType(filenameExtension: pathExtension)?.preferredMIMEType ?? "application/octet-stream" return UTType(filenameExtension: pathExtension)?.preferredMIMEType ?? "application/octet-stream"
} else { } else {
@@ -560,6 +576,7 @@ extension MultipartFormData {
return "application/octet-stream" return "application/octet-stream"
} }
#endif
} }
} }
@@ -569,7 +586,7 @@ extension MultipartFormData {
// MARK: - Private - Mime Type // MARK: - Private - Mime Type
private func mimeType(forPathExtension pathExtension: String) -> String { private func mimeType(forPathExtension pathExtension: String) -> String {
#if !(os(Linux) || os(Windows)) #if canImport(CoreServices) || canImport(MobileCoreServices)
if if
let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(), let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(),
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() { let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() {

View File

@@ -28,8 +28,8 @@ import Foundation
final class MultipartUpload { final class MultipartUpload {
lazy var result = Result { try build() } lazy var result = Result { try build() }
@Protected private let multipartFormData: Protected<MultipartFormData>
private(set) var multipartFormData: MultipartFormData
let encodingMemoryThreshold: UInt64 let encodingMemoryThreshold: UInt64
let request: URLRequestConvertible let request: URLRequestConvertible
let fileManager: FileManager let fileManager: FileManager
@@ -40,13 +40,13 @@ final class MultipartUpload {
self.encodingMemoryThreshold = encodingMemoryThreshold self.encodingMemoryThreshold = encodingMemoryThreshold
self.request = request self.request = request
fileManager = multipartFormData.fileManager fileManager = multipartFormData.fileManager
self.multipartFormData = multipartFormData self.multipartFormData = Protected(multipartFormData)
} }
func build() throws -> UploadRequest.Uploadable { func build() throws -> UploadRequest.Uploadable {
let uploadable: UploadRequest.Uploadable let uploadable: UploadRequest.Uploadable
if $multipartFormData.contentLength < encodingMemoryThreshold { if multipartFormData.contentLength < encodingMemoryThreshold {
let data = try $multipartFormData.read { try $0.encode() } let data = try multipartFormData.read { try $0.encode() }
uploadable = .data(data) uploadable = .data(data)
} else { } else {
@@ -58,7 +58,7 @@ final class MultipartUpload {
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
do { do {
try $multipartFormData.read { try $0.writeEncodedData(to: fileURL) } try multipartFormData.read { try $0.writeEncodedData(to: fileURL) }
} catch { } catch {
// Cleanup after attempted write if it fails. // Cleanup after attempted write if it fails.
try? fileManager.removeItem(at: fileURL) try? fileManager.removeItem(at: fileURL)
@@ -76,7 +76,7 @@ extension MultipartUpload: UploadConvertible {
func asURLRequest() throws -> URLRequest { func asURLRequest() throws -> URLRequest {
var urlRequest = try request.asURLRequest() var urlRequest = try request.asURLRequest()
$multipartFormData.read { multipartFormData in multipartFormData.read { multipartFormData in
urlRequest.headers.add(.contentType(multipartFormData.contentType)) urlRequest.headers.add(.contentType(multipartFormData.contentType))
} }

View File

@@ -22,7 +22,7 @@
// THE SOFTWARE. // THE SOFTWARE.
// //
#if !(os(watchOS) || os(Linux) || os(Windows)) #if canImport(SystemConfiguration)
import Foundation import Foundation
import SystemConfiguration import SystemConfiguration
@@ -113,8 +113,7 @@ open class NetworkReachabilityManager {
private let reachability: SCNetworkReachability private let reachability: SCNetworkReachability
/// Protected storage for mutable state. /// Protected storage for mutable state.
@Protected private let mutableState = Protected(MutableState())
private var mutableState = MutableState()
// MARK: - Initialization // MARK: - Initialization
@@ -168,7 +167,7 @@ open class NetworkReachabilityManager {
onUpdatePerforming listener: @escaping Listener) -> Bool { onUpdatePerforming listener: @escaping Listener) -> Bool {
stopListening() stopListening()
$mutableState.write { state in mutableState.write { state in
state.listenerQueue = queue state.listenerQueue = queue
state.listener = listener state.listener = listener
} }
@@ -194,7 +193,8 @@ open class NetworkReachabilityManager {
let description = weakManager.manager?.flags?.readableDescription ?? "nil" let description = weakManager.manager?.flags?.readableDescription ?? "nil"
return Unmanaged.passRetained(description as CFString) return Unmanaged.passRetained(description as CFString)
}) }
)
let callback: SCNetworkReachabilityCallBack = { _, flags, info in let callback: SCNetworkReachabilityCallBack = { _, flags, info in
guard let info = info else { return } guard let info = info else { return }
@@ -219,7 +219,7 @@ open class NetworkReachabilityManager {
open func stopListening() { open func stopListening() {
SCNetworkReachabilitySetCallback(reachability, nil, nil) SCNetworkReachabilitySetCallback(reachability, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachability, nil) SCNetworkReachabilitySetDispatchQueue(reachability, nil)
$mutableState.write { state in mutableState.write { state in
state.listener = nil state.listener = nil
state.listenerQueue = nil state.listenerQueue = nil
state.previousStatus = nil state.previousStatus = nil
@@ -236,7 +236,7 @@ open class NetworkReachabilityManager {
func notifyListener(_ flags: SCNetworkReachabilityFlags) { func notifyListener(_ flags: SCNetworkReachabilityFlags) {
let newStatus = NetworkReachabilityStatus(flags) let newStatus = NetworkReachabilityStatus(flags)
$mutableState.write { state in mutableState.write { state in
guard state.previousStatus != newStatus else { return } guard state.previousStatus != newStatus else { return }
state.previousStatus = newStatus state.previousStatus = newStatus
@@ -266,7 +266,7 @@ extension SCNetworkReachabilityFlags {
var canConnectWithoutUserInteraction: Bool { canConnectAutomatically && !contains(.interventionRequired) } var canConnectWithoutUserInteraction: Bool { canConnectAutomatically && !contains(.interventionRequired) }
var isActuallyReachable: Bool { isReachable && (!isConnectionRequired || canConnectWithoutUserInteraction) } var isActuallyReachable: Bool { isReachable && (!isConnectionRequired || canConnectWithoutUserInteraction) }
var isCellular: Bool { var isCellular: Bool {
#if os(iOS) || os(tvOS) #if os(iOS) || os(tvOS) || (swift(>=5.9) && os(visionOS))
return contains(.isWWAN) return contains(.isWWAN)
#else #else
return false return false

View File

@@ -49,13 +49,7 @@ extension Lock {
} }
} }
#if os(Linux) || os(Windows) #if canImport(Darwin)
extension NSLock: Lock {}
#endif
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
/// An `os_unfair_lock` wrapper. /// An `os_unfair_lock` wrapper.
final class UnfairLock: Lock { final class UnfairLock: Lock {
private let unfairLock: os_unfair_lock_t private let unfairLock: os_unfair_lock_t
@@ -78,41 +72,35 @@ final class UnfairLock: Lock {
os_unfair_lock_unlock(unfairLock) os_unfair_lock_unlock(unfairLock)
} }
} }
#elseif canImport(Foundation)
extension NSLock: Lock {}
#else
#error("This platform needs a Lock-conforming type without Foundation.")
#endif #endif
/// A thread-safe wrapper around a value. /// A thread-safe wrapper around a value.
@propertyWrapper
@dynamicMemberLookup @dynamicMemberLookup
final class Protected<T> { final class Protected<Value> {
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) #if canImport(Darwin)
private let lock = UnfairLock() private let lock = UnfairLock()
#elseif os(Linux) || os(Windows) #elseif canImport(Foundation)
private let lock = NSLock() private let lock = NSLock()
#else
#error("This platform needs a Lock-conforming type without Foundation.")
#endif #endif
private var value: T private var value: Value
init(_ value: T) { init(_ value: Value) {
self.value = value self.value = value
} }
/// The contained value. Unsafe for anything more than direct read or write.
var wrappedValue: T {
get { lock.around { value } }
set { lock.around { value = newValue } }
}
var projectedValue: Protected<T> { self }
init(wrappedValue: T) {
value = wrappedValue
}
/// Synchronously read or transform the contained value. /// Synchronously read or transform the contained value.
/// ///
/// - Parameter closure: The closure to execute. /// - Parameter closure: The closure to execute.
/// ///
/// - Returns: The return value of the closure passed. /// - Returns: The return value of the closure passed.
func read<U>(_ closure: (T) throws -> U) rethrows -> U { func read<U>(_ closure: (Value) throws -> U) rethrows -> U {
try lock.around { try closure(self.value) } try lock.around { try closure(self.value) }
} }
@@ -122,21 +110,28 @@ final class Protected<T> {
/// ///
/// - Returns: The modified value. /// - Returns: The modified value.
@discardableResult @discardableResult
func write<U>(_ closure: (inout T) throws -> U) rethrows -> U { func write<U>(_ closure: (inout Value) throws -> U) rethrows -> U {
try lock.around { try closure(&self.value) } try lock.around { try closure(&self.value) }
} }
subscript<Property>(dynamicMember keyPath: WritableKeyPath<T, Property>) -> Property { /// Synchronously update the protected value.
///
/// - Parameter value: The `Value`.
func write(_ value: Value) {
write { $0 = value }
}
subscript<Property>(dynamicMember keyPath: WritableKeyPath<Value, Property>) -> Property {
get { lock.around { value[keyPath: keyPath] } } get { lock.around { value[keyPath: keyPath] } }
set { lock.around { value[keyPath: keyPath] = newValue } } set { lock.around { value[keyPath: keyPath] = newValue } }
} }
subscript<Property>(dynamicMember keyPath: KeyPath<T, Property>) -> Property { subscript<Property>(dynamicMember keyPath: KeyPath<Value, Property>) -> Property {
lock.around { value[keyPath: keyPath] } lock.around { value[keyPath: keyPath] }
} }
} }
extension Protected where T == Request.MutableState { extension Protected where Value == Request.MutableState {
/// Attempts to transition to the passed `State`. /// Attempts to transition to the passed `State`.
/// ///
/// - Parameter state: The `State` to attempt transition to. /// - Parameter state: The `State` to attempt transition to.
@@ -159,3 +154,15 @@ extension Protected where T == Request.MutableState {
lock.around { perform(value.state) } lock.around { perform(value.state) }
} }
} }
extension Protected: Equatable where Value: Equatable {
static func ==(lhs: Protected<Value>, rhs: Protected<Value>) -> Bool {
lhs.read { left in rhs.read { right in left == right }}
}
}
extension Protected: Hashable where Value: Hashable {
func hash(into hasher: inout Hasher) {
read { hasher.combine($0) }
}
}

View File

@@ -125,11 +125,10 @@ public class Request {
} }
/// Protected `MutableState` value that provides thread-safe access to state values. /// Protected `MutableState` value that provides thread-safe access to state values.
@Protected fileprivate let mutableState = Protected(MutableState())
fileprivate var mutableState = MutableState()
/// `State` of the `Request`. /// `State` of the `Request`.
public var state: State { $mutableState.state } public var state: State { mutableState.state }
/// Returns whether `state` is `.initialized`. /// Returns whether `state` is `.initialized`.
public var isInitialized: Bool { state == .initialized } public var isInitialized: Bool { state == .initialized }
/// Returns whether `state is `.resumed`. /// Returns whether `state is `.resumed`.
@@ -152,50 +151,49 @@ public class Request {
public let downloadProgress = Progress(totalUnitCount: 0) public let downloadProgress = Progress(totalUnitCount: 0)
/// `ProgressHandler` called when `uploadProgress` is updated, on the provided `DispatchQueue`. /// `ProgressHandler` called when `uploadProgress` is updated, on the provided `DispatchQueue`.
private var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? { private var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? {
get { $mutableState.uploadProgressHandler } get { mutableState.uploadProgressHandler }
set { $mutableState.uploadProgressHandler = newValue } set { mutableState.uploadProgressHandler = newValue }
} }
/// `ProgressHandler` called when `downloadProgress` is updated, on the provided `DispatchQueue`. /// `ProgressHandler` called when `downloadProgress` is updated, on the provided `DispatchQueue`.
fileprivate var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? { fileprivate var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? {
get { $mutableState.downloadProgressHandler } get { mutableState.downloadProgressHandler }
set { $mutableState.downloadProgressHandler = newValue } set { mutableState.downloadProgressHandler = newValue }
} }
// MARK: Redirect Handling // MARK: Redirect Handling
/// `RedirectHandler` set on the instance. /// `RedirectHandler` set on the instance.
public private(set) var redirectHandler: RedirectHandler? { public private(set) var redirectHandler: RedirectHandler? {
get { $mutableState.redirectHandler } get { mutableState.redirectHandler }
set { $mutableState.redirectHandler = newValue } set { mutableState.redirectHandler = newValue }
} }
// MARK: Cached Response Handling // MARK: Cached Response Handling
/// `CachedResponseHandler` set on the instance. /// `CachedResponseHandler` set on the instance.
public private(set) var cachedResponseHandler: CachedResponseHandler? { public private(set) var cachedResponseHandler: CachedResponseHandler? {
get { $mutableState.cachedResponseHandler } get { mutableState.cachedResponseHandler }
set { $mutableState.cachedResponseHandler = newValue } set { mutableState.cachedResponseHandler = newValue }
} }
// MARK: URLCredential // MARK: URLCredential
/// `URLCredential` used for authentication challenges. Created by calling one of the `authenticate` methods. /// `URLCredential` used for authentication challenges. Created by calling one of the `authenticate` methods.
public private(set) var credential: URLCredential? { public private(set) var credential: URLCredential? {
get { $mutableState.credential } get { mutableState.credential }
set { $mutableState.credential = newValue } set { mutableState.credential = newValue }
} }
// MARK: Validators // MARK: Validators
/// `Validator` callback closures that store the validation calls enqueued. /// `Validator` callback closures that store the validation calls enqueued.
@Protected fileprivate let validators = Protected<[() -> Void]>([])
fileprivate var validators: [() -> Void] = []
// MARK: URLRequests // MARK: URLRequests
/// All `URLRequests` created on behalf of the `Request`, including original and adapted requests. /// All `URLRequests` created on behalf of the `Request`, including original and adapted requests.
public var requests: [URLRequest] { $mutableState.requests } public var requests: [URLRequest] { mutableState.requests }
/// First `URLRequest` created on behalf of the `Request`. May not be the first one actually executed. /// First `URLRequest` created on behalf of the `Request`. May not be the first one actually executed.
public var firstRequest: URLRequest? { requests.first } public var firstRequest: URLRequest? { requests.first }
/// Last `URLRequest` created on behalf of the `Request`. /// Last `URLRequest` created on behalf of the `Request`.
@@ -205,7 +203,7 @@ public class Request {
/// `URLRequest`s from all of the `URLSessionTask`s executed on behalf of the `Request`. May be different from /// `URLRequest`s from all of the `URLSessionTask`s executed on behalf of the `Request`. May be different from
/// `requests` due to `URLSession` manipulation. /// `requests` due to `URLSession` manipulation.
public var performedRequests: [URLRequest] { $mutableState.read { $0.tasks.compactMap(\.currentRequest) } } public var performedRequests: [URLRequest] { mutableState.read { $0.tasks.compactMap(\.currentRequest) } }
// MARK: HTTPURLResponse // MARK: HTTPURLResponse
@@ -216,7 +214,7 @@ public class Request {
// MARK: Tasks // MARK: Tasks
/// All `URLSessionTask`s created on behalf of the `Request`. /// All `URLSessionTask`s created on behalf of the `Request`.
public var tasks: [URLSessionTask] { $mutableState.tasks } public var tasks: [URLSessionTask] { mutableState.tasks }
/// First `URLSessionTask` created on behalf of the `Request`. /// First `URLSessionTask` created on behalf of the `Request`.
public var firstTask: URLSessionTask? { tasks.first } public var firstTask: URLSessionTask? { tasks.first }
/// Last `URLSessionTask` created on behalf of the `Request`. /// Last `URLSessionTask` created on behalf of the `Request`.
@@ -227,7 +225,7 @@ public class Request {
// MARK: Metrics // MARK: Metrics
/// All `URLSessionTaskMetrics` gathered on behalf of the `Request`. Should correspond to the `tasks` created. /// All `URLSessionTaskMetrics` gathered on behalf of the `Request`. Should correspond to the `tasks` created.
public var allMetrics: [URLSessionTaskMetrics] { $mutableState.metrics } public var allMetrics: [URLSessionTaskMetrics] { mutableState.metrics }
/// First `URLSessionTaskMetrics` gathered on behalf of the `Request`. /// First `URLSessionTaskMetrics` gathered on behalf of the `Request`.
public var firstMetrics: URLSessionTaskMetrics? { allMetrics.first } public var firstMetrics: URLSessionTaskMetrics? { allMetrics.first }
/// Last `URLSessionTaskMetrics` gathered on behalf of the `Request`. /// Last `URLSessionTaskMetrics` gathered on behalf of the `Request`.
@@ -238,14 +236,14 @@ public class Request {
// MARK: Retry Count // MARK: Retry Count
/// Number of times the `Request` has been retried. /// Number of times the `Request` has been retried.
public var retryCount: Int { $mutableState.retryCount } public var retryCount: Int { mutableState.retryCount }
// MARK: Error // MARK: Error
/// `Error` returned from Alamofire internally, from the network request directly, or any validators executed. /// `Error` returned from Alamofire internally, from the network request directly, or any validators executed.
public fileprivate(set) var error: AFError? { public fileprivate(set) var error: AFError? {
get { $mutableState.error } get { mutableState.error }
set { $mutableState.error = newValue } set { mutableState.error = newValue }
} }
/// Default initializer for the `Request` superclass. /// Default initializer for the `Request` superclass.
@@ -283,7 +281,7 @@ public class Request {
func didCreateInitialURLRequest(_ request: URLRequest) { func didCreateInitialURLRequest(_ request: URLRequest) {
dispatchPrecondition(condition: .onQueue(underlyingQueue)) dispatchPrecondition(condition: .onQueue(underlyingQueue))
$mutableState.write { $0.requests.append(request) } mutableState.write { $0.requests.append(request) }
eventMonitor?.request(self, didCreateInitialURLRequest: request) eventMonitor?.request(self, didCreateInitialURLRequest: request)
} }
@@ -313,7 +311,7 @@ public class Request {
func didAdaptInitialRequest(_ initialRequest: URLRequest, to adaptedRequest: URLRequest) { func didAdaptInitialRequest(_ initialRequest: URLRequest, to adaptedRequest: URLRequest) {
dispatchPrecondition(condition: .onQueue(underlyingQueue)) dispatchPrecondition(condition: .onQueue(underlyingQueue))
$mutableState.write { $0.requests.append(adaptedRequest) } mutableState.write { $0.requests.append(adaptedRequest) }
eventMonitor?.request(self, didAdaptInitialRequest: initialRequest, to: adaptedRequest) eventMonitor?.request(self, didAdaptInitialRequest: initialRequest, to: adaptedRequest)
} }
@@ -343,7 +341,7 @@ public class Request {
func didCreateURLRequest(_ request: URLRequest) { func didCreateURLRequest(_ request: URLRequest) {
dispatchPrecondition(condition: .onQueue(underlyingQueue)) dispatchPrecondition(condition: .onQueue(underlyingQueue))
$mutableState.read { state in mutableState.read { state in
state.urlRequestHandler?.queue.async { state.urlRequestHandler?.handler(request) } state.urlRequestHandler?.queue.async { state.urlRequestHandler?.handler(request) }
} }
@@ -354,7 +352,7 @@ public class Request {
/// Asynchronously calls any stored `cURLHandler` and then removes it from `mutableState`. /// Asynchronously calls any stored `cURLHandler` and then removes it from `mutableState`.
private func callCURLHandlerIfNecessary() { private func callCURLHandlerIfNecessary() {
$mutableState.write { mutableState in mutableState.write { mutableState in
guard let cURLHandler = mutableState.cURLHandler else { return } guard let cURLHandler = mutableState.cURLHandler else { return }
cURLHandler.queue.async { cURLHandler.handler(self.cURLDescription()) } cURLHandler.queue.async { cURLHandler.handler(self.cURLDescription()) }
@@ -369,7 +367,7 @@ public class Request {
func didCreateTask(_ task: URLSessionTask) { func didCreateTask(_ task: URLSessionTask) {
dispatchPrecondition(condition: .onQueue(underlyingQueue)) dispatchPrecondition(condition: .onQueue(underlyingQueue))
$mutableState.write { state in mutableState.write { state in
state.tasks.append(task) state.tasks.append(task)
guard let urlSessionTaskHandler = state.urlSessionTaskHandler else { return } guard let urlSessionTaskHandler = state.urlSessionTaskHandler else { return }
@@ -416,7 +414,9 @@ public class Request {
func didCancel() { func didCancel() {
dispatchPrecondition(condition: .onQueue(underlyingQueue)) dispatchPrecondition(condition: .onQueue(underlyingQueue))
error = error ?? AFError.explicitlyCancelled mutableState.write { mutableState in
mutableState.error = mutableState.error ?? AFError.explicitlyCancelled
}
eventMonitor?.requestDidCancel(self) eventMonitor?.requestDidCancel(self)
} }
@@ -436,7 +436,7 @@ public class Request {
func didGatherMetrics(_ metrics: URLSessionTaskMetrics) { func didGatherMetrics(_ metrics: URLSessionTaskMetrics) {
dispatchPrecondition(condition: .onQueue(underlyingQueue)) dispatchPrecondition(condition: .onQueue(underlyingQueue))
$mutableState.write { $0.metrics.append(metrics) } mutableState.write { $0.metrics.append(metrics) }
eventMonitor?.request(self, didGatherMetrics: metrics) eventMonitor?.request(self, didGatherMetrics: metrics)
} }
@@ -468,6 +468,7 @@ public class Request {
self.error = self.error ?? error self.error = self.error ?? error
let validators = validators.read { $0 }
validators.forEach { $0() } validators.forEach { $0() }
eventMonitor?.request(self, didCompleteTask: task, with: error) eventMonitor?.request(self, didCompleteTask: task, with: error)
@@ -479,7 +480,7 @@ public class Request {
func prepareForRetry() { func prepareForRetry() {
dispatchPrecondition(condition: .onQueue(underlyingQueue)) dispatchPrecondition(condition: .onQueue(underlyingQueue))
$mutableState.write { $0.retryCount += 1 } mutableState.write { $0.retryCount += 1 }
reset() reset()
@@ -513,9 +514,9 @@ public class Request {
func finish(error: AFError? = nil) { func finish(error: AFError? = nil) {
dispatchPrecondition(condition: .onQueue(underlyingQueue)) dispatchPrecondition(condition: .onQueue(underlyingQueue))
guard !$mutableState.isFinishing else { return } guard !mutableState.isFinishing else { return }
$mutableState.isFinishing = true mutableState.isFinishing = true
if let error = error { self.error = error } if let error = error { self.error = error }
@@ -531,7 +532,7 @@ public class Request {
/// ///
/// - Parameter closure: The closure containing the response serialization call. /// - Parameter closure: The closure containing the response serialization call.
func appendResponseSerializer(_ closure: @escaping () -> Void) { func appendResponseSerializer(_ closure: @escaping () -> Void) {
$mutableState.write { mutableState in mutableState.write { mutableState in
mutableState.responseSerializers.append(closure) mutableState.responseSerializers.append(closure)
if mutableState.state == .finished { if mutableState.state == .finished {
@@ -554,7 +555,7 @@ public class Request {
func nextResponseSerializer() -> (() -> Void)? { func nextResponseSerializer() -> (() -> Void)? {
var responseSerializer: (() -> Void)? var responseSerializer: (() -> Void)?
$mutableState.write { mutableState in mutableState.write { mutableState in
let responseSerializerIndex = mutableState.responseSerializerCompletions.count let responseSerializerIndex = mutableState.responseSerializerCompletions.count
if responseSerializerIndex < mutableState.responseSerializers.count { if responseSerializerIndex < mutableState.responseSerializers.count {
@@ -571,7 +572,7 @@ public class Request {
// Execute all response serializer completions and clear them // Execute all response serializer completions and clear them
var completions: [() -> Void] = [] var completions: [() -> Void] = []
$mutableState.write { mutableState in mutableState.write { mutableState in
completions = mutableState.responseSerializerCompletions completions = mutableState.responseSerializerCompletions
// Clear out all response serializers and response serializer completions in mutable state since the // Clear out all response serializers and response serializer completions in mutable state since the
@@ -605,7 +606,7 @@ public class Request {
/// - Parameter completion: The completion handler provided with the response serializer, called when all serializers /// - Parameter completion: The completion handler provided with the response serializer, called when all serializers
/// are complete. /// are complete.
func responseSerializerDidComplete(completion: @escaping () -> Void) { func responseSerializerDidComplete(completion: @escaping () -> Void) {
$mutableState.write { $0.responseSerializerCompletions.append(completion) } mutableState.write { $0.responseSerializerCompletions.append(completion) }
processNextResponseSerializer() processNextResponseSerializer()
} }
@@ -618,7 +619,7 @@ public class Request {
downloadProgress.totalUnitCount = 0 downloadProgress.totalUnitCount = 0
downloadProgress.completedUnitCount = 0 downloadProgress.completedUnitCount = 0
$mutableState.write { state in mutableState.write { state in
state.isFinishing = false state.isFinishing = false
state.responseSerializerCompletions = [] state.responseSerializerCompletions = []
} }
@@ -640,7 +641,7 @@ public class Request {
/// ///
/// - Parameter perform: The closure to perform. /// - Parameter perform: The closure to perform.
func withState(perform: (State) -> Void) { func withState(perform: (State) -> Void) {
$mutableState.withState(perform: perform) mutableState.withState(perform: perform)
} }
// MARK: Task Creation // MARK: Task Creation
@@ -667,7 +668,7 @@ public class Request {
/// - Returns: The instance. /// - Returns: The instance.
@discardableResult @discardableResult
public func cancel() -> Self { public func cancel() -> Self {
$mutableState.write { mutableState in mutableState.write { mutableState in
guard mutableState.state.canTransitionTo(.cancelled) else { return } guard mutableState.state.canTransitionTo(.cancelled) else { return }
mutableState.state = .cancelled mutableState.state = .cancelled
@@ -693,7 +694,7 @@ public class Request {
/// - Returns: The instance. /// - Returns: The instance.
@discardableResult @discardableResult
public func suspend() -> Self { public func suspend() -> Self {
$mutableState.write { mutableState in mutableState.write { mutableState in
guard mutableState.state.canTransitionTo(.suspended) else { return } guard mutableState.state.canTransitionTo(.suspended) else { return }
mutableState.state = .suspended mutableState.state = .suspended
@@ -714,7 +715,7 @@ public class Request {
/// - Returns: The instance. /// - Returns: The instance.
@discardableResult @discardableResult
public func resume() -> Self { public func resume() -> Self {
$mutableState.write { mutableState in mutableState.write { mutableState in
guard mutableState.state.canTransitionTo(.resumed) else { return } guard mutableState.state.canTransitionTo(.resumed) else { return }
mutableState.state = .resumed mutableState.state = .resumed
@@ -754,7 +755,7 @@ public class Request {
/// - Returns: The instance. /// - Returns: The instance.
@discardableResult @discardableResult
public func authenticate(with credential: URLCredential) -> Self { public func authenticate(with credential: URLCredential) -> Self {
$mutableState.credential = credential mutableState.credential = credential
return self return self
} }
@@ -770,7 +771,7 @@ public class Request {
/// - Returns: The instance. /// - Returns: The instance.
@discardableResult @discardableResult
public func downloadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self { public func downloadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self {
$mutableState.downloadProgressHandler = (handler: closure, queue: queue) mutableState.downloadProgressHandler = (handler: closure, queue: queue)
return self return self
} }
@@ -786,7 +787,7 @@ public class Request {
/// - Returns: The instance. /// - Returns: The instance.
@discardableResult @discardableResult
public func uploadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self { public func uploadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self {
$mutableState.uploadProgressHandler = (handler: closure, queue: queue) mutableState.uploadProgressHandler = (handler: closure, queue: queue)
return self return self
} }
@@ -802,7 +803,7 @@ public class Request {
/// - Returns: The instance. /// - Returns: The instance.
@discardableResult @discardableResult
public func redirect(using handler: RedirectHandler) -> Self { public func redirect(using handler: RedirectHandler) -> Self {
$mutableState.write { mutableState in mutableState.write { mutableState in
precondition(mutableState.redirectHandler == nil, "Redirect handler has already been set.") precondition(mutableState.redirectHandler == nil, "Redirect handler has already been set.")
mutableState.redirectHandler = handler mutableState.redirectHandler = handler
} }
@@ -821,7 +822,7 @@ public class Request {
/// - Returns: The instance. /// - Returns: The instance.
@discardableResult @discardableResult
public func cacheResponse(using handler: CachedResponseHandler) -> Self { public func cacheResponse(using handler: CachedResponseHandler) -> Self {
$mutableState.write { mutableState in mutableState.write { mutableState in
precondition(mutableState.cachedResponseHandler == nil, "Cached response handler has already been set.") precondition(mutableState.cachedResponseHandler == nil, "Cached response handler has already been set.")
mutableState.cachedResponseHandler = handler mutableState.cachedResponseHandler = handler
} }
@@ -842,7 +843,7 @@ public class Request {
/// - Returns: The instance. /// - Returns: The instance.
@discardableResult @discardableResult
public func cURLDescription(on queue: DispatchQueue, calling handler: @escaping (String) -> Void) -> Self { public func cURLDescription(on queue: DispatchQueue, calling handler: @escaping (String) -> Void) -> Self {
$mutableState.write { mutableState in mutableState.write { mutableState in
if mutableState.requests.last != nil { if mutableState.requests.last != nil {
queue.async { handler(self.cURLDescription()) } queue.async { handler(self.cURLDescription()) }
} else { } else {
@@ -863,13 +864,7 @@ public class Request {
/// - Returns: The instance. /// - Returns: The instance.
@discardableResult @discardableResult
public func cURLDescription(calling handler: @escaping (String) -> Void) -> Self { public func cURLDescription(calling handler: @escaping (String) -> Void) -> Self {
$mutableState.write { mutableState in cURLDescription(on: underlyingQueue, calling: handler)
if mutableState.requests.last != nil {
underlyingQueue.async { handler(self.cURLDescription()) }
} else {
mutableState.cURLHandler = (underlyingQueue, handler)
}
}
return self return self
} }
@@ -885,7 +880,7 @@ public class Request {
/// - Returns: The instance. /// - Returns: The instance.
@discardableResult @discardableResult
public func onURLRequestCreation(on queue: DispatchQueue = .main, perform handler: @escaping (URLRequest) -> Void) -> Self { public func onURLRequestCreation(on queue: DispatchQueue = .main, perform handler: @escaping (URLRequest) -> Void) -> Self {
$mutableState.write { state in mutableState.write { state in
if let request = state.requests.last { if let request = state.requests.last {
queue.async { handler(request) } queue.async { handler(request) }
} }
@@ -909,7 +904,7 @@ public class Request {
/// - Returns: The instance. /// - Returns: The instance.
@discardableResult @discardableResult
public func onURLSessionTaskCreation(on queue: DispatchQueue = .main, perform handler: @escaping (URLSessionTask) -> Void) -> Self { public func onURLSessionTaskCreation(on queue: DispatchQueue = .main, perform handler: @escaping (URLSessionTask) -> Void) -> Self {
$mutableState.write { state in mutableState.write { state in
if let task = state.tasks.last { if let task = state.tasks.last {
queue.async { handler(task) } queue.async { handler(task) }
} }
@@ -928,19 +923,37 @@ public class Request {
func onFinish(perform finishHandler: @escaping () -> Void) { func onFinish(perform finishHandler: @escaping () -> Void) {
guard !isFinished else { finishHandler(); return } guard !isFinished else { finishHandler(); return }
$mutableState.write { state in mutableState.write { state in
state.finishHandlers.append(finishHandler) state.finishHandlers.append(finishHandler)
} }
} }
/// Final cleanup step executed when the instance finishes response serialization. /// Final cleanup step executed when the instance finishes response serialization.
func cleanup() { func cleanup() {
delegate?.cleanup(after: self) let handlers = mutableState.finishHandlers
let handlers = $mutableState.finishHandlers
handlers.forEach { $0() } handlers.forEach { $0() }
$mutableState.write { state in mutableState.write { state in
state.finishHandlers.removeAll() state.finishHandlers.removeAll()
} }
delegate?.cleanup(after: self)
}
}
extension Request {
/// Type indicating how a `DataRequest` or `DataStreamRequest` should proceed after receiving an `HTTPURLResponse`.
public enum ResponseDisposition {
/// Allow the request to continue normally.
case allow
/// Cancel the request, similar to calling `cancel()`.
case cancel
var sessionDisposition: URLSession.ResponseDisposition {
switch self {
case .allow: return .allow
case .cancel: return .cancel
}
}
} }
} }
@@ -1085,11 +1098,16 @@ public class DataRequest: Request {
/// `URLRequestConvertible` value used to create `URLRequest`s for this instance. /// `URLRequestConvertible` value used to create `URLRequest`s for this instance.
public let convertible: URLRequestConvertible public let convertible: URLRequestConvertible
/// `Data` read from the server so far. /// `Data` read from the server so far.
public var data: Data? { mutableData } public var data: Data? { dataMutableState.data }
/// Protected storage for the `Data` read by the instance. private struct DataMutableState {
@Protected var data: Data?
private var mutableData: Data? = nil var httpResponseHandler: (queue: DispatchQueue,
handler: (_ response: HTTPURLResponse,
_ completionHandler: @escaping (ResponseDisposition) -> Void) -> Void)?
}
private let dataMutableState = Protected(DataMutableState())
/// Creates a `DataRequest` using the provided parameters. /// Creates a `DataRequest` using the provided parameters.
/// ///
@@ -1122,7 +1140,9 @@ public class DataRequest: Request {
override func reset() { override func reset() {
super.reset() super.reset()
mutableData = nil dataMutableState.write { mutableState in
mutableState.data = nil
}
} }
/// Called when `Data` is received by this instance. /// Called when `Data` is received by this instance.
@@ -1131,15 +1151,41 @@ public class DataRequest: Request {
/// ///
/// - Parameter data: The `Data` received. /// - Parameter data: The `Data` received.
func didReceive(data: Data) { func didReceive(data: Data) {
if self.data == nil { dataMutableState.write { mutableState in
mutableData = data if mutableState.data == nil {
} else { mutableState.data = data
$mutableData.write { $0?.append(data) } } else {
mutableState.data?.append(data)
}
} }
updateDownloadProgress() updateDownloadProgress()
} }
func didReceiveResponse(_ response: HTTPURLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
dataMutableState.read { dataMutableState in
guard let httpResponseHandler = dataMutableState.httpResponseHandler else {
underlyingQueue.async { completionHandler(.allow) }
return
}
httpResponseHandler.queue.async {
httpResponseHandler.handler(response) { disposition in
if disposition == .cancel {
self.mutableState.write { mutableState in
mutableState.state = .cancelled
mutableState.error = mutableState.error ?? AFError.explicitlyCancelled
}
}
self.underlyingQueue.async {
completionHandler(disposition.sessionDisposition)
}
}
}
}
}
override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
let copiedRequest = request let copiedRequest = request
return session.dataTask(with: copiedRequest) return session.dataTask(with: copiedRequest)
@@ -1179,7 +1225,48 @@ public class DataRequest: Request {
withResult: result) withResult: result)
} }
$validators.write { $0.append(validator) } validators.write { $0.append(validator) }
return self
}
/// Sets a closure called whenever the `DataRequest` produces an `HTTPURLResponse` and providing a completion
/// handler to return a `ResponseDisposition` value.
///
/// - Parameters:
/// - queue: `DispatchQueue` on which the closure will be called. `.main` by default.
/// - handler: Closure called when the instance produces an `HTTPURLResponse`. The `completionHandler` provided
/// MUST be called, otherwise the request will never complete.
///
/// - Returns: The instance.
@_disfavoredOverload
@discardableResult
public func onHTTPResponse(
on queue: DispatchQueue = .main,
perform handler: @escaping (_ response: HTTPURLResponse,
_ completionHandler: @escaping (ResponseDisposition) -> Void) -> Void
) -> Self {
dataMutableState.write { mutableState in
mutableState.httpResponseHandler = (queue, handler)
}
return self
}
/// Sets a closure called whenever the `DataRequest` produces an `HTTPURLResponse`.
///
/// - Parameters:
/// - queue: `DispatchQueue` on which the closure will be called. `.main` by default.
/// - handler: Closure called when the instance produces an `HTTPURLResponse`.
///
/// - Returns: The instance.
@discardableResult
public func onHTTPResponse(on queue: DispatchQueue = .main,
perform handler: @escaping (HTTPURLResponse) -> Void) -> Self {
onHTTPResponse(on: queue) { response, completionHandler in
handler(response)
completionHandler(.allow)
}
return self return self
} }
@@ -1259,10 +1346,13 @@ public final class DataStreamRequest: Request {
var numberOfExecutingStreams = 0 var numberOfExecutingStreams = 0
/// Completion calls enqueued while streams are still executing. /// Completion calls enqueued while streams are still executing.
var enqueuedCompletionEvents: [() -> Void] = [] var enqueuedCompletionEvents: [() -> Void] = []
/// Handler for any `HTTPURLResponse`s received.
var httpResponseHandler: (queue: DispatchQueue,
handler: (_ response: HTTPURLResponse,
_ completionHandler: @escaping (ResponseDisposition) -> Void) -> Void)?
} }
@Protected let streamMutableState = Protected(StreamMutableState())
var streamMutableState = StreamMutableState()
/// Creates a `DataStreamRequest` using the provided parameters. /// Creates a `DataStreamRequest` using the provided parameters.
/// ///
@@ -1306,7 +1396,7 @@ public final class DataStreamRequest: Request {
} }
override func finish(error: AFError? = nil) { override func finish(error: AFError? = nil) {
$streamMutableState.write { state in streamMutableState.write { state in
state.outputStream?.close() state.outputStream?.close()
} }
@@ -1314,8 +1404,8 @@ public final class DataStreamRequest: Request {
} }
func didReceive(data: Data) { func didReceive(data: Data) {
$streamMutableState.write { state in streamMutableState.write { state in
#if !(os(Linux) || os(Windows)) #if !canImport(FoundationNetworking) // If we not using swift-corelibs-foundation.
if let stream = state.outputStream { if let stream = state.outputStream {
underlyingQueue.async { underlyingQueue.async {
var bytes = Array(data) var bytes = Array(data)
@@ -1329,6 +1419,30 @@ public final class DataStreamRequest: Request {
} }
} }
func didReceiveResponse(_ response: HTTPURLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
streamMutableState.read { dataMutableState in
guard let httpResponseHandler = dataMutableState.httpResponseHandler else {
underlyingQueue.async { completionHandler(.allow) }
return
}
httpResponseHandler.queue.async {
httpResponseHandler.handler(response) { disposition in
if disposition == .cancel {
self.mutableState.write { mutableState in
mutableState.state = .cancelled
mutableState.error = mutableState.error ?? AFError.explicitlyCancelled
}
}
self.underlyingQueue.async {
completionHandler(disposition.sessionDisposition)
}
}
}
}
}
/// Validates the `URLRequest` and `HTTPURLResponse` received for the instance using the provided `Validation` closure. /// Validates the `URLRequest` and `HTTPURLResponse` received for the instance using the provided `Validation` closure.
/// ///
/// - Parameter validation: `Validation` closure used to validate the request and response. /// - Parameter validation: `Validation` closure used to validate the request and response.
@@ -1351,12 +1465,12 @@ public final class DataStreamRequest: Request {
withResult: result) withResult: result)
} }
$validators.write { $0.append(validator) } validators.write { $0.append(validator) }
return self return self
} }
#if !(os(Linux) || os(Windows)) #if !canImport(FoundationNetworking) // If we not using swift-corelibs-foundation.
/// Produces an `InputStream` that receives the `Data` received by the instance. /// Produces an `InputStream` that receives the `Data` received by the instance.
/// ///
/// - Note: The `InputStream` produced by this method must have `open()` called before being able to read `Data`. /// - Note: The `InputStream` produced by this method must have `open()` called before being able to read `Data`.
@@ -1370,7 +1484,7 @@ public final class DataStreamRequest: Request {
defer { resume() } defer { resume() }
var inputStream: InputStream? var inputStream: InputStream?
$streamMutableState.write { state in streamMutableState.write { state in
Foundation.Stream.getBoundStreams(withBufferSize: bufferSize, Foundation.Stream.getBoundStreams(withBufferSize: bufferSize,
inputStream: &inputStream, inputStream: &inputStream,
outputStream: &state.outputStream) outputStream: &state.outputStream)
@@ -1381,6 +1495,47 @@ public final class DataStreamRequest: Request {
} }
#endif #endif
/// Sets a closure called whenever the `DataRequest` produces an `HTTPURLResponse` and providing a completion
/// handler to return a `ResponseDisposition` value.
///
/// - Parameters:
/// - queue: `DispatchQueue` on which the closure will be called. `.main` by default.
/// - handler: Closure called when the instance produces an `HTTPURLResponse`. The `completionHandler` provided
/// MUST be called, otherwise the request will never complete.
///
/// - Returns: The instance.
@_disfavoredOverload
@discardableResult
public func onHTTPResponse(
on queue: DispatchQueue = .main,
perform handler: @escaping (_ response: HTTPURLResponse,
_ completionHandler: @escaping (ResponseDisposition) -> Void) -> Void
) -> Self {
streamMutableState.write { mutableState in
mutableState.httpResponseHandler = (queue, handler)
}
return self
}
/// Sets a closure called whenever the `DataRequest` produces an `HTTPURLResponse`.
///
/// - Parameters:
/// - queue: `DispatchQueue` on which the closure will be called. `.main` by default.
/// - handler: Closure called when the instance produces an `HTTPURLResponse`.
///
/// - Returns: The instance.
@discardableResult
public func onHTTPResponse(on queue: DispatchQueue = .main,
perform handler: @escaping (HTTPURLResponse) -> Void) -> Self {
onHTTPResponse(on: queue) { response, completionHandler in
handler(response)
completionHandler(.allow)
}
return self
}
func capturingError(from closure: () throws -> Void) { func capturingError(from closure: () throws -> Void) {
do { do {
try closure() try closure()
@@ -1395,7 +1550,7 @@ public final class DataStreamRequest: Request {
appendResponseSerializer { appendResponseSerializer {
self.underlyingQueue.async { self.underlyingQueue.async {
self.responseSerializerDidComplete { self.responseSerializerDidComplete {
self.$streamMutableState.write { state in self.streamMutableState.write { state in
guard state.numberOfExecutingStreams == 0 else { guard state.numberOfExecutingStreams == 0 else {
state.enqueuedCompletionEvents.append { state.enqueuedCompletionEvents.append {
self.enqueueCompletion(on: queue, stream: stream) self.enqueueCompletion(on: queue, stream: stream)
@@ -1546,23 +1701,22 @@ public class DownloadRequest: Request {
} }
/// Protected mutable state specific to `DownloadRequest`. /// Protected mutable state specific to `DownloadRequest`.
@Protected private let mutableDownloadState = Protected(DownloadRequestMutableState())
private var mutableDownloadState = DownloadRequestMutableState()
/// If the download is resumable and is eventually cancelled or fails, this value may be used to resume the download /// If the download is resumable and is eventually cancelled or fails, this value may be used to resume the download
/// using the `download(resumingWith data:)` API. /// using the `download(resumingWith data:)` API.
/// ///
/// - Note: For more information about `resumeData`, see [Apple's documentation](https://developer.apple.com/documentation/foundation/urlsessiondownloadtask/1411634-cancel). /// - Note: For more information about `resumeData`, see [Apple's documentation](https://developer.apple.com/documentation/foundation/urlsessiondownloadtask/1411634-cancel).
public var resumeData: Data? { public var resumeData: Data? {
#if !(os(Linux) || os(Windows)) #if !canImport(FoundationNetworking) // If we not using swift-corelibs-foundation.
return $mutableDownloadState.resumeData ?? error?.downloadResumeData return mutableDownloadState.resumeData ?? error?.downloadResumeData
#else #else
return $mutableDownloadState.resumeData return mutableDownloadState.resumeData
#endif #endif
} }
/// If the download is successful, the `URL` where the file was downloaded. /// If the download is successful, the `URL` where the file was downloaded.
public var fileURL: URL? { $mutableDownloadState.fileURL } public var fileURL: URL? { mutableDownloadState.fileURL }
// MARK: Initial State // MARK: Initial State
@@ -1605,7 +1759,7 @@ public class DownloadRequest: Request {
override func reset() { override func reset() {
super.reset() super.reset()
$mutableDownloadState.write { mutableDownloadState.write {
$0.resumeData = nil $0.resumeData = nil
$0.fileURL = nil $0.fileURL = nil
} }
@@ -1620,7 +1774,7 @@ public class DownloadRequest: Request {
eventMonitor?.request(self, didFinishDownloadingUsing: task, with: result) eventMonitor?.request(self, didFinishDownloadingUsing: task, with: result)
switch result { switch result {
case let .success(url): $mutableDownloadState.fileURL = url case let .success(url): mutableDownloadState.fileURL = url
case let .failure(error): self.error = error case let .failure(error): self.error = error
} }
} }
@@ -1698,7 +1852,7 @@ public class DownloadRequest: Request {
/// ///
/// - Returns: The instance. /// - Returns: The instance.
private func cancel(optionallyProducingResumeData completionHandler: ((_ resumeData: Data?) -> Void)?) -> Self { private func cancel(optionallyProducingResumeData completionHandler: ((_ resumeData: Data?) -> Void)?) -> Self {
$mutableState.write { mutableState in mutableState.write { mutableState in
guard mutableState.state.canTransitionTo(.cancelled) else { return } guard mutableState.state.canTransitionTo(.cancelled) else { return }
mutableState.state = .cancelled mutableState.state = .cancelled
@@ -1714,7 +1868,7 @@ public class DownloadRequest: Request {
// Resume to ensure metrics are gathered. // Resume to ensure metrics are gathered.
task.resume() task.resume()
task.cancel { resumeData in task.cancel { resumeData in
self.$mutableDownloadState.resumeData = resumeData self.mutableDownloadState.resumeData = resumeData
self.underlyingQueue.async { self.didCancelTask(task) } self.underlyingQueue.async { self.didCancelTask(task) }
completionHandler(resumeData) completionHandler(resumeData)
} }
@@ -1754,7 +1908,7 @@ public class DownloadRequest: Request {
withResult: result) withResult: result)
} }
$validators.write { $0.append(validator) } validators.write { $0.append(validator) }
return self return self
} }

View File

@@ -131,7 +131,7 @@ struct RequestTaskMap {
switch (events.completed, events.metricsGathered) { switch (events.completed, events.metricsGathered) {
case (true, _): fatalError("RequestTaskMap consistency error: duplicate completionReceivedForTask call.") case (true, _): fatalError("RequestTaskMap consistency error: duplicate completionReceivedForTask call.")
#if os(Linux) // Linux doesn't gather metrics, so unconditionally remove the reference and return true. #if os(Linux) || os(Android) // Linux doesn't gather metrics, so unconditionally remove the reference and return true.
default: self[task] = nil; return true default: self[task] = nil; return true
#else #else
case (false, false): case (false, false):

View File

@@ -1153,7 +1153,7 @@ extension DataStreamRequest {
} }
} }
$streamMutableState.write { $0.streams.append(parser) } streamMutableState.write { $0.streams.append(parser) }
appendStreamCompletion(on: queue, stream: stream) appendStreamCompletion(on: queue, stream: stream)
return self return self
@@ -1195,7 +1195,7 @@ extension DataStreamRequest {
} }
} }
$streamMutableState.write { $0.streams.append(parser) } streamMutableState.write { $0.streams.append(parser) }
appendStreamCompletion(on: queue, stream: stream) appendStreamCompletion(on: queue, stream: stream)
return self return self
@@ -1230,14 +1230,14 @@ extension DataStreamRequest {
} }
} }
$streamMutableState.write { $0.streams.append(parser) } streamMutableState.write { $0.streams.append(parser) }
appendStreamCompletion(on: queue, stream: stream) appendStreamCompletion(on: queue, stream: stream)
return self return self
} }
private func updateAndCompleteIfPossible() { private func updateAndCompleteIfPossible() {
$streamMutableState.write { state in streamMutableState.write { state in
state.numberOfExecutingStreams -= 1 state.numberOfExecutingStreams -= 1
guard state.numberOfExecutingStreams == 0, !state.enqueuedCompletionEvents.isEmpty else { return } guard state.numberOfExecutingStreams == 0, !state.enqueuedCompletionEvents.isEmpty else { return }

View File

@@ -1,5 +1,5 @@
// //
// ServerTrustPolicy.swift // ServerTrustEvaluation.swift
// //
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
// //
@@ -48,7 +48,7 @@ open class ServerTrustManager {
self.evaluators = evaluators self.evaluators = evaluators
} }
#if !(os(Linux) || os(Windows)) #if canImport(Security)
/// Returns the `ServerTrustEvaluating` value for the given host, if one is set. /// Returns the `ServerTrustEvaluating` value for the given host, if one is set.
/// ///
/// By default, this method will return the policy that perfectly matches the given host. Subclasses could override /// By default, this method will return the policy that perfectly matches the given host. Subclasses could override
@@ -75,8 +75,8 @@ open class ServerTrustManager {
/// A protocol describing the API used to evaluate server trusts. /// A protocol describing the API used to evaluate server trusts.
public protocol ServerTrustEvaluating { public protocol ServerTrustEvaluating {
#if os(Linux) || os(Windows) #if !canImport(Security)
// Implement this once Linux/Windows has API for evaluating server trusts. // Implement this once other platforms have API for evaluating server trusts.
#else #else
/// Evaluates the given `SecTrust` value for the given `host`. /// Evaluates the given `SecTrust` value for the given `host`.
/// ///
@@ -91,7 +91,7 @@ public protocol ServerTrustEvaluating {
// MARK: - Server Trust Evaluators // MARK: - Server Trust Evaluators
#if !(os(Linux) || os(Windows)) #if canImport(Security)
/// An evaluator which uses the default server trust evaluation while allowing you to control whether to validate the /// An evaluator which uses the default server trust evaluation while allowing you to control whether to validate the
/// host provided by the challenge. Applications are encouraged to always validate the host in production environments /// host provided by the challenge. Applications are encouraged to always validate the host in production environments
/// to guarantee the validity of the server's certificate chain. /// to guarantee the validity of the server's certificate chain.
@@ -181,6 +181,15 @@ public final class RevocationTrustEvaluator: ServerTrustEvaluating {
try trust.af.performValidation(forHost: host) try trust.af.performValidation(forHost: host)
} }
#if swift(>=5.9)
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, visionOS 1, *) {
try trust.af.evaluate(afterApplying: SecPolicy.af.revocation(options: options))
} else {
try trust.af.validate(policy: SecPolicy.af.revocation(options: options)) { status, result in
AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options))
}
}
#else
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) { if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
try trust.af.evaluate(afterApplying: SecPolicy.af.revocation(options: options)) try trust.af.evaluate(afterApplying: SecPolicy.af.revocation(options: options))
} else { } else {
@@ -188,6 +197,7 @@ public final class RevocationTrustEvaluator: ServerTrustEvaluating {
AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options)) AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options))
} }
} }
#endif
} }
} }
@@ -355,10 +365,8 @@ public final class PublicKeysTrustEvaluator: ServerTrustEvaluating {
let pinnedKeysInServerKeys: Bool = { let pinnedKeysInServerKeys: Bool = {
for serverPublicKey in trust.af.publicKeys { for serverPublicKey in trust.af.publicKeys {
for pinnedPublicKey in keys { if keys.contains(serverPublicKey) {
if serverPublicKey == pinnedPublicKey { return true
return true
}
} }
} }
return false return false
@@ -449,7 +457,7 @@ public final class DisabledTrustEvaluator: ServerTrustEvaluating {
// MARK: - Extensions // MARK: - Extensions
extension Array where Element == ServerTrustEvaluating { extension Array where Element == ServerTrustEvaluating {
#if os(Linux) || os(Windows) #if os(Linux) || os(Windows) || os(Android)
// Add this same convenience method for Linux/Windows. // Add this same convenience method for Linux/Windows.
#else #else
/// Evaluates the given `SecTrust` value for the given `host`. /// Evaluates the given `SecTrust` value for the given `host`.
@@ -598,7 +606,15 @@ extension AlamofireExtension where ExtendedType == SecTrust {
/// The `SecCertificate`s contained in `self`. /// The `SecCertificate`s contained in `self`.
public var certificates: [SecCertificate] { public var certificates: [SecCertificate] {
#if swift(>=5.5.1) // Xcode 13.1 / 2021 SDKs. #if swift(>=5.9)
if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, visionOS 1, *) {
return (SecTrustCopyCertificateChain(type) as? [SecCertificate]) ?? []
} else {
return (0..<SecTrustGetCertificateCount(type)).compactMap { index in
SecTrustGetCertificateAtIndex(type, index)
}
}
#elseif swift(>=5.5.1) // Xcode 13.1 / 2021 SDKs.
if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
return (SecTrustCopyCertificateChain(type) as? [SecCertificate]) ?? [] return (SecTrustCopyCertificateChain(type) as? [SecCertificate]) ?? []
} else { } else {
@@ -623,6 +639,15 @@ extension AlamofireExtension where ExtendedType == SecTrust {
/// - Parameter host: The hostname, used only in the error output if validation fails. /// - Parameter host: The hostname, used only in the error output if validation fails.
/// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.defaultEvaluationFailed` reason. /// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.defaultEvaluationFailed` reason.
public func performDefaultValidation(forHost host: String) throws { public func performDefaultValidation(forHost host: String) throws {
#if swift(>=5.9)
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, visionOS 1, *) {
try evaluate(afterApplying: SecPolicy.af.default)
} else {
try validate(policy: SecPolicy.af.default) { status, result in
AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, type, status, result)))
}
}
#else
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) { if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
try evaluate(afterApplying: SecPolicy.af.default) try evaluate(afterApplying: SecPolicy.af.default)
} else { } else {
@@ -630,6 +655,7 @@ extension AlamofireExtension where ExtendedType == SecTrust {
AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, type, status, result))) AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, type, status, result)))
} }
} }
#endif
} }
/// Validates `self` after applying `SecPolicy.af.hostname(host)`, which performs the default validation as well as /// Validates `self` after applying `SecPolicy.af.hostname(host)`, which performs the default validation as well as
@@ -638,6 +664,15 @@ extension AlamofireExtension where ExtendedType == SecTrust {
/// - Parameter host: The hostname to use in the validation. /// - Parameter host: The hostname to use in the validation.
/// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.defaultEvaluationFailed` reason. /// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.defaultEvaluationFailed` reason.
public func performValidation(forHost host: String) throws { public func performValidation(forHost host: String) throws {
#if swift(>=5.9)
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, visionOS 1, *) {
try evaluate(afterApplying: SecPolicy.af.hostname(host))
} else {
try validate(policy: SecPolicy.af.hostname(host)) { status, result in
AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, type, status, result)))
}
}
#else
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) { if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
try evaluate(afterApplying: SecPolicy.af.hostname(host)) try evaluate(afterApplying: SecPolicy.af.hostname(host))
} else { } else {
@@ -645,6 +680,7 @@ extension AlamofireExtension where ExtendedType == SecTrust {
AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, type, status, result))) AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, type, status, result)))
} }
} }
#endif
} }
} }
@@ -704,11 +740,19 @@ extension AlamofireExtension where ExtendedType == SecCertificate {
guard let createdTrust = trust, trustCreationStatus == errSecSuccess else { return nil } guard let createdTrust = trust, trustCreationStatus == errSecSuccess else { return nil }
#if swift(>=5.9)
if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, visionOS 1, *) {
return SecTrustCopyKey(createdTrust)
} else {
return SecTrustCopyPublicKey(createdTrust)
}
#else
if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) { if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) {
return SecTrustCopyKey(createdTrust) return SecTrustCopyKey(createdTrust)
} else { } else {
return SecTrustCopyPublicKey(createdTrust) return SecTrustCopyPublicKey(createdTrust)
} }
#endif
} }
} }

View File

@@ -94,7 +94,7 @@ extension SessionDelegate: URLSessionTaskDelegate {
case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest, NSURLAuthenticationMethodNTLM, case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest, NSURLAuthenticationMethodNTLM,
NSURLAuthenticationMethodNegotiate: NSURLAuthenticationMethodNegotiate:
evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task) evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task)
#if !(os(Linux) || os(Windows)) #if canImport(Security)
case NSURLAuthenticationMethodServerTrust: case NSURLAuthenticationMethodServerTrust:
evaluation = attemptServerTrustAuthentication(with: challenge) evaluation = attemptServerTrustAuthentication(with: challenge)
case NSURLAuthenticationMethodClientCertificate: case NSURLAuthenticationMethodClientCertificate:
@@ -111,7 +111,7 @@ extension SessionDelegate: URLSessionTaskDelegate {
completionHandler(evaluation.disposition, evaluation.credential) completionHandler(evaluation.disposition, evaluation.credential)
} }
#if !(os(Linux) || os(Windows)) #if canImport(Security)
/// Evaluates the server trust `URLAuthenticationChallenge` received. /// Evaluates the server trust `URLAuthenticationChallenge` received.
/// ///
/// - Parameter challenge: The `URLAuthenticationChallenge`. /// - Parameter challenge: The `URLAuthenticationChallenge`.
@@ -230,6 +230,25 @@ extension SessionDelegate: URLSessionTaskDelegate {
// MARK: URLSessionDataDelegate // MARK: URLSessionDataDelegate
extension SessionDelegate: URLSessionDataDelegate { extension SessionDelegate: URLSessionDataDelegate {
open func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: response)
guard let response = response as? HTTPURLResponse else { completionHandler(.allow); return }
if let request = request(for: dataTask, as: DataRequest.self) {
request.didReceiveResponse(response, completionHandler: completionHandler)
} else if let request = request(for: dataTask, as: DataStreamRequest.self) {
request.didReceiveResponse(response, completionHandler: completionHandler)
} else {
assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive response")
completionHandler(.allow)
return
}
}
open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data) eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data)
@@ -238,7 +257,7 @@ extension SessionDelegate: URLSessionDataDelegate {
} else if let request = request(for: dataTask, as: DataStreamRequest.self) { } else if let request = request(for: dataTask, as: DataStreamRequest.self) {
request.didReceive(data: data) request.didReceive(data: data)
} else { } else {
assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive") assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive data")
return return
} }
} }

View File

@@ -695,6 +695,74 @@ extension _URLEncodedFormEncoder.KeyedContainer: KeyedEncodingContainerProtocol
try encode(nilValue, forKey: key) try encode(nilValue, forKey: key)
} }
func encodeIfPresent(_ value: Bool?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: String?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Double?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Float?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Int?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Int8?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Int16?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Int32?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Int64?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: UInt?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: UInt8?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: UInt16?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: UInt32?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: UInt64?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent<Value>(_ value: Value?, forKey key: Key) throws where Value: Encodable {
try _encodeIfPresent(value, forKey: key)
}
func _encodeIfPresent<Value>(_ value: Value?, forKey key: Key) throws where Value: Encodable {
if let value = value {
try encode(value, forKey: key)
} else {
try encodeNil(forKey: key)
}
}
func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable { func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
var container = nestedSingleValueEncoder(for: key) var container = nestedSingleValueEncoder(for: key)
try container.encode(value) try container.encode(value)

View File

@@ -17,52 +17,76 @@ extension DDAlamofire {
public init() {} public init() {}
} }
public enum DDError : Error {
case allError
}
// MARK: example // MARK: example
public func get<T:Decodable,P:Encodable>(urlString:String,parameters:P? = DDParameters(),encoding:ParameterEncoder = URLEncodedFormParameterEncoder.default,headers:HTTPHeaders? = nil,responseType:T.Type = T.self) -> Single<T?> { public func get<T:Decodable,P:Encodable>(urlString:String,parameters:P? = DDParameters(),encoding:ParameterEncoder = URLEncodedFormParameterEncoder.default,headers:HTTPHeaders? = nil,responseType:T.Type = T.self,completionHandler: (((SingleEvent<T?>) -> Void,AFDataResponse<T>) -> Void)? = nil) -> Single<T?> {
return Single<T?>.create {[weak self] single in return Single<T?>.create {[weak self] single in
self?.request(urlString: urlString, method: .get,parameters: parameters,encoding: encoding,headers: headers,responseType: responseType,completionHandler: {[weak self] response in self?.request(urlString: urlString, method: .get,parameters: parameters,encoding: encoding,headers: headers,responseType: responseType,completionHandler: {[weak self] response in
self?.logInfo(parameters: parameters, response: response) self?.logInfo(parameters: parameters, response: response)
single(.success(response.value)) if let completionHandler {
completionHandler(single,response)
}else{
single(.success(response.value))
}
}) })
return Disposables.create() return Disposables.create()
} }
} }
public func post<T:Decodable,P:Encodable>(urlString:String,parameters:P? = DDParameters(),encoding:ParameterEncoder = URLEncodedFormParameterEncoder.default,headers:HTTPHeaders? = nil,responseType:T.Type = T.self) -> Single<T?> { public func post<T:Decodable,P:Encodable>(urlString:String,parameters:P? = DDParameters(),encoding:ParameterEncoder = URLEncodedFormParameterEncoder.default,headers:HTTPHeaders? = nil,responseType:T.Type = T.self,completionHandler: (((SingleEvent<T?>) -> Void,AFDataResponse<T>) -> Void)? = nil) -> Single<T?> {
return Single<T?>.create {[weak self] single in return Single<T?>.create {[weak self] single in
self?.request(urlString: urlString, method: .post,parameters: parameters,encoding: encoding,headers: headers,responseType: responseType,completionHandler: {[weak self] response in self?.request(urlString: urlString, method: .post,parameters: parameters,encoding: encoding,headers: headers,responseType: responseType,completionHandler: {[weak self] response in
self?.logInfo(parameters: parameters, response: response) self?.logInfo(parameters: parameters, response: response)
single(.success(response.value)) if let completionHandler {
completionHandler(single,response)
}else{
single(.success(response.value))
}
}) })
return Disposables.create() return Disposables.create()
} }
} }
public func get<T:Decodable>(urlString:String,parameters:[String:Any]? = nil,encoding:ParameterEncoding = URLEncoding.default,headers:HTTPHeaders? = nil,responseType:T.Type = T.self) -> Single<T?> { public func get<T:Decodable>(urlString:String,parameters:[String:Any]? = nil,encoding:ParameterEncoding = URLEncoding.default,headers:HTTPHeaders? = nil,responseType:T.Type = T.self,completionHandler: (((SingleEvent<T?>) -> Void,AFDataResponse<T>) -> Void)? = nil) -> Single<T?> {
return Single<T?>.create {[weak self] single in return Single<T?>.create {[weak self] single in
self?.request(urlString: urlString, method: .get,parameters: parameters,encoding: encoding,headers: headers,responseType: responseType,completionHandler: {[weak self] response in self?.request(urlString: urlString, method: .get,parameters: parameters,encoding: encoding,headers: headers,responseType: responseType,completionHandler: {[weak self] response in
self?.logInfo(parameters: parameters, response: response) self?.logInfo(parameters: parameters, response: response)
single(.success(response.value)) if let completionHandler {
completionHandler(single,response)
}else{
single(.success(response.value))
}
}) })
return Disposables.create() return Disposables.create()
} }
} }
public func post<T:Decodable>(urlString:String,parameters:[String:Any]? = nil,encoding:ParameterEncoding = URLEncoding.default,headers:HTTPHeaders? = nil,responseType:T.Type = T.self) -> Single<T?> { public func post<T:Decodable>(urlString:String,parameters:[String:Any]? = nil,encoding:ParameterEncoding = URLEncoding.default,headers:HTTPHeaders? = nil,responseType:T.Type = T.self,completionHandler: (((SingleEvent<T?>) -> Void,AFDataResponse<T>) -> Void)? = nil) -> Single<T?> {
return Single<T?>.create {[weak self] single in return Single<T?>.create {[weak self] single in
self?.request(urlString: urlString, method: .post,parameters: parameters,encoding: encoding,headers: headers,responseType: responseType,completionHandler: {[weak self] response in self?.request(urlString: urlString, method: .post,parameters: parameters,encoding: encoding,headers: headers,responseType: responseType,completionHandler: {[weak self] response in
self?.logInfo(parameters: parameters, response: response) self?.logInfo(parameters: parameters, response: response)
single(.success(response.value)) if let completionHandler {
completionHandler(single,response)
}else{
single(.success(response.value))
}
}) })
return Disposables.create() return Disposables.create()
} }
} }
public func upload<T:Decodable>(urlString:String,headers:HTTPHeaders? = nil,responseType:T.Type = T.self,multipartFormData: @escaping (MultipartFormData) -> Void,uploadProgress: @escaping (Progress) -> Void) -> Single<T?> { public func upload<T:Decodable>(urlString:String,headers:HTTPHeaders? = nil,responseType:T.Type = T.self,multipartFormData: @escaping (MultipartFormData) -> Void,uploadProgress: @escaping (Progress) -> Void,completionHandler: (((SingleEvent<T?>) -> Void,AFDataResponse<T>) -> Void)? = nil) -> Single<T?> {
return Single<T?>.create {[weak self] single in return Single<T?>.create {[weak self] single in
self?.upload(urlString: urlString,method: .post,headers: headers,responseType: responseType,multipartFormData: multipartFormData,uploadProgress: uploadProgress,completionHandler: { response in self?.upload(urlString: urlString,method: .post,headers: headers,responseType: responseType,multipartFormData: multipartFormData,uploadProgress: uploadProgress,completionHandler: { response in
self?.logInfo(parameters: nil, response: response) self?.logInfo(parameters: nil, response: response)
single(.success(response.value)) if let completionHandler {
completionHandler(single,response)
}else{
single(.success(response.value))
}
}) })
return Disposables.create() return Disposables.create()
} }

8
Pods/Manifest.lock generated
View File

@@ -1,5 +1,5 @@
PODS: PODS:
- Alamofire (5.7.1) - Alamofire (5.8.1)
- AMapFoundation-NO-IDFA (1.8.2) - AMapFoundation-NO-IDFA (1.8.2)
- AMapLocation-NO-IDFA (2.9.0): - AMapLocation-NO-IDFA (2.9.0):
- AMapFoundation-NO-IDFA (>= 1.7.0) - AMapFoundation-NO-IDFA (>= 1.7.0)
@@ -270,7 +270,7 @@ PODS:
- DDLogKit_Private - DDLogKit_Private
- DDMAMapKit_Private/DDMAUtil (0.1.5): - DDMAMapKit_Private/DDMAUtil (0.1.5):
- DDMAMapKit_Private/DDMAMap - DDMAMapKit_Private/DDMAMap
- DDNetworkingOfAlamofireKit_Private (0.1.8): - DDNetworkingOfAlamofireKit_Private (0.2.1):
- Alamofire - Alamofire
- DDLogKit_Private - DDLogKit_Private
- RxSwift - RxSwift
@@ -412,7 +412,7 @@ SPEC REPOS:
- ZLPhotoBrowser - ZLPhotoBrowser
SPEC CHECKSUMS: SPEC CHECKSUMS:
Alamofire: 0123a34370cb170936ae79a8df46cc62b2edeb88 Alamofire: 3ca42e259043ee0dc5c0cdd76c4bc568b8e42af7
AMapFoundation-NO-IDFA: 6ce0ef596d4eb8d934ff498e56747b6de1247b05 AMapFoundation-NO-IDFA: 6ce0ef596d4eb8d934ff498e56747b6de1247b05
AMapLocation-NO-IDFA: 6839d1543b3138ae594ddd36ab72741dc87df66f AMapLocation-NO-IDFA: 6839d1543b3138ae594ddd36ab72741dc87df66f
AMapNavi-NO-IDFA: 70c724400376bfadcb8ec08b9761f526096cfdb6 AMapNavi-NO-IDFA: 70c724400376bfadcb8ec08b9761f526096cfdb6
@@ -430,7 +430,7 @@ SPEC CHECKSUMS:
DDFontKit_Private: 7b8f4ebf0f60622874036202734d8460dc7b3806 DDFontKit_Private: 7b8f4ebf0f60622874036202734d8460dc7b3806
DDLogKit_Private: 1ed442cc7be004bd05f27bfda9b525e113df54e0 DDLogKit_Private: 1ed442cc7be004bd05f27bfda9b525e113df54e0
DDMAMapKit_Private: b378d69f693d6998d136155cd5c81be2e4545fae DDMAMapKit_Private: b378d69f693d6998d136155cd5c81be2e4545fae
DDNetworkingOfAlamofireKit_Private: d65c96f99bc59311d374e7b7a7e8a9e042d9b5ea DDNetworkingOfAlamofireKit_Private: 652eb70a7d8bac81d77d036fabeb52f807120f6d
DDPersistenceKit_Private: c150822543ffa6ece3900178629812f64902ed90 DDPersistenceKit_Private: c150822543ffa6ece3900178629812f64902ed90
DDProgressHUDKit_Private: 1e219062ddeb7801a4bb13b367efa1f3fbf17f1e DDProgressHUDKit_Private: 1e219062ddeb7801a4bb13b367efa1f3fbf17f1e
DDTimerSwiftKit_Private: cce3fe58b1b581fe4cddb3fb84fcde31b4e83541 DDTimerSwiftKit_Private: cce3fe58b1b581fe4cddb3fb84fcde31b4e83541