From 9429ff05226bf2efd30233e3077b5a6f64528874 Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Tue, 17 Nov 2020 11:54:52 +0100 Subject: [PATCH 1/6] feat(): implemented support for a custom RemoteImageURLDataPublisher --- .../Protocols/RemoteImageURLDataPublisher.swift | 13 ------------- .../Services/RemoteImageServiceDependencies.swift | 4 ++-- .../URLSession+RemoteImageURLDataPublisher.swift | 4 ++-- .../Protocols/RemoteImageURLDataPublisher.swift | 13 +++++++++++++ .../public/Services/RemoteImageService.swift | 4 +--- .../public/Services/RemoteImageServiceFactory.swift | 4 ++-- Sources/RemoteImage/public/Views/RemoteImage.swift | 7 +++++-- 7 files changed, 25 insertions(+), 24 deletions(-) delete mode 100644 Sources/RemoteImage/private/Protocols/RemoteImageURLDataPublisher.swift rename Sources/RemoteImage/{private => public}/Extensions/URLSession+RemoteImageURLDataPublisher.swift (54%) create mode 100644 Sources/RemoteImage/public/Protocols/RemoteImageURLDataPublisher.swift diff --git a/Sources/RemoteImage/private/Protocols/RemoteImageURLDataPublisher.swift b/Sources/RemoteImage/private/Protocols/RemoteImageURLDataPublisher.swift deleted file mode 100644 index 1f50d31..0000000 --- a/Sources/RemoteImage/private/Protocols/RemoteImageURLDataPublisher.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// RemoteImageURLDataPublisher.swift -// RemoteImage -// -// Created by Christian Elies on 15.12.19. -// - -import Combine -import Foundation - -protocol RemoteImageURLDataPublisher { - func dataPublisher(for request: URLRequest) -> AnyPublisher<(data: Data, response: URLResponse), URLError> -} diff --git a/Sources/RemoteImage/private/Services/RemoteImageServiceDependencies.swift b/Sources/RemoteImage/private/Services/RemoteImageServiceDependencies.swift index deb2c02..f5de93f 100644 --- a/Sources/RemoteImage/private/Services/RemoteImageServiceDependencies.swift +++ b/Sources/RemoteImage/private/Services/RemoteImageServiceDependencies.swift @@ -15,8 +15,8 @@ struct RemoteImageServiceDependencies: RemoteImageServiceDependenciesProtocol { let photoKitService: PhotoKitServiceProtocol let remoteImageURLDataPublisher: RemoteImageURLDataPublisher - init() { + init(remoteImageURLDataPublisher: RemoteImageURLDataPublisher) { photoKitService = PhotoKitService() - remoteImageURLDataPublisher = URLSession.shared + self.remoteImageURLDataPublisher = remoteImageURLDataPublisher } } diff --git a/Sources/RemoteImage/private/Extensions/URLSession+RemoteImageURLDataPublisher.swift b/Sources/RemoteImage/public/Extensions/URLSession+RemoteImageURLDataPublisher.swift similarity index 54% rename from Sources/RemoteImage/private/Extensions/URLSession+RemoteImageURLDataPublisher.swift rename to Sources/RemoteImage/public/Extensions/URLSession+RemoteImageURLDataPublisher.swift index 1e29b2e..0186c01 100644 --- a/Sources/RemoteImage/private/Extensions/URLSession+RemoteImageURLDataPublisher.swift +++ b/Sources/RemoteImage/public/Extensions/URLSession+RemoteImageURLDataPublisher.swift @@ -9,7 +9,7 @@ import Combine import Foundation extension URLSession: RemoteImageURLDataPublisher { - func dataPublisher(for request: URLRequest) -> AnyPublisher<(data: Data, response: URLResponse), URLError> { - dataTaskPublisher(for: request).eraseToAnyPublisher() + public func dataPublisher(for url: URL) -> AnyPublisher<(data: Data, response: URLResponse), URLError> { + dataTaskPublisher(for: url).eraseToAnyPublisher() } } diff --git a/Sources/RemoteImage/public/Protocols/RemoteImageURLDataPublisher.swift b/Sources/RemoteImage/public/Protocols/RemoteImageURLDataPublisher.swift new file mode 100644 index 0000000..84c9d8d --- /dev/null +++ b/Sources/RemoteImage/public/Protocols/RemoteImageURLDataPublisher.swift @@ -0,0 +1,13 @@ +// +// RemoteImageURLDataPublisher.swift +// RemoteImage +// +// Created by Christian Elies on 15.12.19. +// + +import Combine +import Foundation + +public protocol RemoteImageURLDataPublisher { + func dataPublisher(for url: URL) -> AnyPublisher<(data: Data, response: URLResponse), URLError> +} diff --git a/Sources/RemoteImage/public/Services/RemoteImageService.swift b/Sources/RemoteImage/public/Services/RemoteImageService.swift index aef52a9..9e17ec0 100644 --- a/Sources/RemoteImage/public/Services/RemoteImageService.swift +++ b/Sources/RemoteImage/public/Services/RemoteImageService.swift @@ -49,9 +49,7 @@ private extension RemoteImageService { return } - let urlRequest = URLRequest(url: url) - - cancellable = dependencies.remoteImageURLDataPublisher.dataPublisher(for: urlRequest) + cancellable = dependencies.remoteImageURLDataPublisher.dataPublisher(for: url) .map { UniversalImage(data: $0.data) } .receive(on: RunLoop.main) .sink(receiveCompletion: { [weak self] completion in diff --git a/Sources/RemoteImage/public/Services/RemoteImageServiceFactory.swift b/Sources/RemoteImage/public/Services/RemoteImageServiceFactory.swift index a6e5abd..bc14c48 100644 --- a/Sources/RemoteImage/public/Services/RemoteImageServiceFactory.swift +++ b/Sources/RemoteImage/public/Services/RemoteImageServiceFactory.swift @@ -6,8 +6,8 @@ // public final class RemoteImageServiceFactory { - public static func makeRemoteImageService() -> RemoteImageService { - let dependencies = RemoteImageServiceDependencies() + public static func makeRemoteImageService(remoteImageURLDataPublisher: RemoteImageURLDataPublisher) -> RemoteImageService { + let dependencies = RemoteImageServiceDependencies(remoteImageURLDataPublisher: remoteImageURLDataPublisher) return RemoteImageService(dependencies: dependencies) } } diff --git a/Sources/RemoteImage/public/Views/RemoteImage.swift b/Sources/RemoteImage/public/Views/RemoteImage.swift index ab77913..97bc7a6 100644 --- a/Sources/RemoteImage/public/Views/RemoteImage.swift +++ b/Sources/RemoteImage/public/Views/RemoteImage.swift @@ -16,7 +16,7 @@ public struct RemoteImage: private let imageView: (Image) -> ImageView private let loadingView: () -> LoadingView - @ObservedObject private var service = RemoteImageServiceFactory.makeRemoteImageService() + @ObservedObject private var service: RemoteImageService public var body: some View { switch service.state { @@ -33,12 +33,15 @@ public struct RemoteImage: } } - public init(type: RemoteImageType, @ViewBuilder errorView: @escaping (Error) -> ErrorView, @ViewBuilder imageView: @escaping (Image) -> ImageView, @ViewBuilder loadingView: @escaping () -> LoadingView) { + public init(type: RemoteImageType, remoteImageURLDataPublisher: RemoteImageURLDataPublisher = URLSession.shared, @ViewBuilder errorView: @escaping (Error) -> ErrorView, @ViewBuilder imageView: @escaping (Image) -> ImageView, @ViewBuilder loadingView: @escaping () -> LoadingView) { self.type = type self.errorView = errorView self.imageView = imageView self.loadingView = loadingView + let service = RemoteImageServiceFactory.makeRemoteImageService(remoteImageURLDataPublisher: remoteImageURLDataPublisher) + _service = ObservedObject(wrappedValue: service) + service.fetchImage(ofType: type) } } From e6850b79b2a2f3c8a27ac771943e7d13eefc7df3 Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Mon, 23 Nov 2020 23:46:48 +0100 Subject: [PATCH 2/6] refactor(): added default prefix to types; feat(): implemented support for a custom service --- .../private/Models/RemoteImageState.swift | 31 ------------- ...faultRemoteImageServiceDependencies.swift} | 6 +-- .../private/Services/PhotoKitService.swift | 12 ++++-- .../public/Models/RemoteImageState.swift | 15 +++++++ .../Protocols/RemoteImageService.swift} | 6 ++- ....swift => DefaultRemoteImageService.swift} | 16 +++---- .../DefaultRemoteImageServiceFactory.swift | 13 ++++++ .../Services/RemoteImageServiceFactory.swift | 13 ------ .../public/Views/RemoteImage.swift | 34 +++++++++++++-- ...ion+RemoteImageURLDataPublisherTests.swift | 5 +-- .../MockRemoteImageServiceDependencies.swift | 2 +- .../MockRemoteImageURLDataPublisher.swift | 2 +- .../Models/RemoteImageStateTests.swift | 43 ------------------- .../RemoteImageServiceDependenciesTests.swift | 2 +- .../RemoteImageServiceFactoryTests.swift | 2 +- .../Services/RemoteImageServiceTests.swift | 8 ++-- 16 files changed, 91 insertions(+), 119 deletions(-) delete mode 100644 Sources/RemoteImage/private/Models/RemoteImageState.swift rename Sources/RemoteImage/private/Services/{RemoteImageServiceDependencies.swift => DefaultRemoteImageServiceDependencies.swift} (60%) create mode 100644 Sources/RemoteImage/public/Models/RemoteImageState.swift rename Sources/RemoteImage/{private/Protocols/RemoteImageServiceProtocol.swift => public/Protocols/RemoteImageService.swift} (63%) rename Sources/RemoteImage/public/Services/{RemoteImageService.swift => DefaultRemoteImageService.swift} (85%) create mode 100644 Sources/RemoteImage/public/Services/DefaultRemoteImageServiceFactory.swift delete mode 100644 Sources/RemoteImage/public/Services/RemoteImageServiceFactory.swift delete mode 100644 Tests/RemoteImageTests/Models/RemoteImageStateTests.swift diff --git a/Sources/RemoteImage/private/Models/RemoteImageState.swift b/Sources/RemoteImage/private/Models/RemoteImageState.swift deleted file mode 100644 index a86f680..0000000 --- a/Sources/RemoteImage/private/Models/RemoteImageState.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// RemoteImageState.swift -// RemoteImage -// -// Created by Christian Elies on 11.08.19. -// Copyright © 2019 Christian Elies. All rights reserved. -// - -import Foundation - -enum RemoteImageState: Hashable { - case error(_ error: NSError) - case image(_ image: UniversalImage) - case loading -} - -extension RemoteImageState { - var error: NSError? { - guard case let RemoteImageState.error(error) = self else { - return nil - } - return error - } - - var image: UniversalImage? { - guard case let RemoteImageState.image(uiImage) = self else { - return nil - } - return uiImage - } -} diff --git a/Sources/RemoteImage/private/Services/RemoteImageServiceDependencies.swift b/Sources/RemoteImage/private/Services/DefaultRemoteImageServiceDependencies.swift similarity index 60% rename from Sources/RemoteImage/private/Services/RemoteImageServiceDependencies.swift rename to Sources/RemoteImage/private/Services/DefaultRemoteImageServiceDependencies.swift index f5de93f..5a71980 100644 --- a/Sources/RemoteImage/private/Services/RemoteImageServiceDependencies.swift +++ b/Sources/RemoteImage/private/Services/DefaultRemoteImageServiceDependencies.swift @@ -1,5 +1,5 @@ // -// RemoteImageServiceDependencies.swift +// DefaultRemoteImageServiceDependencies.swift // RemoteImage // // Created by Christian Elies on 29.10.19. @@ -7,11 +7,11 @@ import Foundation -protocol RemoteImageServiceDependenciesProtocol: PhotoKitServiceProvider, RemoteImageURLDataPublisherProvider { +protocol DefaultRemoteImageServiceDependenciesProtocol: PhotoKitServiceProvider, RemoteImageURLDataPublisherProvider { } -struct RemoteImageServiceDependencies: RemoteImageServiceDependenciesProtocol { +struct DefaultRemoteImageServiceDependencies: DefaultRemoteImageServiceDependenciesProtocol { let photoKitService: PhotoKitServiceProtocol let remoteImageURLDataPublisher: RemoteImageURLDataPublisher diff --git a/Sources/RemoteImage/private/Services/PhotoKitService.swift b/Sources/RemoteImage/private/Services/PhotoKitService.swift index 606255e..ef67bbd 100644 --- a/Sources/RemoteImage/private/Services/PhotoKitService.swift +++ b/Sources/RemoteImage/private/Services/PhotoKitService.swift @@ -12,8 +12,10 @@ protocol PhotoKitServiceProvider { } protocol PhotoKitServiceProtocol { - func getPhotoData(localIdentifier: String, - _ completion: @escaping (Result) -> Void) + func getPhotoData( + localIdentifier: String, + _ completion: @escaping (Result) -> Void + ) } final class PhotoKitService { @@ -22,8 +24,10 @@ final class PhotoKitService { } extension PhotoKitService: PhotoKitServiceProtocol { - func getPhotoData(localIdentifier: String, - _ completion: @escaping (Result) -> Void) { + func getPhotoData( + localIdentifier: String, + _ completion: @escaping (Result) -> Void + ) { let fetchAssetsResult = Self.asset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil) guard let phAsset = fetchAssetsResult.firstObject else { completion(.failure(PhotoKitServiceError.phAssetNotFound(localIdentifier: localIdentifier))) diff --git a/Sources/RemoteImage/public/Models/RemoteImageState.swift b/Sources/RemoteImage/public/Models/RemoteImageState.swift new file mode 100644 index 0000000..6bf2e35 --- /dev/null +++ b/Sources/RemoteImage/public/Models/RemoteImageState.swift @@ -0,0 +1,15 @@ +// +// RemoteImageState.swift +// RemoteImage +// +// Created by Christian Elies on 11.08.19. +// Copyright © 2019 Christian Elies. All rights reserved. +// + +import Foundation + +public enum RemoteImageState: Hashable { + case error(_ error: NSError) + case image(_ image: UniversalImage) + case loading +} diff --git a/Sources/RemoteImage/private/Protocols/RemoteImageServiceProtocol.swift b/Sources/RemoteImage/public/Protocols/RemoteImageService.swift similarity index 63% rename from Sources/RemoteImage/private/Protocols/RemoteImageServiceProtocol.swift rename to Sources/RemoteImage/public/Protocols/RemoteImageService.swift index 4c9e1ae..8f57bd0 100644 --- a/Sources/RemoteImage/private/Protocols/RemoteImageServiceProtocol.swift +++ b/Sources/RemoteImage/public/Protocols/RemoteImageService.swift @@ -1,5 +1,5 @@ // -// RemoteImageServiceProtocol.swift +// RemoteImageService.swift // RemoteImage // // Created by Christian Elies on 15.12.19. @@ -7,7 +7,9 @@ import Combine -protocol RemoteImageServiceProtocol where Self: ObservableObject { +public typealias RemoteImageCacheKeyProvider = (RemoteImageType) -> AnyObject + +public protocol RemoteImageService where Self: ObservableObject { static var cache: RemoteImageCache { get set } static var cacheKeyProvider: RemoteImageCacheKeyProvider { get set } diff --git a/Sources/RemoteImage/public/Services/RemoteImageService.swift b/Sources/RemoteImage/public/Services/DefaultRemoteImageService.swift similarity index 85% rename from Sources/RemoteImage/public/Services/RemoteImageService.swift rename to Sources/RemoteImage/public/Services/DefaultRemoteImageService.swift index 9e17ec0..1f57c8a 100644 --- a/Sources/RemoteImage/public/Services/RemoteImageService.swift +++ b/Sources/RemoteImage/public/Services/DefaultRemoteImageService.swift @@ -1,5 +1,5 @@ // -// RemoteImageService.swift +// DefaultRemoteImageService.swift // RemoteImage // // Created by Christian Elies on 11.08.19. @@ -9,13 +9,11 @@ import Combine import Foundation -public typealias RemoteImageCacheKeyProvider = (RemoteImageType) -> AnyObject - -public final class RemoteImageService: NSObject, ObservableObject, RemoteImageServiceProtocol { - private let dependencies: RemoteImageServiceDependenciesProtocol +public final class DefaultRemoteImageService: NSObject, ObservableObject, RemoteImageService { + private let dependencies: DefaultRemoteImageServiceDependenciesProtocol private var cancellable: AnyCancellable? - @Published var state: RemoteImageState = .loading + @Published public var state: RemoteImageState = .loading public static var cache: RemoteImageCache = DefaultRemoteImageCache() public static var cacheKeyProvider: RemoteImageCacheKeyProvider = { remoteImageType in @@ -25,11 +23,11 @@ public final class RemoteImageService: NSObject, ObservableObject, RemoteImageSe } } - init(dependencies: RemoteImageServiceDependenciesProtocol) { + init(dependencies: DefaultRemoteImageServiceDependenciesProtocol) { self.dependencies = dependencies } - func fetchImage(ofType type: RemoteImageType) { + public func fetchImage(ofType type: RemoteImageType) { switch type { case .url(let url): fetchImage(atURL: url) @@ -39,7 +37,7 @@ public final class RemoteImageService: NSObject, ObservableObject, RemoteImageSe } } -private extension RemoteImageService { +private extension DefaultRemoteImageService { func fetchImage(atURL url: URL) { cancellable?.cancel() diff --git a/Sources/RemoteImage/public/Services/DefaultRemoteImageServiceFactory.swift b/Sources/RemoteImage/public/Services/DefaultRemoteImageServiceFactory.swift new file mode 100644 index 0000000..c9ef39b --- /dev/null +++ b/Sources/RemoteImage/public/Services/DefaultRemoteImageServiceFactory.swift @@ -0,0 +1,13 @@ +// +// DefaultRemoteImageServiceFactory.swift +// RemoteImage +// +// Created by Christian Elies on 29.10.19. +// + +public final class DefaultRemoteImageServiceFactory { + public static func makeDefaultRemoteImageService(remoteImageURLDataPublisher: RemoteImageURLDataPublisher) -> DefaultRemoteImageService { + let dependencies = DefaultRemoteImageServiceDependencies(remoteImageURLDataPublisher: remoteImageURLDataPublisher) + return DefaultRemoteImageService(dependencies: dependencies) + } +} diff --git a/Sources/RemoteImage/public/Services/RemoteImageServiceFactory.swift b/Sources/RemoteImage/public/Services/RemoteImageServiceFactory.swift deleted file mode 100644 index bc14c48..0000000 --- a/Sources/RemoteImage/public/Services/RemoteImageServiceFactory.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// RemoteImageServiceFactory.swift -// RemoteImage -// -// Created by Christian Elies on 29.10.19. -// - -public final class RemoteImageServiceFactory { - public static func makeRemoteImageService(remoteImageURLDataPublisher: RemoteImageURLDataPublisher) -> RemoteImageService { - let dependencies = RemoteImageServiceDependencies(remoteImageURLDataPublisher: remoteImageURLDataPublisher) - return RemoteImageService(dependencies: dependencies) - } -} diff --git a/Sources/RemoteImage/public/Views/RemoteImage.swift b/Sources/RemoteImage/public/Views/RemoteImage.swift index 97bc7a6..cdf2b8e 100644 --- a/Sources/RemoteImage/public/Views/RemoteImage.swift +++ b/Sources/RemoteImage/public/Views/RemoteImage.swift @@ -10,13 +10,13 @@ import Combine import SwiftUI -public struct RemoteImage: View { +public struct RemoteImage: View { private let type: RemoteImageType private let errorView: (Error) -> ErrorView private let imageView: (Image) -> ImageView private let loadingView: () -> LoadingView - @ObservedObject private var service: RemoteImageService + @ObservedObject private var service: Service public var body: some View { switch service.state { @@ -33,13 +33,41 @@ public struct RemoteImage: } } + /// <#Description#> + /// + /// - Parameters: + /// - type: <#type description#> + /// - service: <#service description#> + /// - errorView: <#errorView description#> + /// - imageView: <#imageView description#> + /// - loadingView: <#loadingView description#> + public init(type: RemoteImageType, service: Service, @ViewBuilder errorView: @escaping (Error) -> ErrorView, @ViewBuilder imageView: @escaping (Image) -> ImageView, @ViewBuilder loadingView: @escaping () -> LoadingView) { + self.type = type + self.errorView = errorView + self.imageView = imageView + self.loadingView = loadingView + _service = ObservedObject(wrappedValue: service) + + service.fetchImage(ofType: type) + } +} + +extension RemoteImage where Service == DefaultRemoteImageService { + /// <#Description#> + /// + /// - Parameters: + /// - type: <#type description#> + /// - remoteImageURLDataPublisher: <#remoteImageURLDataPublisher description#> + /// - errorView: <#errorView description#> + /// - imageView: <#imageView description#> + /// - loadingView: <#loadingView description#> public init(type: RemoteImageType, remoteImageURLDataPublisher: RemoteImageURLDataPublisher = URLSession.shared, @ViewBuilder errorView: @escaping (Error) -> ErrorView, @ViewBuilder imageView: @escaping (Image) -> ImageView, @ViewBuilder loadingView: @escaping () -> LoadingView) { self.type = type self.errorView = errorView self.imageView = imageView self.loadingView = loadingView - let service = RemoteImageServiceFactory.makeRemoteImageService(remoteImageURLDataPublisher: remoteImageURLDataPublisher) + let service = DefaultRemoteImageServiceFactory.makeDefaultRemoteImageService(remoteImageURLDataPublisher: remoteImageURLDataPublisher) _service = ObservedObject(wrappedValue: service) service.fetchImage(ofType: type) diff --git a/Tests/RemoteImageTests/Extensions/URLSession+RemoteImageURLDataPublisherTests.swift b/Tests/RemoteImageTests/Extensions/URLSession+RemoteImageURLDataPublisherTests.swift index cdcb8f1..2c37016 100644 --- a/Tests/RemoteImageTests/Extensions/URLSession+RemoteImageURLDataPublisherTests.swift +++ b/Tests/RemoteImageTests/Extensions/URLSession+RemoteImageURLDataPublisherTests.swift @@ -16,9 +16,8 @@ final class URLSession_RemoteImageURLDataPublisherTests: XCTestCase { return } let urlSession: URLSession = .shared - let urlRequest = URLRequest(url: url) - let dataTaskPublisher = urlSession.dataTaskPublisher(for: urlRequest).eraseToAnyPublisher() - let dataPublisher = urlSession.dataPublisher(for: urlRequest) + let dataTaskPublisher = urlSession.dataTaskPublisher(for: url).eraseToAnyPublisher() + let dataPublisher = urlSession.dataPublisher(for: url) XCTAssertEqual(dataPublisher.description, dataTaskPublisher.description) } diff --git a/Tests/RemoteImageTests/Mocks/MockRemoteImageServiceDependencies.swift b/Tests/RemoteImageTests/Mocks/MockRemoteImageServiceDependencies.swift index 9d22087..4c6d630 100644 --- a/Tests/RemoteImageTests/Mocks/MockRemoteImageServiceDependencies.swift +++ b/Tests/RemoteImageTests/Mocks/MockRemoteImageServiceDependencies.swift @@ -7,7 +7,7 @@ @testable import RemoteImage -struct MockRemoteImageServiceDependencies: RemoteImageServiceDependenciesProtocol { +struct MockRemoteImageServiceDependencies: DefaultRemoteImageServiceDependenciesProtocol { let photoKitService: PhotoKitServiceProtocol let remoteImageURLDataPublisher: RemoteImageURLDataPublisher diff --git a/Tests/RemoteImageTests/Mocks/MockRemoteImageURLDataPublisher.swift b/Tests/RemoteImageTests/Mocks/MockRemoteImageURLDataPublisher.swift index 3bc5b8f..1e549c6 100644 --- a/Tests/RemoteImageTests/Mocks/MockRemoteImageURLDataPublisher.swift +++ b/Tests/RemoteImageTests/Mocks/MockRemoteImageURLDataPublisher.swift @@ -12,7 +12,7 @@ import Foundation final class MockRemoteImageURLDataPublisher: RemoteImageURLDataPublisher { var publisher = PassthroughSubject<(data: Data, response: URLResponse), URLError>() - func dataPublisher(for request: URLRequest) -> AnyPublisher<(data: Data, response: URLResponse), URLError> { + func dataPublisher(for url: URL) -> AnyPublisher<(data: Data, response: URLResponse), URLError> { publisher.eraseToAnyPublisher() } } diff --git a/Tests/RemoteImageTests/Models/RemoteImageStateTests.swift b/Tests/RemoteImageTests/Models/RemoteImageStateTests.swift deleted file mode 100644 index c90a750..0000000 --- a/Tests/RemoteImageTests/Models/RemoteImageStateTests.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// RemoteImageStateTests.swift -// RemoteImageTests -// -// Created by Christian Elies on 23.02.20. -// - -#if canImport(UIKit) -@testable import RemoteImage -import UIKit -import XCTest - -final class RemoteImageStateTests: XCTestCase { - func testErrorValue() { - let error = NSError(domain: "MockDomain", code: 1, userInfo: nil) - let state: RemoteImageState = .error(error) - XCTAssertEqual(state.error, error) - } - - func testNoErrorValue() { - let state: RemoteImageState = .loading - XCTAssertNil(state.error) - } - - func testImageValue() { - let image = UIImage() - let state: RemoteImageState = .image(image) - XCTAssertEqual(state.image, image) - } - - func testNoImageValue() { - let state: RemoteImageState = .loading - XCTAssertNil(state.image) - } - - static var allTests = [ - ("testErrorValue", testErrorValue), - ("testNoErrorValue", testNoErrorValue), - ("testImageValue", testImageValue), - ("testNoImageValue", testNoImageValue) - ] -} -#endif diff --git a/Tests/RemoteImageTests/Services/RemoteImageServiceDependenciesTests.swift b/Tests/RemoteImageTests/Services/RemoteImageServiceDependenciesTests.swift index 55702d6..5f5ec55 100644 --- a/Tests/RemoteImageTests/Services/RemoteImageServiceDependenciesTests.swift +++ b/Tests/RemoteImageTests/Services/RemoteImageServiceDependenciesTests.swift @@ -11,7 +11,7 @@ import XCTest final class RemoteImageServiceDependenciesTests: XCTestCase { func testInitialization() { - let dependencies = RemoteImageServiceDependencies() + let dependencies = DefaultRemoteImageServiceDependencies(remoteImageURLDataPublisher: URLSession.shared) XCTAssertTrue(dependencies.photoKitService is PhotoKitService) XCTAssertTrue(dependencies.remoteImageURLDataPublisher is URLSession) } diff --git a/Tests/RemoteImageTests/Services/RemoteImageServiceFactoryTests.swift b/Tests/RemoteImageTests/Services/RemoteImageServiceFactoryTests.swift index 68fd1ea..5ddab1c 100644 --- a/Tests/RemoteImageTests/Services/RemoteImageServiceFactoryTests.swift +++ b/Tests/RemoteImageTests/Services/RemoteImageServiceFactoryTests.swift @@ -11,7 +11,7 @@ import XCTest final class RemoteImageServiceFactoryTests: XCTestCase { func testMakeRemoteImageService() { - let service = RemoteImageServiceFactory.makeRemoteImageService() + let service = DefaultRemoteImageServiceFactory.makeDefaultRemoteImageService(remoteImageURLDataPublisher: URLSession.shared) XCTAssertEqual(service.state, .loading) } diff --git a/Tests/RemoteImageTests/Services/RemoteImageServiceTests.swift b/Tests/RemoteImageTests/Services/RemoteImageServiceTests.swift index ae75791..8d12f38 100644 --- a/Tests/RemoteImageTests/Services/RemoteImageServiceTests.swift +++ b/Tests/RemoteImageTests/Services/RemoteImageServiceTests.swift @@ -18,10 +18,10 @@ final class RemoteImageServiceTests: XCTestCase { let dependencies = MockRemoteImageServiceDependencies() lazy var photoKitService = dependencies.photoKitService as? MockPhotoKitService lazy var remoteImageURLDataPublisher = dependencies.remoteImageURLDataPublisher as? MockRemoteImageURLDataPublisher - lazy var service = RemoteImageService(dependencies: dependencies) + lazy var service = DefaultRemoteImageService(dependencies: dependencies) override func setUp() { - RemoteImageService.cache.removeAllObjects() + DefaultRemoteImageService.cache.removeAllObjects() photoKitService?.resultToReturn = .success(Data()) } @@ -144,7 +144,7 @@ final class RemoteImageServiceTests: XCTestCase { } let cacheKey = url as NSURL - RemoteImageService.cache.setObject(image, forKey: cacheKey) + DefaultRemoteImageService.cache.setObject(image, forKey: cacheKey) let expectation = self.expectation(description: "FetchImageURLCached") let remoteImageType: RemoteImageType = .url(url) @@ -261,7 +261,7 @@ final class RemoteImageServiceTests: XCTestCase { let localIdentifier = "TestIdentifier" let cacheKey = localIdentifier as NSString - RemoteImageService.cache.setObject(image, forKey: cacheKey) + DefaultRemoteImageService.cache.setObject(image, forKey: cacheKey) let expectation = self.expectation(description: "FetchPHAssetCached") let remoteImageType: RemoteImageType = .phAsset(localIdentifier: localIdentifier) From 30e3dec1050a64e910644a23c76c69a3170c5fbd Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Mon, 23 Nov 2020 23:53:53 +0100 Subject: [PATCH 3/6] refactor(): removed unnecessary code --- .../RemoteImage/public/Services/DefaultRemoteImageService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/RemoteImage/public/Services/DefaultRemoteImageService.swift b/Sources/RemoteImage/public/Services/DefaultRemoteImageService.swift index 1f57c8a..bb41c57 100644 --- a/Sources/RemoteImage/public/Services/DefaultRemoteImageService.swift +++ b/Sources/RemoteImage/public/Services/DefaultRemoteImageService.swift @@ -9,7 +9,7 @@ import Combine import Foundation -public final class DefaultRemoteImageService: NSObject, ObservableObject, RemoteImageService { +public final class DefaultRemoteImageService: RemoteImageService { private let dependencies: DefaultRemoteImageServiceDependenciesProtocol private var cancellable: AnyCancellable? From 3aa0e24aa7587944409ffa8b836b65dbf65db541 Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Tue, 24 Nov 2020 21:14:35 +0100 Subject: [PATCH 4/6] refactor(default remote image service factory): added default parameter value --- .../public/Services/DefaultRemoteImageServiceFactory.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/RemoteImage/public/Services/DefaultRemoteImageServiceFactory.swift b/Sources/RemoteImage/public/Services/DefaultRemoteImageServiceFactory.swift index c9ef39b..7ef43c6 100644 --- a/Sources/RemoteImage/public/Services/DefaultRemoteImageServiceFactory.swift +++ b/Sources/RemoteImage/public/Services/DefaultRemoteImageServiceFactory.swift @@ -5,8 +5,10 @@ // Created by Christian Elies on 29.10.19. // +import Foundation + public final class DefaultRemoteImageServiceFactory { - public static func makeDefaultRemoteImageService(remoteImageURLDataPublisher: RemoteImageURLDataPublisher) -> DefaultRemoteImageService { + public static func makeDefaultRemoteImageService(remoteImageURLDataPublisher: RemoteImageURLDataPublisher = URLSession.shared) -> DefaultRemoteImageService { let dependencies = DefaultRemoteImageServiceDependencies(remoteImageURLDataPublisher: remoteImageURLDataPublisher) return DefaultRemoteImageService(dependencies: dependencies) } From 059657c4bed70c0f6bf0ebe26ee65da9a5830819 Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Tue, 24 Nov 2020 21:38:17 +0100 Subject: [PATCH 5/6] deprecate phAsset remote image type --- Sources/RemoteImage/public/Models/RemoteImageType.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/RemoteImage/public/Models/RemoteImageType.swift b/Sources/RemoteImage/public/Models/RemoteImageType.swift index 4c66654..7f08446 100644 --- a/Sources/RemoteImage/public/Models/RemoteImageType.swift +++ b/Sources/RemoteImage/public/Models/RemoteImageType.swift @@ -8,6 +8,7 @@ import Foundation public enum RemoteImageType { + @available(*, deprecated, message: "Will be removed in the future because the localIdentifier is device specific and therefore cannot be used to uniquely identify a PHAsset across devices.") case phAsset(localIdentifier: String) case url(_ url: URL) } From 868644be1e625a9de4f60dba03f173a4cbd5eb2e Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Tue, 24 Nov 2020 22:07:34 +0100 Subject: [PATCH 6/6] docs(): added some basic code documentation --- .../public/Protocols/RemoteImageService.swift | 8 ++++++ .../public/Views/RemoteImage.swift | 25 ++++++++++--------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Sources/RemoteImage/public/Protocols/RemoteImageService.swift b/Sources/RemoteImage/public/Protocols/RemoteImageService.swift index 8f57bd0..d62514c 100644 --- a/Sources/RemoteImage/public/Protocols/RemoteImageService.swift +++ b/Sources/RemoteImage/public/Protocols/RemoteImageService.swift @@ -9,10 +9,18 @@ import Combine public typealias RemoteImageCacheKeyProvider = (RemoteImageType) -> AnyObject +/// Represents the service associated with a `RemoteImage` view. Responsible for fetching the image and managing the state. public protocol RemoteImageService where Self: ObservableObject { + /// The cache for the images fetched by any instance of `RemoteImageService`. static var cache: RemoteImageCache { get set } + /// Provides a key for a given `RemoteImageType` used for storing an image in the cache. static var cacheKeyProvider: RemoteImageCacheKeyProvider { get set } + /// The current state of the image fetching process - `loading`, `error` or `image (success)`. var state: RemoteImageState { get set } + + /// Fetches the image with the given type. + /// + /// - Parameter type: Specifies the source type of the remote image. Choose between `.url` or `.phAsset`. func fetchImage(ofType type: RemoteImageType) } diff --git a/Sources/RemoteImage/public/Views/RemoteImage.swift b/Sources/RemoteImage/public/Views/RemoteImage.swift index cdf2b8e..a258ff5 100644 --- a/Sources/RemoteImage/public/Views/RemoteImage.swift +++ b/Sources/RemoteImage/public/Views/RemoteImage.swift @@ -10,6 +10,7 @@ import Combine import SwiftUI +/// A custom Image view for remote images with support for a loading and error state. public struct RemoteImage: View { private let type: RemoteImageType private let errorView: (Error) -> ErrorView @@ -33,14 +34,14 @@ public struct RemoteImage + /// Initializes the view with the given values, especially with a custom `RemoteImageService`. /// /// - Parameters: - /// - type: <#type description#> - /// - service: <#service description#> - /// - errorView: <#errorView description#> - /// - imageView: <#imageView description#> - /// - loadingView: <#loadingView description#> + /// - type: Specifies the source type of the remote image. Choose between `.url` or `.phAsset`. + /// - service: An object conforming to the `RemoteImageService` protocol. Responsible for fetching the image and managing the state. + /// - errorView: A view builder used to create the view displayed in the error state. + /// - imageView: A view builder used to create the `Image` displayed in the image state. + /// - loadingView: A view builder used to create the view displayed in the loading state. public init(type: RemoteImageType, service: Service, @ViewBuilder errorView: @escaping (Error) -> ErrorView, @ViewBuilder imageView: @escaping (Image) -> ImageView, @ViewBuilder loadingView: @escaping () -> LoadingView) { self.type = type self.errorView = errorView @@ -53,14 +54,14 @@ public struct RemoteImage + /// Initializes the view with the given values. Uses the built-in `DefaultRemoteImageService`. /// /// - Parameters: - /// - type: <#type description#> - /// - remoteImageURLDataPublisher: <#remoteImageURLDataPublisher description#> - /// - errorView: <#errorView description#> - /// - imageView: <#imageView description#> - /// - loadingView: <#loadingView description#> + /// - type: Specifies the source type of the remote image. Choose between `.url` or `.phAsset`. + /// - remoteImageURLDataPublisher: An object conforming to the `RemoteImageURLDataPublisher` protocol, by default `URLSession.shared` is used. + /// - errorView: A view builder used to create the view displayed in the error state. + /// - imageView: A view builder used to create the `Image` displayed in the image state. + /// - loadingView: A view builder used to create the view displayed in the loading state. public init(type: RemoteImageType, remoteImageURLDataPublisher: RemoteImageURLDataPublisher = URLSession.shared, @ViewBuilder errorView: @escaping (Error) -> ErrorView, @ViewBuilder imageView: @escaping (Image) -> ImageView, @ViewBuilder loadingView: @escaping () -> LoadingView) { self.type = type self.errorView = errorView