summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2023-05-04 11:28:56 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-05-04 11:28:56 +0200
commit6ed13e3a8ad510f2d2f751ffe57301252294160e (patch)
treeb538ac455cb39b51c849a6b0c52384e26b7db150
parent492b4572963f0ab42a3150bb92d36a1c495eb515 (diff)
parentb1b1c15666c08b1a2a3185b7f0a30befd67b98a8 (diff)
downloadmullvadvpn-6ed13e3a8ad510f2d2f751ffe57301252294160e.tar.xz
mullvadvpn-6ed13e3a8ad510f2d2f751ffe57301252294160e.zip
Merge branch 'cancellable-initializer'
-rw-r--r--ios/MullvadREST/RESTAccessTokenManager.swift48
-rw-r--r--ios/MullvadTypes/Cancellable.swift19
-rw-r--r--ios/MullvadTypes/Result+Extensions.swift (renamed from ios/MullvadVPN/Extensions/Result+Extensions.swift)12
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj41
-rw-r--r--ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift14
-rw-r--r--ios/MullvadVPN/AppDelegate.swift14
-rw-r--r--ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift17
-rw-r--r--ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift15
-rw-r--r--ios/MullvadVPN/TunnelManager/SetAccountOperation.swift122
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift23
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/MapViewController.swift4
-rw-r--r--ios/Operations/AsyncBlockOperation.swift79
-rw-r--r--ios/Operations/AsyncOperation.swift5
-rw-r--r--ios/Operations/ResultBlockOperation.swift103
-rw-r--r--ios/Operations/TransformOperation.swift92
-rw-r--r--ios/OperationsTests/AsyncBlockOperationTests.swift99
-rw-r--r--ios/OperationsTests/AsyncResultBlockOperationTests.swift75
-rw-r--r--ios/OperationsTests/OperationCancellationTests.swift35
-rw-r--r--ios/OperationsTests/OperationConditionTests.swift4
-rw-r--r--ios/OperationsTests/OperationObserverTests.swift4
-rw-r--r--ios/OperationsTests/TransformOperationTests.swift93
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider.swift57
22 files changed, 531 insertions, 444 deletions
diff --git a/ios/MullvadREST/RESTAccessTokenManager.swift b/ios/MullvadREST/RESTAccessTokenManager.swift
index d8d7ef5521..8b374b88d5 100644
--- a/ios/MullvadREST/RESTAccessTokenManager.swift
+++ b/ios/MullvadREST/RESTAccessTokenManager.swift
@@ -27,42 +27,34 @@ extension REST {
accountNumber: String,
completionHandler: @escaping (Result<REST.AccessTokenData, Swift.Error>) -> Void
) -> Cancellable {
- let operation = ResultBlockOperation<REST.AccessTokenData>(dispatchQueue: dispatchQueue)
+ let operation =
+ ResultBlockOperation<REST.AccessTokenData>(dispatchQueue: dispatchQueue) { finish -> Cancellable in
+ if let tokenData = self.tokens[accountNumber], tokenData.expiry > Date() {
+ finish(.success(tokenData))
+ return AnyCancellable()
+ }
- operation.setExecutionBlock { operation in
- if let tokenData = self.tokens[accountNumber], tokenData.expiry > Date() {
- operation.finish(result: .success(tokenData))
- return
- }
+ return self.proxy.getAccessToken(accountNumber: accountNumber, retryStrategy: .noRetry) { result in
+ self.dispatchQueue.async {
+ switch result {
+ case let .success(tokenData):
+ self.tokens[accountNumber] = tokenData
- let task = self.proxy.getAccessToken(
- accountNumber: accountNumber,
- retryStrategy: .noRetry
- ) { result in
- self.dispatchQueue.async {
- switch result {
- case let .success(tokenData):
- self.tokens[accountNumber] = tokenData
+ case let .failure(error) where !error.isOperationCancellationError:
+ self.logger.error(
+ error: error,
+ message: "Failed to fetch access token."
+ )
- case let .failure(error) where !error.isOperationCancellationError:
- self.logger.error(
- error: error,
- message: "Failed to fetch access token."
- )
+ default:
+ break
+ }
- default:
- break
+ finish(result)
}
-
- operation.finish(result: result)
}
}
- operation.addCancellationBlock {
- task.cancel()
- }
- }
-
operation.completionQueue = .main
operation.completionHandler = completionHandler
diff --git a/ios/MullvadTypes/Cancellable.swift b/ios/MullvadTypes/Cancellable.swift
index a00b76eeda..8f658a6da1 100644
--- a/ios/MullvadTypes/Cancellable.swift
+++ b/ios/MullvadTypes/Cancellable.swift
@@ -13,3 +13,22 @@ public protocol Cancellable {
}
extension Operation: Cancellable {}
+
+/// An object representing a cancellation token.
+public final class AnyCancellable: Cancellable {
+ private let block: (() -> Void)?
+
+ /// Create cancellation token with block handler.
+ public init(block: @escaping () -> Void) {
+ self.block = block
+ }
+
+ /// Create empty cancellation token.
+ public init() {
+ block = nil
+ }
+
+ public func cancel() {
+ block?()
+ }
+}
diff --git a/ios/MullvadVPN/Extensions/Result+Extensions.swift b/ios/MullvadTypes/Result+Extensions.swift
index 204df0714e..a471d9ab26 100644
--- a/ios/MullvadVPN/Extensions/Result+Extensions.swift
+++ b/ios/MullvadTypes/Result+Extensions.swift
@@ -9,7 +9,7 @@
import Foundation
extension Result {
- var value: Success? {
+ public var value: Success? {
switch self {
case let .success(value):
return value
@@ -18,7 +18,7 @@ extension Result {
}
}
- var error: Failure? {
+ public var error: Failure? {
switch self {
case .success:
return nil
@@ -27,7 +27,7 @@ extension Result {
}
}
- var isSuccess: Bool {
+ public var isSuccess: Bool {
switch self {
case .success:
return true
@@ -36,7 +36,7 @@ extension Result {
}
}
- func tryMap<NewSuccess>(_ body: (Success) throws -> NewSuccess) -> Result<NewSuccess, Error> {
+ public func tryMap<NewSuccess>(_ body: (Success) throws -> NewSuccess) -> Result<NewSuccess, Error> {
return Result<NewSuccess, Error> {
let value = try self.get()
@@ -44,7 +44,7 @@ extension Result {
}
}
- @discardableResult func inspectError(_ body: (Failure) -> Void) -> Self {
+ @discardableResult public func inspectError(_ body: (Failure) -> Void) -> Self {
if case let .failure(error) = self {
body(error)
}
@@ -53,7 +53,7 @@ extension Result {
}
extension Result {
- func flattenValue<T>() -> T? where Success == T? {
+ public func flattenValue<T>() -> T? where Success == T? {
switch self {
case let .success(optional):
return optional.flatMap { $0 }
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index d12da06daa..2dbb51e2b2 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -26,7 +26,6 @@
06410E05292D0FC000AFC18C /* SettingsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06410E03292D0F7100AFC18C /* SettingsParser.swift */; };
06410E07292D108E00AFC18C /* SettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06410E06292D108E00AFC18C /* SettingsStore.swift */; };
06410E08292D117800AFC18C /* SettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06410E06292D108E00AFC18C /* SettingsStore.swift */; };
- 06410E09292D990C00AFC18C /* Result+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F1311427E0B2AB007AC5BC /* Result+Extensions.swift */; };
06799ACE28F98E1D00ACD94E /* MullvadREST.h in Headers */ = {isa = PBXBuildFile; fileRef = 06799ABE28F98E1D00ACD94E /* MullvadREST.h */; settings = {ATTRIBUTES = (Public, ); }; };
06799AD128F98E1D00ACD94E /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; };
06799AD228F98E1D00ACD94E /* MullvadREST.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -117,7 +116,6 @@
585E820327F3285E00939F0E /* SendStoreReceiptOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */; };
58607A4D2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58607A4C2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift */; };
586168692976F6BD00EF8598 /* DisplayError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586168682976F6BD00EF8598 /* DisplayError.swift */; };
- 586250BB29E6F8F300F4B521 /* OperationCancellationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586250BA29E6F8F300F4B521 /* OperationCancellationTests.swift */; };
5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */; };
5864211F29F04CED00822139 /* UIBarButtonItem+Blocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5864211E29F04CED00822139 /* UIBarButtonItem+Blocks.swift */; };
5864859929A0D028006C5743 /* FormsheetPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5864859829A0D028006C5743 /* FormsheetPresentationController.swift */; };
@@ -222,6 +220,9 @@
58ACF64B26553C3F00ACE4B7 /* SettingsSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */; };
58ACF64D26567A5000ACE4B7 /* CustomSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */; };
58ACF64F26567A7100ACE4B7 /* CustomSwitchContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */; };
+ 58AFC99529F96F7B000829DE /* AsyncBlockOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AFC99429F96F7B000829DE /* AsyncBlockOperationTests.swift */; };
+ 58AFC99729F9753D000829DE /* AsyncResultBlockOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AFC99629F9753D000829DE /* AsyncResultBlockOperationTests.swift */; };
+ 58AFC99929F97856000829DE /* TransformOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AFC99829F97856000829DE /* TransformOperationTests.swift */; };
58B0A2A8238EE68200BC001D /* RelaySelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584B26F3237434D00073B10E /* RelaySelectorTests.swift */; };
58B26E1E2943514300D5980C /* InAppNotificationDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E1D2943514300D5980C /* InAppNotificationDescriptor.swift */; };
58B26E22294351EA00D5980C /* InAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E21294351EA00D5980C /* InAppNotificationProvider.swift */; };
@@ -335,13 +336,13 @@
58E11188292FA11F009FCA84 /* SettingsMigrationUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E11187292FA11F009FCA84 /* SettingsMigrationUIHandler.swift */; };
58E20771274672CA00DE5D77 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E20770274672CA00DE5D77 /* LaunchViewController.swift */; };
58E25F812837BBBB002CFB2C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E25F802837BBBB002CFB2C /* SceneDelegate.swift */; };
+ 58E45A5729F12C5100281ECF /* Result+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F1311427E0B2AB007AC5BC /* Result+Extensions.swift */; };
58E511E628DDDEAC00B0BCDE /* CodingErrors+CustomErrorDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E511E528DDDEAC00B0BCDE /* CodingErrors+CustomErrorDescription.swift */; };
58E511E828DDDF2400B0BCDE /* CodingErrors+CustomErrorDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E511E528DDDEAC00B0BCDE /* CodingErrors+CustomErrorDescription.swift */; };
58EE2E3A272FF814003BFF93 /* SettingsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58EE2E38272FF814003BFF93 /* SettingsDataSource.swift */; };
58EE2E3B272FF814003BFF93 /* SettingsDataSourceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58EE2E39272FF814003BFF93 /* SettingsDataSourceDelegate.swift */; };
58EF580B25D69D7A00AEBA94 /* ProblemReportSubmissionOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58EF580A25D69D7A00AEBA94 /* ProblemReportSubmissionOverlayView.swift */; };
58EF581125D69DB400AEBA94 /* StatusImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58EF581025D69DB400AEBA94 /* StatusImageView.swift */; };
- 58F1311527E0B2AB007AC5BC /* Result+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F1311427E0B2AB007AC5BC /* Result+Extensions.swift */; };
58F185AA298A3E3E00075977 /* TunnelCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F185A9298A3E3E00075977 /* TunnelCoordinator.swift */; };
58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */; };
58F2E144276A13F300A79513 /* StartTunnelOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2E143276A13F300A79513 /* StartTunnelOperation.swift */; };
@@ -556,6 +557,13 @@
remoteGlobalIDString = 5898D28829017BD300EB5EBA;
remoteInfo = TunnelProviderMessaging;
};
+ 58EED36D29FBEF040000CBAF /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 58CE5E58224146200008646E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 58D223D4294C8E5E0029F5F8;
+ remoteInfo = MullvadTypes;
+ };
58FBDAA122A52A6800EB69A3 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 58CE5E58224146200008646E /* Project object */;
@@ -749,7 +757,6 @@
585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendStoreReceiptOperation.swift; sourceTree = "<group>"; };
58607A4C2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiryInAppNotificationProvider.swift; sourceTree = "<group>"; };
586168682976F6BD00EF8598 /* DisplayError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayError.swift; sourceTree = "<group>"; };
- 586250BA29E6F8F300F4B521 /* OperationCancellationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationCancellationTests.swift; sourceTree = "<group>"; };
5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslucentButtonBlurView.swift; sourceTree = "<group>"; };
5864211E29F04CED00822139 /* UIBarButtonItem+Blocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+Blocks.swift"; sourceTree = "<group>"; };
5864859829A0D028006C5743 /* FormsheetPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormsheetPresentationController.swift; sourceTree = "<group>"; };
@@ -852,6 +859,9 @@
58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSwitch.swift; sourceTree = "<group>"; };
58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSwitchContainer.swift; sourceTree = "<group>"; };
58AEEF642344A36000C9BBD5 /* KeychainError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainError.swift; sourceTree = "<group>"; };
+ 58AFC99429F96F7B000829DE /* AsyncBlockOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncBlockOperationTests.swift; sourceTree = "<group>"; };
+ 58AFC99629F9753D000829DE /* AsyncResultBlockOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncResultBlockOperationTests.swift; sourceTree = "<group>"; };
+ 58AFC99829F97856000829DE /* TransformOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransformOperationTests.swift; sourceTree = "<group>"; };
58B0A2A0238EE67E00BC001D /* MullvadVPNTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MullvadVPNTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
58B0A2A4238EE67E00BC001D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
58B26E1D2943514300D5980C /* InAppNotificationDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppNotificationDescriptor.swift; sourceTree = "<group>"; };
@@ -1201,6 +1211,7 @@
58900D0228BBDCC70094E4F0 /* FixedWidthInteger+Arithmetics.swift */,
06410E172934F43B00AFC18C /* PacketTunnelErrorWrapper.swift */,
58CAFA01298530DC00BE19F7 /* Promise.swift */,
+ 58F1311427E0B2AB007AC5BC /* Result+Extensions.swift */,
58D223D7294C8E5E0029F5F8 /* MullvadTypes.h */,
);
path = MullvadTypes;
@@ -1432,7 +1443,6 @@
5871FB9F254C26BF0051A0A4 /* NSRegularExpression+IPAddress.swift */,
06FAE67828F83CA50033DD93 /* RESTCreateApplePaymentResponse+Localization.swift */,
58B9EB142489139B00095626 /* RESTError+Display.swift */,
- 58F1311427E0B2AB007AC5BC /* Result+Extensions.swift */,
58A8EE592976BFBB009C0F8D /* SKError+Localized.swift */,
58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */,
58A8EE5D2976DB00009C0F8D /* StorePaymentManagerError+Display.swift */,
@@ -1643,11 +1653,13 @@
589A455328E094B300565204 /* OperationsTests */ = {
isa = PBXGroup;
children = (
- 58DF5B7E2852778600E92647 /* OperationSmokeTests.swift */,
+ 58AFC99429F96F7B000829DE /* AsyncBlockOperationTests.swift */,
+ 58AFC99629F9753D000829DE /* AsyncResultBlockOperationTests.swift */,
+ 580CBFB72848D503007878F0 /* OperationConditionTests.swift */,
58DF5B772852178600E92647 /* OperationInputInjectionTests.swift */,
583E1E292848DF67004838B3 /* OperationObserverTests.swift */,
- 580CBFB72848D503007878F0 /* OperationConditionTests.swift */,
- 586250BA29E6F8F300F4B521 /* OperationCancellationTests.swift */,
+ 58DF5B7E2852778600E92647 /* OperationSmokeTests.swift */,
+ 58AFC99829F97856000829DE /* TransformOperationTests.swift */,
);
path = OperationsTests;
sourceTree = "<group>";
@@ -2198,6 +2210,7 @@
buildRules = (
);
dependencies = (
+ 58EED36E29FBEF040000CBAF /* PBXTargetDependency */,
);
name = Operations;
productName = Operations;
@@ -2568,9 +2581,11 @@
files = (
589A455F28E094BF00565204 /* OperationConditionTests.swift in Sources */,
589A455E28E094BF00565204 /* OperationInputInjectionTests.swift in Sources */,
- 586250BB29E6F8F300F4B521 /* OperationCancellationTests.swift in Sources */,
+ 58AFC99529F96F7B000829DE /* AsyncBlockOperationTests.swift in Sources */,
+ 58AFC99729F9753D000829DE /* AsyncResultBlockOperationTests.swift in Sources */,
589A455C28E094BF00565204 /* OperationSmokeTests.swift in Sources */,
589A455D28E094BF00565204 /* OperationObserverTests.swift in Sources */,
+ 58AFC99929F97856000829DE /* TransformOperationTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2627,7 +2642,6 @@
5878A27B2909649A0096FC88 /* CustomOverlayRenderer.swift in Sources */,
5847D58D29B7740F008C3808 /* RevokedCoordinator.swift in Sources */,
588527B2276B3F0700BAA373 /* LoadTunnelConfigurationOperation.swift in Sources */,
- 58F1311527E0B2AB007AC5BC /* Result+Extensions.swift in Sources */,
5867770E29096984006F721F /* OutOfTimeInteractor.swift in Sources */,
58F185AA298A3E3E00075977 /* TunnelCoordinator.swift in Sources */,
58F8AC0E25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift in Sources */,
@@ -2795,7 +2809,6 @@
files = (
5806767C27048E9B00C858CB /* PacketTunnelProvider.swift in Sources */,
587AD7C723421D8600E93A53 /* TunnelSettingsV1.swift in Sources */,
- 06410E09292D990C00AFC18C /* Result+Extensions.swift in Sources */,
5893C6FA29C1B481009090D1 /* DNSSettings.swift in Sources */,
58CE38C828992C9200A6D6E5 /* TunnelMonitorDelegate.swift in Sources */,
068CE5782927BE4800A068BB /* Migration.swift in Sources */,
@@ -2863,6 +2876,7 @@
58D22408294C90210029F5F8 /* AnyIPEndpoint.swift in Sources */,
58D22409294C90210029F5F8 /* AnyIPAddress.swift in Sources */,
58D2240A294C90210029F5F8 /* IPAddress+Codable.swift in Sources */,
+ 58E45A5729F12C5100281ECF /* Result+Extensions.swift in Sources */,
58D2240B294C90210029F5F8 /* Cancellable.swift in Sources */,
58D2240C294C90210029F5F8 /* WrappingError.swift in Sources */,
58D2240D294C90210029F5F8 /* CustomErrorDescriptionProtocol.swift in Sources */,
@@ -3043,6 +3057,11 @@
target = 5898D28829017BD300EB5EBA /* TunnelProviderMessaging */;
targetProxy = 58D22433294C94890029F5F8 /* PBXContainerItemProxy */;
};
+ 58EED36E29FBEF040000CBAF /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 58D223D4294C8E5E0029F5F8 /* MullvadTypes */;
+ targetProxy = 58EED36D29FBEF040000CBAF /* PBXContainerItemProxy */;
+ };
58FBDAA222A52A6800EB69A3 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 58FBDA9722A519BC00EB69A3 /* WireGuardGoBridge */;
diff --git a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
index 8c3100f92b..d7c402c802 100644
--- a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
+++ b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
@@ -85,20 +85,16 @@ final class AddressCacheTracker {
}
func updateEndpoints(completionHandler: ((Result<Bool, Error>) -> Void)? = nil) -> Cancellable {
- let operation = ResultBlockOperation<Bool> { operation in
+ let operation = ResultBlockOperation<Bool> { finish -> Cancellable in
guard self.nextScheduleDate() <= Date() else {
- operation.finish(result: .success(false))
- return
+ finish(.success(false))
+ return AnyCancellable()
}
- let task = self.apiProxy.getAddressList(retryStrategy: .default) { result in
+ return self.apiProxy.getAddressList(retryStrategy: .default) { result in
self.setEndpoints(from: result)
- operation.finish(result: result.map { _ in true })
- }
-
- operation.addCancellationBlock {
- task.cancel()
+ finish(result.map { _ in true })
}
}
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index 640d7fc86c..233c17d73f 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -370,7 +370,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
private func startInitialization(application: UIApplication) {
let wipeSettingsOperation = getWipeSettingsOperation()
- let loadTunnelStoreOperation = AsyncBlockOperation(dispatchQueue: .main) { operation in
+ let loadTunnelStoreOperation = AsyncBlockOperation(dispatchQueue: .main) { finish in
self.tunnelStore.loadPersistentTunnels { error in
if let error = error {
self.logger.error(
@@ -378,16 +378,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
message: "Failed to load persistent tunnels."
)
}
- operation.finish()
+ finish(nil)
}
}
- let migrateSettingsOperation = ResultBlockOperation<SettingsMigrationResult>(
- dispatchQueue: .main
- ) { operation in
+ let migrateSettingsOperation = ResultBlockOperation<SettingsMigrationResult>(dispatchQueue: .main) { finish in
SettingsManager.migrateStore(with: self.proxyFactory) { migrationResult in
let finishHandler = {
- operation.finish(result: .success(migrationResult))
+ finish(.success(migrationResult))
}
guard case let .failure(error) = migrationResult,
@@ -404,7 +402,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
migrateSettingsOperation.addDependencies([wipeSettingsOperation, loadTunnelStoreOperation])
- let initTunnelManagerOperation = AsyncBlockOperation(dispatchQueue: .main) { operation in
+ let initTunnelManagerOperation = AsyncBlockOperation(dispatchQueue: .main) { finish in
self.tunnelManager.loadConfiguration { error in
// TODO: avoid throwing fatal error and show the problem report UI instead.
if let error = error {
@@ -416,7 +414,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
NotificationManager.shared.updateNotifications()
self.storePaymentManager.startPaymentQueueMonitoring()
- operation.finish()
+ finish(nil)
}
}
initTunnelManagerOperation.addDependency(migrateSettingsOperation)
diff --git a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
index 75b73b2119..402063052d 100644
--- a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
+++ b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
@@ -97,23 +97,16 @@ final class RelayCacheTracker {
func updateRelays(completionHandler: ((Result<RelaysFetchResult, Error>) -> Void)? = nil)
-> Cancellable
{
- let operation = ResultBlockOperation<RelaysFetchResult>(dispatchQueue: nil) { operation in
+ let operation = ResultBlockOperation<RelaysFetchResult> { finish in
let cachedRelays = try? self.getCachedRelays()
if self.getNextUpdateDate() > Date() {
- operation.finish(result: .success(.throttled))
- return
+ finish(.success(.throttled))
+ return AnyCancellable()
}
- let task = self.apiProxy.getRelays(
- etag: cachedRelays?.etag,
- retryStrategy: .noRetry
- ) { result in
- operation.finish(result: self.handleResponse(result: result))
- }
-
- operation.addCancellationBlock {
- task.cancel()
+ return self.apiProxy.getRelays(etag: cachedRelays?.etag, retryStrategy: .noRetry) { result in
+ finish(self.handleResponse(result: result))
}
}
diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift
index 40e1b6b7e3..62ff2f11f9 100644
--- a/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift
+++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift
@@ -181,17 +181,12 @@ final class StorePaymentManager: NSObject, SKPaymentTransactionObserver {
accountNumber: String,
completionHandler: @escaping (StorePaymentManagerError?) -> Void
) {
- let accountOperation = ResultBlockOperation<REST.AccountData>(dispatchQueue: .main) { op in
- let task = self.accountsProxy.getAccountData(
+ let accountOperation = ResultBlockOperation<REST.AccountData>(dispatchQueue: .main) { finish in
+ return self.accountsProxy.getAccountData(
accountNumber: accountNumber,
- retryStrategy: .default
- ) { result in
- op.finish(result: result)
- }
-
- op.addCancellationBlock {
- task.cancel()
- }
+ retryStrategy: .default,
+ completion: finish
+ )
}
accountOperation.addObserver(BackgroundObserver(
diff --git a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
index 4f99c86bdc..e13e4373fe 100644
--- a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
@@ -9,6 +9,7 @@
import Foundation
import MullvadLogging
import MullvadREST
+import MullvadTypes
import Operations
import class WireGuardKitTypes.PrivateKey
import class WireGuardKitTypes.PublicKey
@@ -183,12 +184,10 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
}
private func getCreateAccountOperation() -> ResultBlockOperation<StoredAccountData> {
- let operation = ResultBlockOperation<StoredAccountData>(dispatchQueue: dispatchQueue)
-
- operation.setExecutionBlock { operation in
+ return ResultBlockOperation<StoredAccountData>(dispatchQueue: dispatchQueue) { finish -> Cancellable in
self.logger.debug("Create new account...")
- let task = self.accountsProxy.createAccount(retryStrategy: .default) { result in
+ return self.accountsProxy.createAccount(retryStrategy: .default) { result in
let result = result.inspectError { error in
guard !error.isOperationCancellationError else { return }
@@ -206,55 +205,37 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
)
}
- operation.finish(result: result)
- }
-
- operation.addCancellationBlock {
- task.cancel()
+ finish(result)
}
}
-
- return operation
}
- private func getExistingAccountOperation(accountNumber: String)
- -> ResultOperation<StoredAccountData>
- {
- let operation = ResultBlockOperation<StoredAccountData>(dispatchQueue: dispatchQueue)
-
- operation.setExecutionBlock { operation in
+ private func getExistingAccountOperation(accountNumber: String) -> ResultOperation<StoredAccountData> {
+ return ResultBlockOperation<StoredAccountData>(dispatchQueue: dispatchQueue) { finish -> Cancellable in
self.logger.debug("Request account data...")
- let task = self.accountsProxy.getAccountData(
- accountNumber: accountNumber,
- retryStrategy: .default
- ) { result in
- let result = result.inspectError { error in
- guard !error.isOperationCancellationError else { return }
-
- self.logger.error(
- error: error,
- message: "Failed to receive account data."
- )
- }.map { accountData -> StoredAccountData in
- self.logger.debug("Received account data.")
+ return self.accountsProxy
+ .getAccountData(accountNumber: accountNumber, retryStrategy: .default) { result in
+ let result = result.inspectError { error in
+ guard !error.isOperationCancellationError else { return }
- return StoredAccountData(
- identifier: accountData.id,
- number: accountNumber,
- expiry: accountData.expiry
- )
- }
+ self.logger.error(
+ error: error,
+ message: "Failed to receive account data."
+ )
+ }.map { accountData -> StoredAccountData in
+ self.logger.debug("Received account data.")
- operation.finish(result: result)
- }
+ return StoredAccountData(
+ identifier: accountData.id,
+ number: accountNumber,
+ expiry: accountData.expiry
+ )
+ }
- operation.addCancellationBlock {
- task.cancel()
- }
+ finish(result)
+ }
}
-
- return operation
}
private func getDeleteDeviceOperation() -> AsyncBlockOperation? {
@@ -262,12 +243,10 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
return nil
}
- let operation = AsyncBlockOperation(dispatchQueue: dispatchQueue)
-
- operation.setExecutionBlock { operation in
+ let operation = AsyncBlockOperation(dispatchQueue: dispatchQueue) { finish -> Cancellable in
self.logger.debug("Delete current device...")
- let task = self.devicesProxy.deleteDevice(
+ return self.devicesProxy.deleteDevice(
accountNumber: accountData.number,
identifier: deviceData.identifier,
retryStrategy: .default
@@ -286,11 +265,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
break
}
- operation.finish(error: result.error)
- }
-
- operation.addCancellationBlock {
- task.cancel()
+ finish(result.error)
}
}
@@ -298,7 +273,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
}
private func getUnsetDeviceStateOperation() -> AsyncBlockOperation {
- return AsyncBlockOperation(dispatchQueue: dispatchQueue) { operation in
+ return AsyncBlockOperation(dispatchQueue: dispatchQueue) { finish in
// Tell the caller to unsubscribe from VPN status notifications.
self.interactor.prepareForVPNConfigurationDeletion()
@@ -311,7 +286,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
// Finish immediately if tunnel provider is not set.
guard let tunnel = self.interactor.tunnel else {
- operation.finish()
+ finish(nil)
return
}
@@ -328,21 +303,17 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
self.interactor.setTunnel(nil, shouldRefreshTunnelState: false)
- operation.finish()
+ finish(nil)
}
}
}
}
- private func getCreateDeviceOperation()
- -> TransformOperation<StoredAccountData, (PrivateKey, REST.Device)>
- {
- let createDeviceOperation = TransformOperation<
- StoredAccountData,
- (PrivateKey, REST.Device)
- >(dispatchQueue: dispatchQueue)
-
- createDeviceOperation.setExecutionBlock { storedAccountData, operation in
+ private func getCreateDeviceOperation() -> TransformOperation<StoredAccountData, (PrivateKey, REST.Device)> {
+ return TransformOperation<StoredAccountData, (
+ PrivateKey,
+ REST.Device
+ )>(dispatchQueue: dispatchQueue) { storedAccountData, finish -> Cancellable in
self.logger.debug("Store last used account.")
do {
@@ -363,7 +334,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
hijackDNS: false
)
- let task = self.devicesProxy.createDevice(
+ return self.devicesProxy.createDevice(
accountNumber: storedAccountData.number,
request: request,
retryStrategy: .default
@@ -376,26 +347,13 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
self.logger.error(error: error, message: "Failed to create device.")
}
- operation.finish(result: result)
- }
-
- operation.addCancellationBlock {
- task.cancel()
+ finish(result)
}
}
-
- return createDeviceOperation
}
- private func getSaveSettingsOperation()
- -> TransformOperation<SetAccountResult, StoredAccountData>
- {
- let saveSettingsOperation = TransformOperation<
- SetAccountResult,
- StoredAccountData
- >(dispatchQueue: dispatchQueue)
-
- saveSettingsOperation.setExecutionBlock { input in
+ private func getSaveSettingsOperation() -> TransformOperation<SetAccountResult, StoredAccountData> {
+ return TransformOperation<SetAccountResult, StoredAccountData>(dispatchQueue: dispatchQueue) { input in
self.logger.debug("Saving settings...")
let device = input.device
@@ -421,7 +379,5 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
return input.accountData
}
-
- return saveSettingsOperation
}
}
diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
index 9c9190200b..b2644dcec0 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
@@ -268,24 +268,21 @@ final class TunnelManager: StorePaymentObserver {
}
func reconnectTunnel(selectNewRelay: Bool, completionHandler: ((Error?) -> Void)? = nil) {
- let operation = AsyncBlockOperation(dispatchQueue: internalQueue) { operation in
- guard let tunnel = self.tunnel else {
- operation.finish(error: UnsetTunnelError())
- return
- }
-
+ let operation = AsyncBlockOperation(dispatchQueue: internalQueue) { finish -> Cancellable in
do {
- let selectorResult = selectNewRelay ? try self.selectRelay() : nil
-
- let task = tunnel.reconnectTunnel(relaySelectorResult: selectorResult) { result in
- operation.finish(error: result.error)
+ guard let tunnel = self.tunnel else {
+ throw UnsetTunnelError()
}
- operation.addCancellationBlock {
- task.cancel()
+ let selectorResult = selectNewRelay ? try self.selectRelay() : nil
+
+ return tunnel.reconnectTunnel(relaySelectorResult: selectorResult) { result in
+ finish(result.error)
}
} catch {
- operation.finish(error: error)
+ finish(error)
+
+ return AnyCancellable()
}
}
diff --git a/ios/MullvadVPN/View controllers/Tunnel/MapViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/MapViewController.swift
index 243672597a..bff8888133 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/MapViewController.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/MapViewController.swift
@@ -234,9 +234,9 @@ final class MapViewController: UIViewController, MKMapViewDelegate {
cancelOtherAnimations: Bool,
block: @escaping (_ finish: @escaping () -> Void) -> Void
) {
- let operation = AsyncBlockOperation(dispatchQueue: .main) { operation in
+ let operation = AsyncBlockOperation(dispatchQueue: .main) { finish in
block {
- operation.finish()
+ finish(nil)
}
}
diff --git a/ios/Operations/AsyncBlockOperation.swift b/ios/Operations/AsyncBlockOperation.swift
index 43be9b8275..062fc528f5 100644
--- a/ios/Operations/AsyncBlockOperation.swift
+++ b/ios/Operations/AsyncBlockOperation.swift
@@ -7,78 +7,53 @@
//
import Foundation
+import protocol MullvadTypes.Cancellable
/// Asynchronous block operation
public class AsyncBlockOperation: AsyncOperation {
- private var executionBlock: ((AsyncBlockOperation) -> Void)?
- private var cancellationBlocks: [() -> Void] = []
+ private var executor: ((@escaping (Error?) -> Void) -> Cancellable?)?
+ private var cancellableTask: Cancellable?
- override public init(dispatchQueue: DispatchQueue? = nil) {
- super.init(dispatchQueue: dispatchQueue)
- }
-
- public init(
- dispatchQueue: DispatchQueue? = nil,
- block: @escaping (AsyncBlockOperation) -> Void
- ) {
- executionBlock = block
+ public init(dispatchQueue: DispatchQueue? = nil, block: @escaping (@escaping (Error?) -> Void) -> Void) {
super.init(dispatchQueue: dispatchQueue)
+ executor = { finish in
+ block(finish)
+ return nil
+ }
}
public init(dispatchQueue: DispatchQueue? = nil, block: @escaping () -> Void) {
- executionBlock = { operation in
+ super.init(dispatchQueue: dispatchQueue)
+ executor = { finish in
block()
- operation.finish()
+ finish(nil)
+ return nil
}
+ }
+
+ public init(
+ dispatchQueue: DispatchQueue? = nil,
+ cancellableTask: @escaping (@escaping (Error?) -> Void) -> Cancellable
+ ) {
super.init(dispatchQueue: dispatchQueue)
+ executor = { cancellableTask($0) }
}
override public func main() {
- let block = executionBlock
- executionBlock = nil
+ let executor = executor
+ self.executor = nil
- if let block = block {
- block(self)
- } else {
- finish()
- }
+ assert(executor != nil)
+
+ cancellableTask = executor?(self.finish)
}
override public func operationDidCancel() {
- let blocks = cancellationBlocks
- cancellationBlocks.removeAll()
-
- for block in blocks {
- block()
- }
+ cancellableTask?.cancel()
}
override public func operationDidFinish() {
- cancellationBlocks.removeAll()
- executionBlock = nil
- }
-
- public func setExecutionBlock(_ block: @escaping (AsyncBlockOperation) -> Void) {
- dispatchQueue.async {
- assert(!self.isExecuting && !self.isFinished)
- self.executionBlock = block
- }
- }
-
- public func setExecutionBlock(_ block: @escaping () -> Void) {
- setExecutionBlock { operation in
- block()
- operation.finish()
- }
- }
-
- public func addCancellationBlock(_ block: @escaping () -> Void) {
- dispatchQueue.async {
- if self.isCancelled, self.isExecuting {
- block()
- } else {
- self.cancellationBlocks.append(block)
- }
- }
+ executor = nil
+ cancellableTask = nil
}
}
diff --git a/ios/Operations/AsyncOperation.swift b/ios/Operations/AsyncOperation.swift
index 2ca049030c..ca11e86d7e 100644
--- a/ios/Operations/AsyncOperation.swift
+++ b/ios/Operations/AsyncOperation.swift
@@ -436,6 +436,11 @@ extension OperationBlockObserverSupport where Self: AsyncOperation {
addBlockObserver(OperationBlockObserver(didFinish: fn))
}
+ /// Add observer responding to start event.
+ public func onStart(_ fn: @escaping (Self) -> Void) {
+ addBlockObserver(OperationBlockObserver(didStart: fn))
+ }
+
/// Add block-based observer.
public func addBlockObserver(_ observer: OperationBlockObserver<Self>) {
addObserver(observer)
diff --git a/ios/Operations/ResultBlockOperation.swift b/ios/Operations/ResultBlockOperation.swift
index 5d848b438d..867dee45cf 100644
--- a/ios/Operations/ResultBlockOperation.swift
+++ b/ios/Operations/ResultBlockOperation.swift
@@ -7,102 +7,53 @@
//
import Foundation
+import protocol MullvadTypes.Cancellable
public final class ResultBlockOperation<Success>: ResultOperation<Success> {
- public typealias ExecutionBlock = (ResultBlockOperation<Success>) -> Void
- public typealias ThrowingExecutionBlock = () throws -> Success
+ private var executor: ((@escaping (Result<Success, Error>) -> Void) -> Cancellable?)?
+ private var cancellableTask: Cancellable?
- private var executionBlock: ExecutionBlock?
- private var cancellationBlocks: [() -> Void] = []
-
- public convenience init(
+ public init(
dispatchQueue: DispatchQueue? = nil,
- executionBlock: ExecutionBlock? = nil
+ executionBlock: @escaping (_ finish: @escaping (Result<Success, Error>) -> Void) -> Void
) {
- self.init(
- dispatchQueue: dispatchQueue,
- executionBlock: executionBlock,
- completionQueue: nil,
- completionHandler: nil
- )
+ super.init(dispatchQueue: dispatchQueue)
+ executor = { finish in
+ executionBlock(finish)
+ return nil
+ }
}
- public convenience init(
- dispatchQueue: DispatchQueue? = nil,
- executionBlock: @escaping ThrowingExecutionBlock
- ) {
- self.init(
- dispatchQueue: dispatchQueue,
- executionBlock: Self.wrapThrowingBlock(executionBlock),
- completionQueue: nil,
- completionHandler: nil
- )
+ public init(dispatchQueue: DispatchQueue? = nil, executionBlock: @escaping () throws -> Success) {
+ super.init(dispatchQueue: dispatchQueue)
+ executor = { finish in
+ finish(Result { try executionBlock() })
+ return nil
+ }
}
public init(
- dispatchQueue: DispatchQueue?,
- executionBlock: ExecutionBlock?,
- completionQueue: DispatchQueue?,
- completionHandler: CompletionHandler?
+ dispatchQueue: DispatchQueue? = nil,
+ cancellableTask: @escaping (_ finish: @escaping (Result<Success, Error>) -> Void) -> Cancellable
) {
- self.executionBlock = executionBlock
-
- super.init(
- dispatchQueue: dispatchQueue,
- completionQueue: completionQueue,
- completionHandler: completionHandler
- )
+ super.init(dispatchQueue: dispatchQueue)
+ executor = { cancellableTask($0) }
}
override public func main() {
- let block = executionBlock
- executionBlock = nil
+ let executor = executor
+ self.executor = nil
- block?(self)
+ assert(executor != nil)
+ cancellableTask = executor?(self.finish)
}
override public func operationDidCancel() {
- let blocks = cancellationBlocks
- cancellationBlocks.removeAll()
-
- for block in blocks {
- block()
- }
+ cancellableTask?.cancel()
}
override public func operationDidFinish() {
- cancellationBlocks.removeAll()
- executionBlock = nil
- }
-
- public func setExecutionBlock(_ block: @escaping ExecutionBlock) {
- dispatchQueue.async {
- assert(!self.isExecuting && !self.isFinished)
- self.executionBlock = block
- }
- }
-
- public func setExecutionBlock(_ block: @escaping ThrowingExecutionBlock) {
- setExecutionBlock(Self.wrapThrowingBlock(block))
- }
-
- public func addCancellationBlock(_ block: @escaping () -> Void) {
- dispatchQueue.async {
- if self.isCancelled, self.isExecuting {
- block()
- } else {
- self.cancellationBlocks.append(block)
- }
- }
- }
-
- private class func wrapThrowingBlock(_ executionBlock: @escaping ThrowingExecutionBlock)
- -> ExecutionBlock
- {
- return { operation in
- let result = Result { try executionBlock() }
-
- operation.finish(result: result)
- }
+ executor = nil
+ cancellableTask = nil
}
}
diff --git a/ios/Operations/TransformOperation.swift b/ios/Operations/TransformOperation.swift
index 609e07896c..b5cc36adad 100644
--- a/ios/Operations/TransformOperation.swift
+++ b/ios/Operations/TransformOperation.swift
@@ -7,10 +7,9 @@
//
import Foundation
+import protocol MullvadTypes.Cancellable
public final class TransformOperation<Input, Output>: ResultOperation<Output>, InputOperation {
- public typealias ExecutionBlock = (Input, TransformOperation<Input, Output>) -> Void
- public typealias ThrowingExecutionBlock = (Input) throws -> Output
public typealias InputBlock = () -> Input?
private let nslock = NSLock()
@@ -35,79 +34,70 @@ public final class TransformOperation<Input, Output>: ResultOperation<Output>, I
private var inputBlock: InputBlock?
- private var executionBlock: ExecutionBlock?
- private var cancellationBlocks: [() -> Void] = []
+ private var executor: ((Input, @escaping (Result<Output, Error>) -> Void) -> Cancellable?)?
+ private var cancellableTask: Cancellable?
public init(
dispatchQueue: DispatchQueue? = nil,
input: Input? = nil,
- block: ExecutionBlock? = nil
+ block: @escaping (_ input: Input, _ finish: @escaping (Result<Output, Error>) -> Void) -> Void
) {
- __input = input
- executionBlock = block
-
super.init(dispatchQueue: dispatchQueue)
+ __input = input
+ executor = { input, finish in
+ block(input, finish)
+ return nil
+ }
}
public init(
dispatchQueue: DispatchQueue? = nil,
input: Input? = nil,
- throwingBlock: @escaping ThrowingExecutionBlock
+ throwingBlock: @escaping (_ input: Input) throws -> Output
) {
+ super.init(dispatchQueue: dispatchQueue)
__input = input
- executionBlock = Self.wrapThrowingBlock(throwingBlock)
+ executor = { input, finish in
+ finish(Result { try throwingBlock(input) })
+ return nil
+ }
+ }
+ public init(
+ dispatchQueue: DispatchQueue? = nil,
+ input: Input? = nil,
+ cancellableTask: @escaping (_ input: Input, _ finish: @escaping (Result<Output, Error>) -> Void) -> Cancellable
+ ) {
super.init(dispatchQueue: dispatchQueue)
+ __input = input
+ executor = cancellableTask
}
override public func main() {
- let inputValue = inputBlock?()
-
- _input = inputValue
+ if let inputBlock = inputBlock {
+ _input = inputBlock()
+ }
- guard let inputValue = inputValue, let executionBlock = executionBlock else {
+ guard let inputValue = _input else {
finish(result: .failure(OperationError.unsatisfiedRequirement))
return
}
- executionBlock(inputValue, self)
- }
+ let executor = executor
+ self.executor = nil
- override public func operationDidCancel() {
- let blocks = cancellationBlocks
- cancellationBlocks.removeAll()
+ assert(executor != nil)
- for block in blocks {
- block()
- }
+ cancellableTask = executor?(inputValue, self.finish)
}
- override public func operationDidFinish() {
- cancellationBlocks.removeAll()
- executionBlock = nil
- }
-
- // MARK: - Block handlers
-
- public func setExecutionBlock(_ block: @escaping ExecutionBlock) {
- dispatchQueue.async {
- assert(!self.isExecuting && !self.isFinished)
- self.executionBlock = block
- }
- }
-
- public func setExecutionBlock(_ block: @escaping ThrowingExecutionBlock) {
- setExecutionBlock(Self.wrapThrowingBlock(block))
+ override public func operationDidCancel() {
+ cancellableTask?.cancel()
}
- public func addCancellationBlock(_ block: @escaping () -> Void) {
- dispatchQueue.async {
- if self.isCancelled, self.isExecuting {
- block()
- } else {
- self.cancellationBlocks.append(block)
- }
- }
+ override public func operationDidFinish() {
+ executor = nil
+ cancellableTask = nil
}
// MARK: - Input injection
@@ -117,14 +107,4 @@ public final class TransformOperation<Input, Output>: ResultOperation<Output>, I
self.inputBlock = block
}
}
-
- private class func wrapThrowingBlock(_ executionBlock: @escaping ThrowingExecutionBlock)
- -> ExecutionBlock
- {
- return { input, operation in
- let result = Result { try executionBlock(input) }
-
- operation.finish(result: result)
- }
- }
}
diff --git a/ios/OperationsTests/AsyncBlockOperationTests.swift b/ios/OperationsTests/AsyncBlockOperationTests.swift
new file mode 100644
index 0000000000..07a65d0ccd
--- /dev/null
+++ b/ios/OperationsTests/AsyncBlockOperationTests.swift
@@ -0,0 +1,99 @@
+//
+// AsyncBlockOperationTests.swift
+// OperationsTests
+//
+// Created by pronebird on 26/04/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadTypes
+import Operations
+import XCTest
+
+final class AsyncBlockOperationTests: XCTestCase {
+ let operationQueue = AsyncOperationQueue()
+
+ func testBlockOperation() {
+ let executionExpectation = expectation(description: "Should execute")
+ let finishExpectation = expectation(description: "Should finish")
+
+ let operation = AsyncBlockOperation(block: { finish in
+ executionExpectation.fulfill()
+ finish(nil)
+ })
+
+ operation.completionBlock = {
+ finishExpectation.fulfill()
+ }
+
+ operationQueue.addOperation(operation)
+
+ waitForExpectations(timeout: 1)
+ }
+
+ func testSynchronousBlockOperation() {
+ let executionExpectation = expectation(description: "Should execute")
+ let finishExpectation = expectation(description: "Should finish")
+
+ let operation = AsyncBlockOperation {
+ executionExpectation.fulfill()
+ }
+
+ operation.completionBlock = {
+ finishExpectation.fulfill()
+ }
+
+ operationQueue.addOperation(operation)
+
+ waitForExpectations(timeout: 1)
+ }
+
+ func testCancellableTaskBlockOperation() {
+ let executionExpectation = expectation(description: "Should execute")
+ let cancelExpectation = expectation(description: "Should cancel")
+ let finishExpectation = expectation(description: "Should finish")
+
+ let operation = AsyncBlockOperation { finish -> Cancellable in
+ executionExpectation.fulfill()
+
+ return AnyCancellable {
+ cancelExpectation.fulfill()
+ finish(nil)
+ }
+ }
+
+ operation.completionBlock = {
+ finishExpectation.fulfill()
+ }
+
+ operation.onStart { op in
+ op.cancel()
+ }
+
+ operationQueue.addOperation(operation)
+
+ waitForExpectations(timeout: 1)
+ }
+
+ func testCancellationShouldNotFireBeforeOperationIsEnqueued() throws {
+ let expect = expectation(description: "Cancellation should not fire.")
+ expect.isInverted = true
+
+ let operation = AsyncBlockOperation {}
+ operation.onCancel { _ in expect.fulfill() }
+ operation.cancel()
+
+ waitForExpectations(timeout: 1)
+ }
+
+ func testCancellationShouldFireAfterCancelledOperationIsEnqueued() throws {
+ let expect = expectation(description: "Cancellation should fire.")
+
+ let operation = AsyncBlockOperation {}
+ operation.onCancel { _ in expect.fulfill() }
+ operation.cancel()
+ operationQueue.addOperation(operation)
+
+ waitForExpectations(timeout: 1)
+ }
+}
diff --git a/ios/OperationsTests/AsyncResultBlockOperationTests.swift b/ios/OperationsTests/AsyncResultBlockOperationTests.swift
new file mode 100644
index 0000000000..5bae7f7d9c
--- /dev/null
+++ b/ios/OperationsTests/AsyncResultBlockOperationTests.swift
@@ -0,0 +1,75 @@
+//
+// AsyncResultBlockOperationTests.swift
+// OperationsTests
+//
+// Created by pronebird on 26/04/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadTypes
+import Operations
+import XCTest
+
+final class AsyncResultBlockOperationTests: XCTestCase {
+ let operationQueue = AsyncOperationQueue()
+
+ func testBlockOperation() {
+ let expectation = expectation(description: "Should finish")
+
+ let operation = ResultBlockOperation<Bool> { finish in
+ finish(.success(true))
+ }
+
+ operation.onFinish { op, error in
+ XCTAssertEqual(op.result?.value, true)
+ expectation.fulfill()
+ }
+
+ operationQueue.addOperation(operation)
+
+ waitForExpectations(timeout: 1)
+ }
+
+ func testThrowingBlockOperation() {
+ let expectation = expectation(description: "Should finish")
+
+ let operation = ResultBlockOperation {
+ throw URLError(.badURL)
+ }
+
+ operation.onFinish { op, error in
+ XCTAssertEqual(op.result?.error as? URLError, URLError(.badURL))
+ XCTAssertEqual(error as? URLError, URLError(.badURL))
+
+ expectation.fulfill()
+ }
+
+ operationQueue.addOperation(operation)
+
+ waitForExpectations(timeout: 1)
+ }
+
+ func testCancellableTaskOperation() {
+ let expectation = expectation(description: "Should finish")
+
+ let operation = ResultBlockOperation<Bool> { finish -> Cancellable in
+ return AnyCancellable {
+ finish(.failure(URLError(.cancelled)))
+ }
+ }
+
+ operation.onStart { op in
+ op.cancel()
+ }
+
+ operation.onFinish { op, error in
+ XCTAssertEqual(op.result?.error as? URLError, URLError(.cancelled))
+ XCTAssertEqual(error as? URLError, URLError(.cancelled))
+ expectation.fulfill()
+ }
+
+ operationQueue.addOperation(operation)
+
+ waitForExpectations(timeout: 1)
+ }
+}
diff --git a/ios/OperationsTests/OperationCancellationTests.swift b/ios/OperationsTests/OperationCancellationTests.swift
deleted file mode 100644
index 7313bc83b2..0000000000
--- a/ios/OperationsTests/OperationCancellationTests.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// OperationCancellationTests.swift
-// OperationsTests
-//
-// Created by pronebird on 12/04/2023.
-// Copyright © 2023 Mullvad VPN AB. All rights reserved.
-//
-
-import Operations
-import XCTest
-
-final class OperationCancellationTests: XCTestCase {
- func testCancellationShouldNotFireBeforeOperationIsEnqueued() throws {
- let expect = expectation(description: "Cancellation should not fire.")
- expect.isInverted = true
-
- let operation = AsyncBlockOperation {}
- operation.onCancel { _ in expect.fulfill() }
- operation.cancel()
-
- waitForExpectations(timeout: 1)
- }
-
- func testCancellationShouldFireAfterCancelledOperationIsEnqueued() throws {
- let expect = expectation(description: "Cancellation should fire.")
-
- let operationQueue = AsyncOperationQueue()
- let operation = AsyncBlockOperation {}
- operation.onCancel { _ in expect.fulfill() }
- operation.cancel()
- operationQueue.addOperation(operation)
-
- waitForExpectations(timeout: 1)
- }
-}
diff --git a/ios/OperationsTests/OperationConditionTests.swift b/ios/OperationsTests/OperationConditionTests.swift
index b35e687319..27898ea424 100644
--- a/ios/OperationsTests/OperationConditionTests.swift
+++ b/ios/OperationsTests/OperationConditionTests.swift
@@ -123,10 +123,10 @@ class OperationConditionTests: XCTestCase {
let exclusiveCategory = "exclusiveOperations"
let operationQueue = AsyncOperationQueue()
- let firstOperation = AsyncBlockOperation { op in
+ let firstOperation = AsyncBlockOperation { finish in
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
expectFirstOperationExecution.fulfill()
- op.finish()
+ finish(nil)
}
}
firstOperation.addCondition(MutuallyExclusive(category: exclusiveCategory))
diff --git a/ios/OperationsTests/OperationObserverTests.swift b/ios/OperationsTests/OperationObserverTests.swift
index c246cdc526..de334be21a 100644
--- a/ios/OperationsTests/OperationObserverTests.swift
+++ b/ios/OperationsTests/OperationObserverTests.swift
@@ -17,7 +17,7 @@ class OperationObserverTests: XCTestCase {
expectDidCancel.isInverted = true
let expectDidFinish = expectation(description: "didAttach handler")
- let operation = AsyncBlockOperation()
+ let operation = AsyncBlockOperation {}
operation.addBlockObserver(OperationBlockObserver(
didAttach: { op in
expectDidAttach.fulfill()
@@ -44,7 +44,7 @@ class OperationObserverTests: XCTestCase {
let expectDidCancel = expectation(description: "didCancel handler")
let expectDidFinish = expectation(description: "didAttach handler")
- let operation = AsyncBlockOperation()
+ let operation = AsyncBlockOperation {}
operation.addBlockObserver(OperationBlockObserver(
didAttach: { op in
expectDidAttach.fulfill()
diff --git a/ios/OperationsTests/TransformOperationTests.swift b/ios/OperationsTests/TransformOperationTests.swift
new file mode 100644
index 0000000000..c16bcd3be1
--- /dev/null
+++ b/ios/OperationsTests/TransformOperationTests.swift
@@ -0,0 +1,93 @@
+//
+// TransformOperationTests.swift
+// OperationsTests
+//
+// Created by pronebird on 26/04/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadTypes
+import Operations
+import XCTest
+
+final class TransformOperationTests: XCTestCase {
+ let operationQueue = AsyncOperationQueue()
+
+ func testBlockTransformOperation() {
+ let finishExpectation = expectation(description: "Should finish")
+
+ let transform = TransformOperation(input: Int.zero) { input, finish in
+ finish(.success(input + 1))
+ }
+
+ transform.onFinish { op, error in
+ XCTAssertEqual(op.result?.value, 1)
+
+ finishExpectation.fulfill()
+ }
+
+ operationQueue.addOperation(transform)
+
+ waitForExpectations(timeout: 1)
+ }
+
+ func testThrowingBlockTransformOperation() {
+ let finishExpectation = expectation(description: "Should finish")
+
+ let transform = TransformOperation(input: Int.zero) { value in
+ throw URLError(.badURL)
+ }
+
+ transform.onFinish { op, error in
+ XCTAssertEqual(error as? URLError, URLError(.badURL))
+
+ finishExpectation.fulfill()
+ }
+
+ operationQueue.addOperation(transform)
+
+ waitForExpectations(timeout: 1)
+ }
+
+ func testCancellableTaskBlockTransformOperation() {
+ let finishExpectation = expectation(description: "Should finish")
+
+ let transform = TransformOperation<Int, Int>(input: Int.zero) { _, finish -> Cancellable in
+ return AnyCancellable {
+ finish(.failure(URLError(.cancelled)))
+ }
+ }
+
+ transform.onStart { op in
+ op.cancel()
+ }
+
+ transform.onFinish { op, error in
+ XCTAssertEqual(error as? URLError, URLError(.cancelled))
+
+ finishExpectation.fulfill()
+ }
+
+ operationQueue.addOperation(transform)
+
+ waitForExpectations(timeout: 1)
+ }
+
+ func testShouldFailWithUnsatisfiedRequirement() {
+ let finishExpectation = expectation(description: "Should finish")
+
+ let transform = TransformOperation<Int, Int> { input, finish in
+ finish(.success(input))
+ }
+
+ transform.onFinish { _, error in
+ XCTAssertEqual(error as? OperationError, .unsatisfiedRequirement)
+
+ finishExpectation.fulfill()
+ }
+
+ operationQueue.addOperation(transform)
+
+ waitForExpectations(timeout: 1)
+ }
+}
diff --git a/ios/PacketTunnel/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider.swift
index 380293695b..c70d5e0d35 100644
--- a/ios/PacketTunnel/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider.swift
@@ -585,16 +585,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
// Ignore all requests to reconnect once tunnel is preparing to stop.
guard !isStopping else { return }
- let blockOperation = AsyncBlockOperation(dispatchQueue: dispatchQueue) { operation in
+ let blockOperation = AsyncBlockOperation(dispatchQueue: dispatchQueue, block: { finish in
if shouldStopTunnelMonitor {
self.tunnelMonitor.stop()
}
self.reconnectTunnelInner(to: nextRelay) { error in
completionHandler?(error)
- operation.finish()
+ finish(nil)
}
- }
+ })
if let reconnectTunnelTask = reconnectTunnelTask {
blockOperation.addDependency(reconnectTunnelTask)
@@ -779,49 +779,28 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
operationQueue.addOperation(groupOperation)
}
- private func createGetAccountDataOperation(accountNumber: String)
- -> ResultOperation<REST.AccountData>
- {
- let operation = ResultBlockOperation<REST.AccountData>(
- dispatchQueue: dispatchQueue
- )
-
- operation.setExecutionBlock { operation in
- let task = self.accountsProxy.getAccountData(
+ private func createGetAccountDataOperation(accountNumber: String) -> ResultOperation<REST.AccountData> {
+ return ResultBlockOperation<REST.AccountData>(dispatchQueue: dispatchQueue) { finish -> Cancellable in
+ return self.accountsProxy.getAccountData(
accountNumber: accountNumber,
- retryStrategy: .noRetry
- ) { result in
- operation.finish(result: result)
- }
-
- operation.addCancellationBlock {
- task.cancel()
- }
+ retryStrategy: .noRetry,
+ completion: finish
+ )
}
-
- return operation
}
- private func createGetDeviceDataOperation(accountNumber: String, identifier: String)
- -> ResultOperation<REST.Device>
- {
- let operation = ResultBlockOperation<REST.Device>(dispatchQueue: dispatchQueue)
-
- operation.setExecutionBlock { operation in
- let task = self.devicesProxy.getDevice(
+ private func createGetDeviceDataOperation(
+ accountNumber: String,
+ identifier: String
+ ) -> ResultOperation<REST.Device> {
+ return ResultBlockOperation<REST.Device>(dispatchQueue: dispatchQueue) { finish -> Cancellable in
+ return self.devicesProxy.getDevice(
accountNumber: accountNumber,
identifier: identifier,
- retryStrategy: .noRetry
- ) { result in
- operation.finish(result: result)
- }
-
- operation.addCancellationBlock {
- task.cancel()
- }
+ retryStrategy: .noRetry,
+ completion: finish
+ )
}
-
- return operation
}
}