Skip to content

Commit

Permalink
Revert "Remove Combine extensions"
Browse files Browse the repository at this point in the history
This reverts commit 7e82d4b.

# Conflicts:
#	Moya.podspec
#	Moya.xcodeproj/project.pbxproj
  • Loading branch information
MaxDesiatov committed Apr 1, 2020
1 parent 71180ee commit 5644997
Show file tree
Hide file tree
Showing 7 changed files with 605 additions and 11 deletions.
17 changes: 7 additions & 10 deletions Examples/Basic/ViewController.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import UIKit
import Moya
import Combine

class ViewController: UITableViewController {
var progressView = UIView()
var repos = NSArray()
var cancellable: AnyCancellable?

override func viewDidLoad() {
super.viewDidLoad()
Expand Down Expand Up @@ -39,16 +41,11 @@ class ViewController: UITableViewController {
}

func downloadZen() {
gitHubProvider.request(.zen) { result in
var message = "Couldn't access API"

if case let .success(response) = result {
let jsonString = try? response.mapString()
message = jsonString ?? message
}

self.showAlert("Zen", message: message)
}
cancellable = gitHubProvider.requestPublisher(.zen)
.mapString()
.sink(receiveCompletion: { _ in }, receiveValue: { message in
self.showAlert("Zen", message: message)
})
}

func uploadGiphy() {
Expand Down
3 changes: 2 additions & 1 deletion Moya.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ Pod::Spec.new do |s|
s.default_subspec = "Core"
s.swift_version = '5.1'
s.cocoapods_version = '>= 1.4.0'
s.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '-weak_framework Combine' }

s.subspec "Core" do |ss|
ss.source_files = "Sources/Moya/", "Sources/Moya/Plugins/"
ss.source_files = "Sources/Moya/", "Sources/Moya/Combine", "Sources/Moya/Plugins/"
ss.dependency "Alamofire", "~> 5.0"
ss.framework = "Foundation"
end
Expand Down
30 changes: 30 additions & 0 deletions Moya.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
83B0E400A7562256224CB7FF /* AccessTokenPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC115388D44D0DB7A753E9BB /* AccessTokenPlugin.swift */; };
85850A4122CF5AB50089E731 /* NimbleHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85850A4022CF5AB50089E731 /* NimbleHelpers.swift */; };
85B2DBBC221C69620098F59A /* PropertyListEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B2DBBB221C69620098F59A /* PropertyListEncoding.swift */; };
85C98000231D2D4000AAAFB2 /* MoyaProvider+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C97FFF231D2D4000AAAFB2 /* MoyaProvider+Combine.swift */; };
85C98002231D3EC700AAAFB2 /* MoyaProvider+CombineSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C98001231D3EC700AAAFB2 /* MoyaProvider+CombineSpec.swift */; };
85C98006231D41E400AAAFB2 /* MoyaPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C98005231D41E400AAAFB2 /* MoyaPublisher.swift */; };
85C98008231D4BE900AAAFB2 /* AnyPublisher+Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C98007231D4BE900AAAFB2 /* AnyPublisher+Response.swift */; };
85D5277A2302BEF30093E9C1 /* TestImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D527792302BEF30093E9C1 /* TestImage.swift */; };
85F6042E22A018BB00063320 /* RequestTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F6042D22A018BB00063320 /* RequestTypeWrapper.swift */; };
8995DF740A59721AA79F5B43 /* MoyaProvider+ReactiveSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = C841AA621AEC61FAEA0CA019 /* MoyaProvider+ReactiveSpec.swift */; };
Expand Down Expand Up @@ -194,6 +198,10 @@
851FFE6DC58E873408D0E8E7 /* MoyaProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MoyaProvider.swift; sourceTree = "<group>"; };
85850A4022CF5AB50089E731 /* NimbleHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbleHelpers.swift; sourceTree = "<group>"; };
85B2DBBB221C69620098F59A /* PropertyListEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyListEncoding.swift; sourceTree = "<group>"; };
85C97FFF231D2D4000AAAFB2 /* MoyaProvider+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MoyaProvider+Combine.swift"; sourceTree = "<group>"; };
85C98001231D3EC700AAAFB2 /* MoyaProvider+CombineSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MoyaProvider+CombineSpec.swift"; sourceTree = "<group>"; };
85C98005231D41E400AAAFB2 /* MoyaPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoyaPublisher.swift; sourceTree = "<group>"; };
85C98007231D4BE900AAAFB2 /* AnyPublisher+Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AnyPublisher+Response.swift"; sourceTree = "<group>"; };
85D527792302BEF30093E9C1 /* TestImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestImage.swift; sourceTree = "<group>"; };
85F6042D22A018BB00063320 /* RequestTypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTypeWrapper.swift; sourceTree = "<group>"; };
86F3C0EA472C23E786E23CE9 /* Image.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -310,6 +318,7 @@
331A6D3090D9773091435406 /* Moya */ = {
isa = PBXGroup;
children = (
85C97FFE231D2D2D00AAAFB2 /* Combine */,
B2CD7F4E23A6F1BA007F67AC /* Atomic.swift */,
149749421F8923EC00FA4900 /* AnyEncodable.swift */,
5C2B20158E599EDBE51D7AB2 /* Cancellable.swift */,
Expand Down Expand Up @@ -402,6 +411,16 @@
path = Plugins;
sourceTree = "<group>";
};
85C97FFE231D2D2D00AAAFB2 /* Combine */ = {
isa = PBXGroup;
children = (
85C98007231D4BE900AAAFB2 /* AnyPublisher+Response.swift */,
85C98005231D41E400AAAFB2 /* MoyaPublisher.swift */,
85C97FFF231D2D4000AAAFB2 /* MoyaProvider+Combine.swift */,
);
path = Combine;
sourceTree = "<group>";
};
85D527782302B4CC0093E9C1 /* MoyaTests */ = {
isa = PBXGroup;
children = (
Expand All @@ -411,6 +430,7 @@
C79C5F376B4A2C3F70051980 /* Error+MoyaSpec.swift */,
7F112022D8F42844A7D5CB5C /* ErrorTests.swift */,
819334FE5D32EFB82599D482 /* MethodSpec.swift */,
85C98001231D3EC700AAAFB2 /* MoyaProvider+CombineSpec.swift */,
C841AA621AEC61FAEA0CA019 /* MoyaProvider+ReactiveSpec.swift */,
4C7A20CC38EB5A0221E6EA34 /* MoyaProvider+RxSpec.swift */,
82E52DC541FD052ABA625D2A /* MoyaProviderIntegrationTests.swift */,
Expand Down Expand Up @@ -838,18 +858,21 @@
B2CD7F4F23A6F1BA007F67AC /* Atomic.swift in Sources */,
FB223B5C3B7D4AA5261E25EA /* Image.swift in Sources */,
EDBA10DB0D0E35C1474AAB4D /* Moya+Alamofire.swift in Sources */,
85C98000231D2D4000AAAFB2 /* MoyaProvider+Combine.swift in Sources */,
15D3A2BD1223B59E2BBE09F0 /* MoyaError.swift in Sources */,
59BC4B9B9D0D6F9C060E5620 /* MoyaProvider+Defaults.swift in Sources */,
149749431F8923EC00FA4900 /* AnyEncodable.swift in Sources */,
9854C1DBF14818CCA76AEC7F /* MoyaProvider+Internal.swift in Sources */,
147E26EC1F5B14B300C1F513 /* Task.swift in Sources */,
149749451F892E2F00FA4900 /* URLRequest+Encoding.swift in Sources */,
85C98008231D4BE900AAAFB2 /* AnyPublisher+Response.swift in Sources */,
5BCAACCA268FEA0DAB07F998 /* MoyaProvider.swift in Sources */,
61633F6E85FB39A4707A6167 /* MultipartFormData.swift in Sources */,
5054F87AFF61EAC0097F88BD /* MultiTarget.swift in Sources */,
BEAA605B30BE7651BA5C61A4 /* Plugin.swift in Sources */,
83B0E400A7562256224CB7FF /* AccessTokenPlugin.swift in Sources */,
B5A7124CF285D8EC7F89C1AD /* CredentialsPlugin.swift in Sources */,
85C98006231D41E400AAAFB2 /* MoyaPublisher.swift in Sources */,
1446FBB31F214C5200C1EFF2 /* URL+Moya.swift in Sources */,
4A609CED953E8A6C59AD01A1 /* NetworkActivityPlugin.swift in Sources */,
1FD44D9E21CEA6B6221807EF /* NetworkLoggerPlugin.swift in Sources */,
Expand Down Expand Up @@ -907,6 +930,7 @@
files = (
A6304A7E74FA3B04C9B10B63 /* AccessTokenPluginSpec.swift in Sources */,
2C7132B56A7B129E12BAACAC /* EndpointSpec.swift in Sources */,
85C98002231D3EC700AAAFB2 /* MoyaProvider+CombineSpec.swift in Sources */,
78A2D3991F44BACD00C9E122 /* RxTestHelpers.swift in Sources */,
53376123902C6A22A44DB88E /* Error+MoyaSpec.swift in Sources */,
78A2D39B1F44D45100C9E122 /* Single+MoyaSpec.swift in Sources */,
Expand Down Expand Up @@ -1141,6 +1165,9 @@
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MACOSX_DEPLOYMENT_TARGET = 10.12;
OTHER_LDFLAGS = $OTHER_LDFLAGS_XCODE11;
OTHER_LDFLAGS_XCODE10 = "";
OTHER_LDFLAGS_XCODE11 = "-weak_framework Combine";
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 5.0;
Expand Down Expand Up @@ -1462,6 +1489,9 @@
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MACOSX_DEPLOYMENT_TARGET = 10.12;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = $OTHER_LDFLAGS_XCODE11;
OTHER_LDFLAGS_XCODE10 = "";
OTHER_LDFLAGS_XCODE11 = "-weak_framework Combine";
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 5.0;
Expand Down
120 changes: 120 additions & 0 deletions Sources/Moya/Combine/AnyPublisher+Response.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#if canImport(Combine)

import Foundation
import Combine

#if canImport(UIKit)
import UIKit.UIImage
#elseif canImport(AppKit)
import AppKit.NSImage
#endif

/// Extension for processing raw NSData generated by network access.
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public extension AnyPublisher where Output == Response, Failure == MoyaError {

/// Filters out responses that don't fall within the given range, generating errors when others are encountered.
func filter<R: RangeExpression>(statusCodes: R) -> AnyPublisher<Response, MoyaError> where R.Bound == Int {
return unwrapThrowable { response in
try response.filter(statusCodes: statusCodes)
}
}

/// Filters out responses that has the specified `statusCode`.
func filter(statusCode: Int) -> AnyPublisher<Response, MoyaError> {
return unwrapThrowable { response in
try response.filter(statusCode: statusCode)
}
}

/// Filters out responses where `statusCode` falls within the range 200 - 299.
func filterSuccessfulStatusCodes() -> AnyPublisher<Response, MoyaError> {
return unwrapThrowable { response in
try response.filterSuccessfulStatusCodes()
}
}

/// Filters out responses where `statusCode` falls within the range 200 - 399
func filterSuccessfulStatusAndRedirectCodes() -> AnyPublisher<Response, MoyaError> {
return unwrapThrowable { response in
try response.filterSuccessfulStatusAndRedirectCodes()
}
}

/// Maps data received from the signal into an Image. If the conversion fails, the signal errors.
func mapImage() -> AnyPublisher<Image, MoyaError> {
return unwrapThrowable { response in
try response.mapImage()
}
}

/// Maps data received from the signal into a JSON object. If the conversion fails, the signal errors.
func mapJSON(failsOnEmptyData: Bool = true) -> AnyPublisher<Any, MoyaError> {
return unwrapThrowable { response in
try response.mapJSON(failsOnEmptyData: failsOnEmptyData)
}
}

/// Maps received data at key path into a String. If the conversion fails, the signal errors.
func mapString(atKeyPath keyPath: String? = nil) -> AnyPublisher<String, MoyaError> {
return unwrapThrowable { response in
try response.mapString(atKeyPath: keyPath)
}
}

/// Maps received data at key path into a Decodable object. If the conversion fails, the signal errors.
func map<D: Decodable>(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) -> AnyPublisher<D, MoyaError> {
return unwrapThrowable { response in
try response.map(type, atKeyPath: keyPath, using: decoder, failsOnEmptyData: failsOnEmptyData)
}
}
}

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public extension AnyPublisher where Output == ProgressResponse, Failure == MoyaError {

/**
Filter completed progress response and maps to actual response

- returns: response associated with ProgressResponse object
*/
func filterCompleted() -> AnyPublisher<Response, MoyaError> {
return self
.compactMap { $0.response }
.eraseToAnyPublisher()
}

/**
Filter progress events of current ProgressResponse

- returns: observable of progress events
*/
func filterProgress() -> AnyPublisher<Double, MoyaError> {
return self
.filter { !$0.completed }
.map { $0.progress }
.eraseToAnyPublisher()
}
}

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension AnyPublisher where Failure == MoyaError {

// Workaround for a lot of things, actually. We don't have Publishers.Once, flatMap
// that can throw and a lot more. So this monster was created because of that. Sorry.
private func unwrapThrowable<T>(throwable: @escaping (Output) throws -> T) -> AnyPublisher<T, MoyaError> {
self.tryMap { element in
try throwable(element)
}
.mapError { error -> MoyaError in
if let moyaError = error as? MoyaError {
return moyaError
} else {
return .underlying(error, nil)
}
}
.eraseToAnyPublisher()
}
}

#endif
62 changes: 62 additions & 0 deletions Sources/Moya/Combine/MoyaProvider+Combine.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#if canImport(Combine)

import Foundation
import Combine

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public extension MoyaProvider {

/// Designated request-making method.
///
/// - Parameters:
/// - target: Entity, which provides specifications necessary for a `MoyaProvider`.
/// - callbackQueue: Callback queue. If nil - queue from provider initializer will be used.
/// - Returns: `AnyPublisher<Response, MoyaError`
func requestPublisher(_ target: Target, callbackQueue: DispatchQueue? = nil) -> AnyPublisher<Response, MoyaError> {
return MoyaPublisher { [weak self] subscriber in
return self?.request(target, callbackQueue: callbackQueue, progress: nil) { result in
switch result {
case let .success(response):
_ = subscriber.receive(response)
subscriber.receive(completion: .finished)
case let .failure(error):
subscriber.receive(completion: .failure(error))
}
}
}
.eraseToAnyPublisher()
}

/// Designated request-making method with progress.
func requestWithProgressPublisher(_ target: Target, callbackQueue: DispatchQueue? = nil) -> AnyPublisher<ProgressResponse, MoyaError> {
let progressBlock: (AnySubscriber<ProgressResponse, MoyaError>) -> (ProgressResponse) -> Void = { subscriber in
return { progress in
_ = subscriber.receive(progress)
}
}

let response = MoyaPublisher<ProgressResponse> { [weak self] subscriber in
let cancellableToken = self?.request(target, callbackQueue: callbackQueue, progress: progressBlock(subscriber)) { result in
switch result {
case .success:
subscriber.receive(completion: .finished)
case let .failure(error):
subscriber.receive(completion: .failure(error))
}
}

return cancellableToken
}

// Accumulate all progress and combine them when the result comes
return response
.scan(ProgressResponse()) { last, progress in
let progressObject = progress.progressObject ?? last.progressObject
let response = progress.response ?? last.response
return ProgressResponse(progress: progressObject, response: response)
}
.eraseToAnyPublisher()
}
}

#endif
42 changes: 42 additions & 0 deletions Sources/Moya/Combine/MoyaPublisher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#if canImport(Combine)

import Combine

// This should be already provided in Combine, but it's not.
// Ideally we would like to remove it, in favor of a framework-provided solution, ASAP.

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
internal class MoyaPublisher<Output>: Publisher {

internal typealias Failure = MoyaError

private class Subscription: Combine.Subscription {

private let cancellable: Cancellable?

init(subscriber: AnySubscriber<Output, MoyaError>, callback: @escaping (AnySubscriber<Output, MoyaError>) -> Cancellable?) {
self.cancellable = callback(subscriber)
}

func request(_ demand: Subscribers.Demand) {
// We don't care for the demand right now
}

func cancel() {
cancellable?.cancel()
}
}

private let callback: (AnySubscriber<Output, MoyaError>) -> Cancellable?

init(callback: @escaping (AnySubscriber<Output, MoyaError>) -> Cancellable?) {
self.callback = callback
}

internal func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
let subscription = Subscription(subscriber: AnySubscriber(subscriber), callback: callback)
subscriber.receive(subscription: subscription)
}
}

#endif
Loading

0 comments on commit 5644997

Please sign in to comment.