summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2020-03-26 12:42:23 +0100
committerAndrej Mihajlov <and@mullvad.net>2020-03-26 16:23:13 +0100
commit11fde0398a9e2a758c082dad02ea08de6844b70a (patch)
tree28d53319c853e3d90df11d2b49b99baa7bb5df1a
parent5eca8ab8efbb03938dfd01908c347822db981ab9 (diff)
downloadmullvadvpn-11fde0398a9e2a758c082dad02ea08de6844b70a.tar.xz
mullvadvpn-11fde0398a9e2a758c082dad02ea08de6844b70a.zip
Add Combine publishers for SKRequest subclasses
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/SKRequestPublisher.swift121
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)
+ }
+}