diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2020-03-26 12:42:23 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2020-03-26 16:23:13 +0100 |
| commit | 11fde0398a9e2a758c082dad02ea08de6844b70a (patch) | |
| tree | 28d53319c853e3d90df11d2b49b99baa7bb5df1a | |
| parent | 5eca8ab8efbb03938dfd01908c347822db981ab9 (diff) | |
| download | mullvadvpn-11fde0398a9e2a758c082dad02ea08de6844b70a.tar.xz mullvadvpn-11fde0398a9e2a758c082dad02ea08de6844b70a.zip | |
Add Combine publishers for SKRequest subclasses
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/SKRequestPublisher.swift | 121 |
2 files changed, 125 insertions, 0 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index e747d23f77..449a9f06cc 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -122,6 +122,7 @@ 58D0C7A223F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D0C7A023F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift */; }; 58EC4E6C23915325003F5C5B /* Bundle+MullvadVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58EC4E6B23915325003F5C5B /* Bundle+MullvadVersion.swift */; }; 58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */; }; + 58FD5BE92419406000112C88 /* SKRequestPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BE82419406000112C88 /* SKRequestPublisher.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -258,6 +259,7 @@ 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerActivityIndicatorView.swift; sourceTree = "<group>"; }; 58FBDAA422A52BDA00EB69A3 /* PacketTunnel-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PacketTunnel-Bridging-Header.h"; sourceTree = "<group>"; }; 58FBDAAA22A52DC500EB69A3 /* MullvadVPN-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MullvadVPN-Bridging-Header.h"; sourceTree = "<group>"; }; + 58FD5BE82419406000112C88 /* SKRequestPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SKRequestPublisher.swift; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -396,6 +398,7 @@ 58CCA01122424D11004F3011 /* SettingsViewController.swift */, 58BA693023EADA6A009DC256 /* SimulatorTunnelProvider.swift */, 587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */, + 58FD5BE82419406000112C88 /* SKRequestPublisher.swift */, 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */, 581CBCED229826FD00727D7F /* StaticTableViewDataSource.swift */, 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */, @@ -711,6 +714,7 @@ 58EC4E6C23915325003F5C5B /* Bundle+MullvadVersion.swift in Sources */, 58BA693123EADA6A009DC256 /* SimulatorTunnelProvider.swift in Sources */, 58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */, + 58FD5BE92419406000112C88 /* SKRequestPublisher.swift in Sources */, 582BB1B52295780F0055B6EF /* AccountExpiry.swift in Sources */, 582BB1B3229574F40055B6EF /* SettingsAccountCell.swift in Sources */, 58CCA010224249A1004F3011 /* ConnectViewController.swift in Sources */, diff --git a/ios/MullvadVPN/SKRequestPublisher.swift b/ios/MullvadVPN/SKRequestPublisher.swift new file mode 100644 index 0000000000..1e2786eb6e --- /dev/null +++ b/ios/MullvadVPN/SKRequestPublisher.swift @@ -0,0 +1,121 @@ +// +// SKRequestPublisher.swift +// MullvadVPN +// +// Created by pronebird on 11/03/2020. +// Copyright © 2020 Mullvad VPN AB. All rights reserved. +// + +import Combine +import Foundation +import StoreKit + +/// A protocol that formalizes the interface of all of the subclasses of +/// `SKRequestSubscription<Output>` +protocol SKRequestSubscriptionProtocol: Subscription { + associatedtype Output + associatedtype Failure: Error + + init<S: Subscriber>(request: SKRequest, subscriber: S) + where S.Input == Output, S.Failure == Failure +} + +/// A base implementation of subscription that handles the `SKRequest`. +class SKRequestSubscription<Output>: NSObject, Subscription, SKRequestDelegate, + SKRequestSubscriptionProtocol +{ + typealias Failure = Error + + private let request: SKRequest + fileprivate let subscriber: AnySubscriber<Output, Failure> + + required init<S: Subscriber>(request: SKRequest, subscriber: S) + where S.Input == Output, S.Failure == Failure + { + self.request = request + self.subscriber = AnySubscriber(subscriber) + + super.init() + request.delegate = self + } + + func request(_ demand: Subscribers.Demand) { + request.start() + } + + func cancel() { + request.cancel() + } + + // MARK: - SKRequestDelegate + + func request(_ request: SKRequest, didFailWithError error: Error) { + subscriber.receive(completion: .failure(error)) + } + + func requestDidFinish(_ request: SKRequest) { + subscriber.receive(completion: .finished) + } +} + +/// A subscription that emits the `SKProductsResponse` upon request completion +class SKProductsRequestSubscription: SKRequestSubscription<SKProductsResponse>, + SKProductsRequestDelegate +{ + + // MARK: - SKProductsRequestDelegate + + func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { + _ = self.subscriber.receive(response) + } + +} + +/// A subscription for requesting the AppStore receipt refresh +class SKRefreshRequestSubscription: SKRequestSubscription<()> { + override func requestDidFinish(_ request: SKRequest) { + // Emit void so that publishers using this subscription could be chained + _ = self.subscriber.receive(()) + + super.requestDidFinish(request) + } +} + +/// A base implementation of publisher that runs `SKRequest`s +class SKRequestPublisher<SubscriptionType>: Publisher + where SubscriptionType: SKRequestSubscriptionProtocol +{ + typealias Output = SubscriptionType.Output + typealias Failure = SubscriptionType.Failure + + fileprivate let request: SKRequest + + init(request: SKRequest) { + self.request = request + } + + func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input { + let subscription = SubscriptionType(request: request, subscriber: subscriber) + + subscriber.receive(subscription: subscription) + } + +} + +protocol SKRequestPublishing { + associatedtype SubscriptionType: SKRequestSubscriptionProtocol + + var publisher: SKRequestPublisher<SubscriptionType> { get } +} + +extension SKProductsRequest: SKRequestPublishing { + var publisher: SKRequestPublisher<SKProductsRequestSubscription> { + return .init(request: self) + } +} + +extension SKReceiptRefreshRequest: SKRequestPublishing { + var publisher: SKRequestPublisher<SKRefreshRequestSubscription> { + return .init(request: self) + } +} |
