Skip to content

Commit

Permalink
Merge fe31429 into fd20dba
Browse files Browse the repository at this point in the history
  • Loading branch information
sprzenus committed Jan 4, 2021
2 parents fd20dba + fe31429 commit d007e9f
Show file tree
Hide file tree
Showing 15 changed files with 370 additions and 28 deletions.
10 changes: 9 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ let package = Package(
.library(
name: "RestlerCore",
targets: ["RestlerCore"]),
.library(
name: "RestlerCombine",
targets: ["RestlerCombine"]),
.library(
name: "RxRestler",
targets: ["RxRestler"]),
Expand All @@ -15,6 +18,11 @@ let package = Package(
.package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "5.1.1")),
],
targets: [
.target(
name: "RestlerCombine",
dependencies: [
.target(name: "RestlerCore"),
]),
.target(
name: "RestlerCore",
dependencies: []),
Expand All @@ -26,6 +34,6 @@ let package = Package(
]),
.testTarget(
name: "RestlerTests",
dependencies: ["RestlerCore"])
dependencies: ["RestlerCore", "RestlerCombine"])
]
)
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ We think that README isn't a good place for complete documentation so that's why

- [RestlerBasicRequestBuilderType](Documentation/Reference/protocols/RestlerBasicRequestBuilderType.md) - available for all HTTP methods.
- [RestlerQueryRequestBuilderType](Documentation/Reference/protocols/RestlerQueryRequestBuilderType.md) - available only for GET.
- [RestlerDownloadRequestBuilderType](Documentation/Reference/protocols/RestlerDownloadRequestBuilderType.md) - available only for GET.
- [RestlerBodyRequestBuilderType](Documentation/Reference/protocols/RestlerBodyRequestBuilderType.md) - available for POST, PUT and PATCH.
- [RestlerMultipartRequestBuilderType](Documentation/Reference/protocols/RestlerMultipartRequestBuilderType.md) - available only for POST.
- [RestlerDecodableResponseRequestBuilderType](Documentation/Reference/protocols/RestlerDecodableResponseRequestBuilderType.md) - available for GET, POST, PUT, PATCH and DELETE (all without HEAD).
Expand Down Expand Up @@ -173,6 +174,27 @@ Any other method call is very similar to these two, but if you have questions si

### Restler + Combine

- Restler approach:

```swift
Restler(baseURL: myBaseURL)
.get("/profile") // 1
.decode(Profile.self) // 2
.publisher // 3
.catch { _ in Empty() } // 4
.assign(to: \.profile, on: self) // 5
.store(in: &subscriptions) // 6
```

1. Makes GET request to the given endpoint.
2. Decode `Profile` object.
3. Convert the request object to publisher for easy using Combine in your code.
4. Handle the thrown error
5. Assign each element from a Publisher to a property on an object.
6. Stores this type-erasing cancellable instance in the specified collection.

- More Combine approach:

```swift
Restler(baseURL: myBaseURL)
.get(Endpoint.myProfile) // 1
Expand All @@ -191,8 +213,8 @@ Restler(baseURL: myBaseURL)
3. Builds a request and returns publisher for Combine support.
4. Specifies the scheduler on which to receive elements from the publisher. In this case the main queue.
5. Get Data object from `DataTaskPublisher`.
6. Decodes Profile object.
7. Handle error
6. Decode `Profile` object.
7. Handle the error
8. Assigns each element from a Publisher to a property on an object.
9. Stores this type-erasing cancellable instance in the specified collection.

Expand Down Expand Up @@ -241,6 +263,10 @@ If you would like Restler to do something else, create an issue with a feature r
4. Open the project in the folder `Restler-Example`. You can do it from the terminal: `open Restler-Example/Restler-Example.xcodeproj`
5. Run tests to be sure everything works properly.

### Lint

Run command `./Scripts/pod_lib_lint.rb Restler` to lint the podspec before pushing changes to the repo.

### Releasing

1. Open the project root directory.
Expand Down
7 changes: 7 additions & 0 deletions Restler-Example/Restler-Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
8AC1C89C2403D3E500FF329E /* PostComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AC1C89B2403D3E500FF329E /* PostComment.swift */; };
8AD434042523063500186599 /* DownloadsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD434032523063500186599 /* DownloadsView.swift */; };
8AD434082523064E00186599 /* DownloadsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD434072523064E00186599 /* DownloadsViewModel.swift */; };
8AE8A05F2580DF3D003D8466 /* RestlerCombine in Frameworks */ = {isa = PBXBuildFile; productRef = 8AE8A05E2580DF3D003D8466 /* RestlerCombine */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -61,6 +62,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
8AE8A05F2580DF3D003D8466 /* RestlerCombine in Frameworks */,
8A0BE49D250A1FC300E76F05 /* RxRestler in Frameworks */,
8A7AD917254C04C80057E6D5 /* RestlerCore in Frameworks */,
8A0BE4A2250A209C00E76F05 /* RxSwift in Frameworks */,
Expand Down Expand Up @@ -208,6 +210,7 @@
8A0BE49C250A1FC300E76F05 /* RxRestler */,
8A0BE4A1250A209C00E76F05 /* RxSwift */,
8A7AD916254C04C80057E6D5 /* RestlerCore */,
8AE8A05E2580DF3D003D8466 /* RestlerCombine */,
);
productName = "Restler-Example";
productReference = 8A87AEEF2400084F007FA662 /* Restler-Example.app */;
Expand Down Expand Up @@ -542,6 +545,10 @@
isa = XCSwiftPackageProductDependency;
productName = RestlerCore;
};
8AE8A05E2580DF3D003D8466 /* RestlerCombine */ = {
isa = XCSwiftPackageProductDependency;
productName = RestlerCombine;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 8A87AEE72400084F007FA662 /* Project object */;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import SwiftUI
import Combine
import RestlerCore
import RestlerCombine

final class ContentViewModel: ObservableObject {
private let restler = Restler(baseURL: URL(string: "https://jsonplaceholder.typicode.com")!)
Expand All @@ -32,7 +33,7 @@ final class ContentViewModel: ObservableObject {

// MARK: - Internal
func createPostViewModel(post: BlogPost) -> PostViewModel {
return PostViewModel(post: post)
PostViewModel(post: post)
}

func createDownloadsViewModel() -> DownloadsViewModel {
Expand All @@ -43,10 +44,9 @@ final class ContentViewModel: ObservableObject {
private func fetchData() -> AnyPublisher<[BlogPost], Error>? {
self.restler
.get(Endpoint.posts)
.publisher()?
.map(\.data)
.decode(type: [BlogPost].self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.receive(on: .main)
.decode([BlogPost].self)
.publisher
.eraseToAnyPublisher()
}
}
16 changes: 12 additions & 4 deletions Restler.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'Restler'
s.version = '1.0.1'
s.version = '1.1'
s.summary = 'Framework for REST requests in Swift'
s.description = <<-DESC
Restler is a framework for type-safe and easy REST API requests in Swift.
Expand All @@ -12,19 +12,27 @@ Pod::Spec.new do |s|
:git => 'https://github.com/railwaymen/restler.git',
:tag => s.version.to_s
}
s.ios.deployment_target = '9.0'
s.swift_versions = '5.2'

s.default_subspec = 'Core'

# Core
s.subspec 'Core' do |ss|
ss.dependency 'RestlerCore', '~> 1.0.1'
s.ios.deployment_target = '9.0'
ss.dependency 'RestlerCore', '~> 1.1'
end

# RxRestler
s.subspec 'Rx' do |ss|
s.ios.deployment_target = '9.0'
ss.dependency 'Restler/Core'
ss.dependency 'RxRestler', '~> 1.0.1'
ss.dependency 'RxRestler', '~> 1.1'
end

# RestlerCombine
s.subspec 'Combine' do |ss|
s.ios.deployment_target = '13.0'
ss.dependency 'Restler/Core'
ss.dependency 'RestlerCombine', '~> 1.1'
end
end
20 changes: 20 additions & 0 deletions RestlerCombine.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Pod::Spec.new do |s|
s.name = 'RestlerCombine'
s.version = '1.1'
s.summary = 'Framework for REST requests in Swift'
s.description = <<-DESC
Restler is a framework for type-safe and easy REST API requests in Swift.
DESC
s.homepage = 'https://github.com/railwaymen/restler'
s.license = { :type => 'MIT', :file => 'LICENSE'}
s.author = { 'Bartłomiej Świerad' => 'bartlomiej.swierad@railwaymen.org' }
s.source = {
:git => 'https://github.com/railwaymen/restler.git',
:tag => s.version.to_s
}
s.ios.deployment_target = '13.0'
s.swift_versions = '5.2'

s.source_files = 'Sources/RestlerCombine/**/*.swift'
s.dependency 'RestlerCore', '~> 1.1'
end
2 changes: 1 addition & 1 deletion RestlerCore.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'RestlerCore'
s.version = '1.0.1'
s.version = '1.1'
s.summary = 'Framework for REST requests in Swift'
s.description = <<-DESC
Restler is a framework for type-safe and easy REST API requests in Swift.
Expand Down
4 changes: 2 additions & 2 deletions RxRestler.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'RxRestler'
s.version = '1.0.1'
s.version = '1.1'
s.summary = 'Framework for REST requests in Swift'
s.description = <<-DESC
Restler is a framework for type-safe and easy REST API requests in Swift.
Expand All @@ -16,6 +16,6 @@ Pod::Spec.new do |s|
s.swift_versions = '5.2'

s.source_files = 'Sources/RxRestler/**/*.swift'
s.dependency 'RestlerCore', '~> 1.0.1'
s.dependency 'RestlerCore', '~> 1.1'
s.dependency 'RxSwift', '~> 5.1.1'
end
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ extension FileReader {

private func createFile(content: String?) {
guard !dryRun else {
console.info("writeToFile: \(text ?? "<nil>")")
console.info("writeToFile: \(content ?? "<nil>")")
return
}
fileManager.createFile(
Expand Down
5 changes: 3 additions & 2 deletions Scripts/releaseTool/Sources/ReleaseTool/Models/Manifest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ struct Manifest {
if let betaNumber = pod.betaNumber {
finalVersion.append("-beta.\(betaNumber)")
}
return pod.podVersion ?? finalVersion
return finalVersion
}
}

// MARK: - Shared
extension Manifest {
static let shared = Manifest(
version: "1.0.1",
version: "1.1",
pods: [
Pod(name: "RestlerCore"),
Pod(name: "RxRestler"),
Pod(name: "RestlerCombine"),
Pod(name: "Restler"),
])
}
65 changes: 65 additions & 0 deletions Sources/RestlerCombine/Request+Combine.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import Foundation
import RestlerCore
import Combine

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension Restler.Request {
public var publisher: RestlerRequestPublisher<D> {
RestlerRequestPublisher<D>(request: self)
}
}

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public struct RestlerRequestPublisher<DecodedType>: Publisher {
public typealias Output = DecodedType
public typealias Failure = Error

private let request: Restler.Request<DecodedType>

// MARK: Initialization
public init(request: Restler.Request<DecodedType>) {
self.request = request
}

// MARK: Publisher
public func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
let subscription = Subscription(request: request, subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
}

// MARK: - Subscription
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension RestlerRequestPublisher {

final class Subscription<S: Subscriber>: Combine.Subscription where Failure == S.Failure, Output == S.Input {
var combineIdentifier: CombineIdentifier = .init()

private var task: RestlerTaskType?
private var subscriber: S?
private let request: Restler.Request<DecodedType>

// MARK: Initialization
init(request: Restler.Request<DecodedType>, subscriber: S) {
self.request = request
self.subscriber = subscriber
}

// MARK: Subscription
func request(_ demand: Subscribers.Demand) {
task = request.subscribe(
onSuccess: { [self] in
_ = subscriber?.receive($0)
subscriber?.receive(completion: .finished)
},
onFailure: { [self] in
subscriber?.receive(completion: .failure($0))
})
}

func cancel() {
subscriber = nil
task?.cancel()
}
}
}
4 changes: 2 additions & 2 deletions Sources/RestlerCore/Internal/Networking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ extension Networking: NetworkingType {
request: urlRequest,
eventLogger: eventLogger,
resumeData: resumeData))
self.downloadTaskProgressHandlers[task.id] = { progressHandler(task) }
self.downloadTaskCompletionHandlers[task.id] = completionHandler
self.downloadTaskProgressHandlers[task.identifier] = { progressHandler(task) }
self.downloadTaskCompletionHandlers[task.identifier] = completionHandler
task.resume()
return task
}
Expand Down
12 changes: 4 additions & 8 deletions Sources/RestlerCore/Public/Classes/Task/DownloadTask.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import Foundation

public protocol RestlerDownloadTaskType: class {
public protocol RestlerDownloadTaskType: RestlerTaskType {
@available(OSX 10.13, iOS 11, *)
var progress: Progress { get }
var downloadProgress: Double { get }
var state: URLSessionTask.State { get }

func resume()
func cancel()
func cancel(byProducingResumeData completionHandler: @escaping (Data?) -> Void)
func suspend()
}

extension Restler {
final public class DownloadTask: RestlerDownloadTaskType {
private let urlTask: URLSessionDownloadTaskType

public var id: Int { urlTask.taskIdentifier }
public var identifier: Int { urlTask.taskIdentifier }

/// A progress of the download task.
@available(OSX 10.13, iOS 11, *)
Expand Down Expand Up @@ -63,10 +59,10 @@ extension Restler {
// MARK: - Hashable
extension Restler.DownloadTask: Hashable {
public static func == (lhs: Restler.DownloadTask, rhs: Restler.DownloadTask) -> Bool {
lhs.id == rhs.id
lhs.identifier == rhs.identifier
}

public func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(identifier)
}
}

0 comments on commit d007e9f

Please sign in to comment.