Skip to content

Commit

Permalink
Merge branch 'release/0.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
antonio-war committed Aug 2, 2024
2 parents a7bded2 + 9fc13b6 commit 41bf726
Show file tree
Hide file tree
Showing 20 changed files with 645 additions and 14 deletions.
9 changes: 9 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The MIT License (MIT)

Copyright (c) 2024 SwiftyNetworking

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4 changes: 4 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import PackageDescription

let package = Package(
name: "SwiftyNetworking",
platforms: [
.iOS(.v13),
.macOS(.v12),
],
products: [
.library(
name: "SwiftyNetworking",
Expand Down
88 changes: 88 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Overview

**SwiftyNetworking** is a powerful and easy-to-use networking client written in Swift.
It simplifies making network requests and handling responses, allowing you to focus on building your application rather than dealing with the complexities of networking.
- **Simple**: Designed with simplicity and ease of use in mind, SwiftyNetworking eliminates the need for extensive configuration, making it ready to use right out of the box.
- **Asynchronous**: Built with modern Swift concurrency, supporting `async/await`.
- **Flexible**: Customize requests with different methods, headers, and cache policies.
- **Inspectable**: SwiftyNetworking collect some network metrics that can be used for in-depth debugging.

---
# Integration
Integrating SwiftyNetworking into your Swift project is straightforward. Follow these steps to get started:

1. **Install SwiftyNetworking**:
- If you're using Swift Package Manager (SPM):
- Open your Xcode project.
- Navigate to "File" > "Swift Packages" > "Add Package Dependency...".
- Enter the SwiftyNetworking repository URL: `https://github.com/antonio-war/SwiftyNetworking`.
- Follow the prompts to select the version and add SwiftyNetworking to your project.
- If you're using CocoaPods or Carthage, we're sorry, but they are not currently supported.
2. **Import SwiftyNetworking**:
- In the files where you want to use SwiftyNetworking features, import its module at the top of the file:
```swift
import SwiftyNetworking
```
3. **Start Using SwiftyNetworking**:
- Once SwiftyNetworking is imported, you can start using its methods to execute a networking request.
- Refer to the usage section for guidance regarding structs, classes and methods.
4. **Run Your Project**:
- Build and run your project to ensure that SwiftyNetworking has been integrated successfully.
- Test out the functionality you've implemented using SwiftyNetworking to ensure everything works as expected.
That's it! You've successfully integrated SwiftyNetworking into your project and can now leverage its powerful features.

---
# Usage
The main steps for using SwiftyNetworking into your project are outlined below, guiding you through the process.

### Request definition
First, define a `SwiftyNetworkingRequest` which is a simple wrapper around `URLRequest` which allows you to easily set up everything you need to make an API call.
Such as the classics method, headers and query parameters, but also some parameters closely linked to the iOS ecosystem such as cache policy or timeout management.

```swift
let request = SwiftyNetworkingRequest(
endpoint: "https://jsonplaceholder.typicode.com",
path: "comments",
method: .get,
parameters: ["postId": 1],
cachePolicy: .reloadIgnoringCacheData
)
```

### Client creation
Create a `SwiftyNetworkingClient` instance using the default or a custom URLSessionConfiguration.
A single instance should be enough to manage the entire networking layer of the app, so hypothetically the client could be placed inside a dependency container.

```swift
let networkingClient = SwiftyNetworkingClient()
```

### Request execution
Execute the request using the defined async method.

```swift
let response = try await networkingClient.send(request: request)
```

### Response handling
If successful, the method will return a `SwiftyNetworkingResponse` which is a simple wrapper around `HTTPURLResponse` and allows you to easily access some elements like body, headers and few metrics. SwiftyNetworking always returns the source of the response and its duration allowing you to understand if it comes from the network or from the cache.

```swift
if response.status == 200 && let body = response.body {
return String(data: body, encoding: .utf8)
}
```

---
# Support
Your generous donations help sustain and improve this project. Here's why supporting us is important:
1. **Development and Maintenance**: Donations enable us to dedicate more time and resources to developing new features, fixing bugs, and maintaining the project's overall health. Your support directly contributes to the project's ongoing improvement and sustainability.
2. **Community Support**: Your contributions show your support for the project and help foster a thriving community around it. Your generosity motivates us to keep pushing the project forward and encourages others to join the cause.
3. **Open Source Sustainability**: By supporting open-source projects like ours, you're contributing to the sustainability of the entire open-source ecosystem. Your donations help ensure that valuable projects remain accessible to everyone.

Every donation, no matter how small, makes a big difference. Thank you for considering supporting us!<br><br>
<a href="https://www.buymeacoffee.com/antoniowar" target="_blank"><img src="https://github.com/appcraftstudio/buymeacoffee/raw/master/Images/snapshot-bmc-button.png" alt="Buy Me A Coffee" height="40"></a>

---
# License
SwiftyNetworking is published under the MIT license.
34 changes: 34 additions & 0 deletions Sources/SwiftyNetworking/Clients/SwiftyNetworkingClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// SwiftyNetworkingClient.swift
//
//
// Created by Antonio Guerra on 01/08/24.
//

import Foundation

public actor SwiftyNetworkingClient {
private let delegate: SwiftyNetworkingDelegate = SwiftyNetworkingDelegate()
private let configuration: URLSessionConfiguration
private let session: URLSession

public init(configuration: URLSessionConfiguration = URLSessionConfiguration.default) {
self.configuration = configuration
self.session = URLSession(configuration: configuration, delegate: delegate)
}

public func send(request: SwiftyNetworkingRequest) async throws -> SwiftyNetworkingResponse {
let underlyingRequest = try request.underlyingRequest
let (body, underlyingResponse) = try await session.data(for: underlyingRequest)
let metrics = delegate.metrics(for: underlyingRequest)
guard let underlyingResponse = underlyingResponse as? HTTPURLResponse else {
throw URLError(.cannotParseResponse)
}
return SwiftyNetworkingResponse(
body: body,
source: metrics.source,
duration: metrics.duration,
underlyingResponse: underlyingResponse
)
}
}
27 changes: 27 additions & 0 deletions Sources/SwiftyNetworking/Delegates/SwiftyNetworkingDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// SwiftyNetworkingDelegate.swift
//
//
// Created by Antonio Guerra on 01/08/24.
//

import Foundation

class SwiftyNetworkingDelegate: NSObject, URLSessionTaskDelegate {
private var metrics: [Int: URLSessionTaskTransactionMetrics] = [:]

func metrics(for request: URLRequest) -> SwiftyNetworkingMetrics {
guard let metrics = metrics[request.hashValue] else {
return (.network, 0)
}
let source = SwiftyNetworkingSource(resourceFetchType: metrics.resourceFetchType)
let duration = metrics.responseEndDate?.timeIntervalSince(metrics.requestStartDate ?? Date()) ?? 0.0
return (source, duration)
}

func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
for metrics in metrics.transactionMetrics {
self.metrics[metrics.request.hashValue] = metrics
}
}
}
14 changes: 14 additions & 0 deletions Sources/SwiftyNetworking/Extensions/URLSession+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// URLSession+.swift
//
//
// Created by Antonio Guerra on 01/08/24.
//

import Foundation

extension URLSession {
convenience init(configuration: URLSessionConfiguration, delegate: (any URLSessionDelegate)) {
self.init(configuration: configuration, delegate: delegate, delegateQueue: nil)
}
}
25 changes: 25 additions & 0 deletions Sources/SwiftyNetworking/Models/SwiftyNetworkingCachePolicy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// SwiftyNetworkingCachePolicy.swift
//
//
// Created by Antonio Guerra on 02/08/24.
//

import Foundation

public enum SwiftyNetworkingCachePolicy: String, Equatable, Hashable, Sendable {
case reloadIgnoringCacheData
case returnCacheDataElseLoad
case returnCacheDataDontLoad

var underlyingCachePolicy: NSURLRequest.CachePolicy {
switch self {
case .reloadIgnoringCacheData:
return .reloadIgnoringLocalCacheData
case .returnCacheDataElseLoad:
return .returnCacheDataElseLoad
case .returnCacheDataDontLoad:
return .returnCacheDataDontLoad
}
}
}
20 changes: 20 additions & 0 deletions Sources/SwiftyNetworking/Models/SwiftyNetworkingMethod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// SwiftyNetworkingMethod.swift
//
//
// Created by Antonio Guerra on 01/08/24.
//

import Foundation

public enum SwiftyNetworkingMethod: String, Equatable, Hashable, Sendable {
case connect
case delete
case get
case head
case options
case patch
case post
case put
case trace
}
84 changes: 84 additions & 0 deletions Sources/SwiftyNetworking/Models/SwiftyNetworkingRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// SwiftyNetworkingRequest.swift
//
//
// Created by Antonio Guerra on 01/08/24.
//

import Foundation

public struct SwiftyNetworkingRequest: Identifiable, Hashable, Sendable {
public let id: UUID
public let endpoint: String
public let path: String
public let method: SwiftyNetworkingMethod
public let headers: [String: String]
public let body: Data?
public let parameters: [String: String]
public let cachePolicy: SwiftyNetworkingCachePolicy
public let timeout: TimeInterval

public init(
id: UUID = UUID(),
endpoint: String,
path: String = "/",
method: SwiftyNetworkingMethod = .get,
headers: [String: String] = [:],
body: Data? = nil,
parameters: [String: Any] = [:],
cachePolicy: SwiftyNetworkingCachePolicy = .returnCacheDataElseLoad,
timeout: TimeInterval = 60
) {
self.id = id
self.endpoint = endpoint
self.path = path
self.method = method
self.headers = headers
self.body = body
self.parameters = parameters.reduce(into: [String: String]()) { result, parameter in
result[parameter.key] = String(describing: parameter.value)
}
self.cachePolicy = cachePolicy
self.timeout = timeout
}

var url: URL {
get throws {
guard let endpoint = URL(string: endpoint) else {
throw URLError(.badURL)
}

guard let scheme = endpoint.scheme, SwiftyNetworkingScheme(rawValue: scheme) != nil else {
throw URLError(.badURL)
}

guard let url = URL(string: path, relativeTo: endpoint), var components = URLComponents(string: url.absoluteString) else {
throw URLError(.badURL)
}

guard !parameters.isEmpty else {
return url
}

components.queryItems = parameters.map { (key, value) in
URLQueryItem(name: key, value: value)
}

guard let url = components.url else {
throw URLError(.badURL)
}

return url
}
}

var underlyingRequest: URLRequest {
get throws {
var request = try URLRequest(url: url, cachePolicy: cachePolicy.underlyingCachePolicy, timeoutInterval: timeout)
request.httpMethod = method.rawValue.uppercased()
request.httpBody = body
request.allHTTPHeaderFields = headers
return request
}
}
}
33 changes: 33 additions & 0 deletions Sources/SwiftyNetworking/Models/SwiftyNetworkingResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// SwiftyNetworkingResponse.swift
//
//
// Created by Antonio Guerra on 01/08/24.
//

import Foundation

public struct SwiftyNetworkingResponse: Identifiable, Hashable, Sendable {
public let id: UUID
public let body: Data
public let status: Int
public let headers: [String: String]
public let source: SwiftyNetworkingSource
public let duration: TimeInterval

init(id: UUID = UUID(), body: Data, source: SwiftyNetworkingSource, duration: TimeInterval, underlyingResponse: HTTPURLResponse) {
self.id = id
self.body = body
self.status = underlyingResponse.statusCode
self.headers = underlyingResponse.allHeaderFields.reduce(into: [String: String]()) { result, header in
if let name = header.key as? String, let value = header.value as? String {
result[name] = value
}
}
self.source = source
self.duration = duration
self.underlyingResponse = underlyingResponse
}

var underlyingResponse: HTTPURLResponse
}
13 changes: 13 additions & 0 deletions Sources/SwiftyNetworking/Models/SwiftyNetworkingScheme.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// SwiftyNetworkingScheme.swift
//
//
// Created by Antonio Guerra on 01/08/24.
//

import Foundation

public enum SwiftyNetworkingScheme: String, Equatable, Hashable, Sendable {
case http
case https
}
22 changes: 22 additions & 0 deletions Sources/SwiftyNetworking/Models/SwiftyNetworkingSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// SwiftyNetworkingSource.swift
//
//
// Created by Antonio Guerra on 02/08/24.
//

import Foundation

public enum SwiftyNetworkingSource: String, Equatable, Hashable, Sendable {
case network
case cache

init(resourceFetchType: URLSessionTaskMetrics.ResourceFetchType) {
switch resourceFetchType {
case .localCache:
self = .cache
default:
self = .network
}
}
}
2 changes: 0 additions & 2 deletions Sources/SwiftyNetworking/SwiftyNetworking.swift

This file was deleted.

Loading

0 comments on commit 41bf726

Please sign in to comment.