Skip to content

Commit

Permalink
iOS 15
Browse files Browse the repository at this point in the history
  • Loading branch information
julianschiavo committed Jun 10, 2021
1 parent f798f9e commit 7f8d8e9
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 120 deletions.
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// swift-tools-version:5.3
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "Loadability",
platforms: [.iOS(.v14), .macOS(.v11), .watchOS(.v7)],
platforms: [.iOS("15"), .macOS("12"), .watchOS("8")],
products: [
.library(name: "Loadability", targets: ["Loadability"]),
],
Expand Down
4 changes: 2 additions & 2 deletions Sources/Loadability/Caching/SerializableCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public final class SerializableCache<Key: Codable & Hashable & Identifiable, Val
/// - autoRemoveStaleItems: Whether to automatically remove stale items, defaults to `false`.
/// - folderURL: The folder in which to store the cache, defaults to the system cache directory.
private init(name: String,
shouldAutomaticallyRemoveStaleItems autoRemoveStaleItems: Bool = false,
folderURL: URL? = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first) {
shouldAutomaticallyRemoveStaleItems autoRemoveStaleItems: Bool = false,
folderURL: URL? = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first) {

guard let folderURL = folderURL else {
fatalError("Invalid Folder URL")
Expand Down
2 changes: 1 addition & 1 deletion Sources/Loadability/Extensions/Error-Description.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ extension Error {
return nsError.localizedDescription
}

private var recoverySuggestion: String? {
var recoverySuggestion: String? {
guard let suggestion = nsError.localizedRecoverySuggestion,
!suggestion.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return nil }
return nsError.localizedRecoverySuggestion
Expand Down
12 changes: 7 additions & 5 deletions Sources/Loadability/Networking/CachedLoader.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation

/// A type that can load data from a source with caching.
@MainActor
public protocol CachedLoader: Loader where Key == Cache.Key, Object == Cache.Value {
/// The type of cache.
associatedtype Cache: AnySharedCache
Expand All @@ -10,10 +11,10 @@ public protocol CachedLoader: Loader where Key == Cache.Key, Object == Cache.Val
}

public extension CachedLoader {
func load(key: Key) {
func load(key: Key) async {
loadCachedData(key: key)
if cache.isValueStale(key) {
loadData(key: key)
await loadData(key: key)
}
}

Expand All @@ -34,15 +35,16 @@ public extension CachedLoader {
completion(self.cache[key])
}

@available(iOS 15.0, watchOS 15.0, macOS 15.0, *)
func loadCompleted(key: Key, object: Object) {
DispatchQueue.global(qos: .userInteractive).async {
async {
self.cache[key] = object
}
}
}

public extension CachedLoader where Key == GenericKey {
func load() {
load(key: .key)
func load() async {
await load(key: .key)
}
}
15 changes: 8 additions & 7 deletions Sources/Loadability/Networking/Loader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Foundation
public typealias SomeLoader = Loader

/// A type that can load data from a source and throw errors.
@MainActor
public protocol Loader: ObservableObject, ThrowsErrors {
/// The type of key that identifies objects.
associatedtype Key: Hashable
Expand All @@ -21,15 +22,15 @@ public protocol Loader: ObservableObject, ThrowsErrors {

/// Begins loading the object.
/// - Parameter key: The key identifying the object to load.
func load(key: Key)
func load(key: Key) async

/// Creates a publisher that loads the object.
/// - Parameter key: The key identifying the object to load.
func createPublisher(key: Key) -> AnyPublisher<Object, Error>?

/// Starts loading the object's data.
/// - Parameter key: The key identifying the object to load.
func loadData(key: Key)
func loadData(key: Key) async

/// Called when the object has been loaded successfully.
/// - Parameters:
Expand All @@ -40,11 +41,11 @@ public protocol Loader: ObservableObject, ThrowsErrors {
}

public extension Loader {
func load(key: Key) {
loadData(key: key)
func load(key: Key) async {
await loadData(key: key)
}

func loadData(key: Key) {
func loadData(key: Key) async {
self.cancellable = self.createPublisher(key: key)?
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.receive(on: DispatchQueue.main)
Expand All @@ -68,7 +69,7 @@ public extension Loader {
}

public extension Loader where Key == GenericKey {
func load() {
load(key: .key)
func load() async {
await load(key: .key)
}
}
1 change: 1 addition & 0 deletions Sources/Loadability/Networking/SimpleNetworkLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Combine
import Foundation

/// A type that can load data from over the network and throw errors.
@MainActor
public protocol SimpleNetworkLoader: Loader {
/// Creates a `URLRequest` for a network loading request.
/// - Parameter key: The key identifying the object to load.
Expand Down
40 changes: 0 additions & 40 deletions Sources/Loadability/Other/IdentifiableError.swift

This file was deleted.

30 changes: 30 additions & 0 deletions Sources/Loadability/Other/LocalizedError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Foundation

struct _LocalizedError: LocalizedError {
/// A localized message describing what error occurred.
var errorDescription: String? {
error.userVisibleTitle
}

/// A localized message describing the reason for the failure.
var failureReason: String? {
nil
}

/// A localized message describing how one might recover from the failure.
var recoverySuggestion: String? {
error.recoverySuggestion
}

/// A localized message providing "help" text if the user requests help.
var helpAnchor: String? {
nil
}

/// The underlying error object.
var error: Error

init(_ error: Error) {
self.error = error
}
}
18 changes: 7 additions & 11 deletions Sources/Loadability/Other/ThrowsErrors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import SwiftUI
/// A type that can throw errors that should be shown to the user.
public protocol ThrowsErrors {
/// An error, if one occurred. Must be annotated with a publisher property wrapper, such as `@State` or `@Published`, to work.
var error: IdentifiableError? { get nonmutating set }
var error: Error? { get nonmutating set }
}

public extension ThrowsErrors {
/// Attempts to execute a block, catching errors thrown by displaying the error to the user.
/// - Parameter block: A throwing block.
func tryAndCatch(_ block: () throws -> Void) {
@MainActor func tryAndCatch(_ block: () throws -> Void) {
do {
try block()
} catch {
Expand All @@ -20,22 +20,18 @@ public extension ThrowsErrors {

/// Catches a thrown error, updating the view state to display the error if a subscriber is subscribed to it (through `View.alert(errorBinding: $error)`).
/// - Parameter error: The error that occurred.
func catchError(_ error: Error?) {
DispatchQueue.main.async {
self.error = IdentifiableError(error)
}
@MainActor func catchError(_ error: Error?) {
self.error = error
}

/// Dismisses the current error, if there is one.
func dismissError() {
DispatchQueue.main.async {
self.error = nil
}
@MainActor func dismissError() {
self.error = nil
}

/// Catches an error thrown from a Combine Publisher.
/// - Parameter completion: The completion received from the publisher.
func catchCompletion(_ completion: Subscribers.Completion<Error>) {
@MainActor func catchCompletion(_ completion: Subscribers.Completion<Error>) {
guard case let .failure(error) = completion else { return }
catchError(error)
}
Expand Down
23 changes: 19 additions & 4 deletions Sources/Loadability/UI/Load.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public struct Load<Loader: SomeLoader, Value, Content: View, PlaceholderContent:
}
}

/// Whether an error alert is currently presented.
@State private var isErrorAlertPresented = false

public init(with loader: Loader,
key: Loader.Key,
objectKeyPath: KeyPath<Loader.Object, Value?>,
Expand Down Expand Up @@ -59,8 +62,8 @@ public struct Load<Loader: SomeLoader, Value, Content: View, PlaceholderContent:

public var body: some View {
bodyContent
.onAppear {
loader.load(key: key)
.task {
await loader.load(key: key)
}
.onDisappear {
loader.cancel()
Expand All @@ -84,8 +87,20 @@ public struct Load<Loader: SomeLoader, Value, Content: View, PlaceholderContent:
/// Presents an alert to the user if an error occurs.
/// - Parameters:
/// - alertContent: Content to display in the alert.
public func displayingErrors(_ alertContent: ((Error) -> Alert)?) -> some View {
body.alert(errorBinding: $loader.error, alert: alertContent)
public func displayingErrors(message: ((Error) -> String)?) -> some View {
var error: _LocalizedError?
if let loaderError = loader.error {
error = _LocalizedError(loaderError)
}

return body
.alert(isPresented: $isErrorAlertPresented, error: error) { _ in
Button("OK") {
loader.dismissError()
}
} message: { error in
Text(message?(error) ?? error.userVisibleTitle)
}
}
}

Expand Down
48 changes: 0 additions & 48 deletions Sources/Loadability/UI/View-ErrorAlert.swift

This file was deleted.

0 comments on commit 7f8d8e9

Please sign in to comment.