summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2024-12-04 14:43:23 +0100
committerBug Magnet <marco.nikic@mullvad.net>2025-01-14 10:18:06 +0100
commitd1cf679456f87b2f93b150c67a76fa20e31d7643 (patch)
tree6ae6911848db62013e09939488a54fd98bad81b4
parentd2949b4a0b1d3d86a25de1569dc8308c9d7fe237 (diff)
downloadmullvadvpn-d1cf679456f87b2f93b150c67a76fa20e31d7643.tar.xz
mullvadvpn-d1cf679456f87b2f93b150c67a76fa20e31d7643.zip
Enable compilation with Swift 6 for most targets
-rw-r--r--ios/MullvadLogging/LogFileOutputStream.swift2
-rw-r--r--ios/MullvadLogging/OSLogHandler.swift2
-rw-r--r--ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift2
-rw-r--r--ios/MullvadMockData/MullvadREST/RESTRequestExecutor+Stubs.swift2
-rw-r--r--ios/MullvadREST/ApiHandlers/APIAvailabilityTestRequest.swift4
-rw-r--r--ios/MullvadREST/ApiHandlers/AddressCache.swift2
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift32
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTAccessTokenManager.swift8
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTAccountsProxy.swift8
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTAuthenticationProxy.swift6
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTAuthorization.swift4
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTDefaults.swift2
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTDevicesProxy.swift24
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTError.swift6
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTNetworkOperation.swift2
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTProxy.swift16
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTRequestExecutor.swift6
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTRequestHandler.swift4
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTTaskIdentifier.swift2
-rw-r--r--ios/MullvadREST/ApiHandlers/SSLPinningURLSessionDelegate.swift4
-rw-r--r--ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift14
-rw-r--r--ios/MullvadREST/Relay/IPOverrideWrapper.swift2
-rw-r--r--ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift4
-rw-r--r--ios/MullvadREST/Relay/RelayCache.swift6
-rw-r--r--ios/MullvadREST/Relay/RelaySelectorProtocol.swift4
-rw-r--r--ios/MullvadREST/Relay/RelaySelectorWrapper.swift2
-rw-r--r--ios/MullvadREST/RetryStrategy/Jittered.swift2
-rw-r--r--ios/MullvadREST/RetryStrategy/RetryStrategy.swift16
-rw-r--r--ios/MullvadREST/Transport/AccessMethodIterator.swift2
-rw-r--r--ios/MullvadREST/Transport/Direct/URLSessionTransport.swift2
-rw-r--r--ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift8
-rw-r--r--ios/MullvadREST/Transport/RESTTransport.swift5
-rw-r--r--ios/MullvadREST/Transport/RESTTransportProvider.swift2
-rw-r--r--ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfiguration.swift2
-rw-r--r--ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfigurationCache.swift4
-rw-r--r--ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift8
-rw-r--r--ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift2
-rw-r--r--ios/MullvadREST/Transport/Shadowsocks/ShadowsocksTransport.swift2
-rw-r--r--ios/MullvadREST/Transport/Socks5/NWConnection+Extensions.swift2
-rw-r--r--ios/MullvadREST/Transport/Socks5/Socks5AddressType.swift2
-rw-r--r--ios/MullvadREST/Transport/Socks5/Socks5Authentication.swift6
-rw-r--r--ios/MullvadREST/Transport/Socks5/Socks5Configuration.swift2
-rw-r--r--ios/MullvadREST/Transport/Socks5/Socks5ConnectNegotiation.swift4
-rw-r--r--ios/MullvadREST/Transport/Socks5/Socks5Connection.swift8
-rw-r--r--ios/MullvadREST/Transport/Socks5/Socks5DataStreamHandler.swift4
-rw-r--r--ios/MullvadREST/Transport/Socks5/Socks5Endpoint.swift4
-rw-r--r--ios/MullvadREST/Transport/Socks5/Socks5EndpointReader.swift8
-rw-r--r--ios/MullvadREST/Transport/Socks5/Socks5Error.swift2
-rw-r--r--ios/MullvadREST/Transport/Socks5/Socks5ForwardingProxy.swift16
-rw-r--r--ios/MullvadREST/Transport/Socks5/Socks5HandshakeNegotiation.swift6
-rw-r--r--ios/MullvadREST/Transport/Socks5/Socks5StatusCode.swift2
-rw-r--r--ios/MullvadREST/Transport/Socks5/URLSessionSocks5Transport.swift12
-rw-r--r--ios/MullvadREST/Transport/TransportProvider.swift12
-rw-r--r--ios/MullvadREST/Transport/TransportStrategy.swift2
-rw-r--r--ios/MullvadRESTTests/Mocks/AnyTransport.swift8
-rw-r--r--ios/MullvadRESTTests/Mocks/RESTTransportStub.swift2
-rw-r--r--ios/MullvadRESTTests/Mocks/TimeServerProxy.swift2
-rw-r--r--ios/MullvadRESTTests/RequestExecutorTests.swift3
-rw-r--r--ios/MullvadRustRuntime/ShadowSocksProxy.swift2
-rw-r--r--ios/MullvadRustRuntimeTests/UnsafeListener.swift2
-rw-r--r--ios/MullvadSettings/AccessMethodRepository.swift2
-rw-r--r--ios/MullvadSettings/AccessMethodRepositoryProtocol.swift2
-rw-r--r--ios/MullvadSettings/DAITASettings.swift6
-rw-r--r--ios/MullvadSettings/DNSSettings.swift4
-rw-r--r--ios/MullvadSettings/DeviceState.swift2
-rw-r--r--ios/MullvadSettings/IPOverride.swift4
-rw-r--r--ios/MullvadSettings/IPOverrideRepository.swift8
-rw-r--r--ios/MullvadSettings/KeychainSettingsStore.swift2
-rw-r--r--ios/MullvadSettings/Migration.swift2
-rw-r--r--ios/MullvadSettings/MigrationManager.swift6
-rw-r--r--ios/MullvadSettings/MultihopSettings.swift4
-rw-r--r--ios/MullvadSettings/QuantumResistanceSettings.swift2
-rw-r--r--ios/MullvadSettings/SettingsManager.swift6
-rw-r--r--ios/MullvadSettings/SettingsStore.swift4
-rw-r--r--ios/MullvadSettings/ShadowsocksCipherOptions.swift4
-rw-r--r--ios/MullvadSettings/StoredAccountData.swift2
-rw-r--r--ios/MullvadSettings/StoredDeviceData.swift4
-rw-r--r--ios/MullvadSettings/StoredWgKeyData.swift4
-rw-r--r--ios/MullvadSettings/TunnelSettings.swift4
-rw-r--r--ios/MullvadSettings/TunnelSettingsPropagator.swift8
-rw-r--r--ios/MullvadSettings/TunnelSettingsStrategy.swift4
-rw-r--r--ios/MullvadSettings/TunnelSettingsUpdate.swift2
-rw-r--r--ios/MullvadSettings/TunnelSettingsV1.swift2
-rw-r--r--ios/MullvadSettings/TunnelSettingsV6.swift2
-rw-r--r--ios/MullvadSettings/WireGuardObfuscationSettings.swift10
-rw-r--r--ios/MullvadTypes/AnyIPEndpoint.swift2
-rw-r--r--ios/MullvadTypes/Cancellable.swift2
-rw-r--r--ios/MullvadTypes/IPv4Endpoint.swift2
-rw-r--r--ios/MullvadTypes/IPv6Endpoint.swift2
-rw-r--r--ios/MullvadTypes/Location.swift2
-rw-r--r--ios/MullvadTypes/MullvadEndpoint.swift2
-rw-r--r--ios/MullvadTypes/ObserverList.swift8
-rw-r--r--ios/MullvadTypes/Promise.swift2
-rw-r--r--ios/MullvadTypes/Protocols/DaitaV2Parameters.swift2
-rw-r--r--ios/MullvadTypes/Protocols/NetworkExtension+HiddenSymbols.swift17
-rw-r--r--ios/MullvadTypes/RESTTypes.swift6
-rw-r--r--ios/MullvadTypes/RelayConstraints.swift2
-rw-r--r--ios/MullvadTypes/RelayLocation.swift6
-rw-r--r--ios/MullvadTypes/TransportLayer.swift2
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj57
-rw-r--r--ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift2
-rw-r--r--ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift2
-rw-r--r--ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift4
-rw-r--r--ios/MullvadVPN/AppDelegate.swift129
-rw-r--r--ios/MullvadVPN/Classes/AccessbilityIdentifier.swift1
-rw-r--r--ios/MullvadVPN/Classes/AutomaticKeyboardResponder.swift1
-rw-r--r--ios/MullvadVPN/Classes/ConsolidatedApplicationLog.swift6
-rw-r--r--ios/MullvadVPN/Containers/Root/RootContainerViewController.swift9
-rw-r--r--ios/MullvadVPN/Coordinators/AccountCoordinator.swift15
-rw-r--r--ios/MullvadVPN/Coordinators/AccountDeletionCoordinator.swift6
-rw-r--r--ios/MullvadVPN/Coordinators/AddCreditSucceededCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift43
-rw-r--r--ios/MullvadVPN/Coordinators/CreateAccountVoucherCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/AddCustomListCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/AddLocationsCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift4
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/AddLocationsViewController.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/CustomListCellConfiguration.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/CustomListDataSourceConfiguration.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/EditCustomListCoordinator.swift6
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/EditLocationsCoordinator.swift7
-rw-r--r--ios/MullvadVPN/Coordinators/LocationCoordinator.swift4
-rw-r--r--ios/MullvadVPN/Coordinators/LoginCoordinator.swift19
-rw-r--r--ios/MullvadVPN/Coordinators/OutOfTimeCoordinator.swift4
-rw-r--r--ios/MullvadVPN/Coordinators/ProfileVoucherCoordinator.swift4
-rw-r--r--ios/MullvadVPN/Coordinators/RelayFilterCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/SafariCoordinator.swift5
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Add/AddAccessMethodCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/TextCellContentView.swift5
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/ShadowsocksSectionHandler.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/SocksSectionHandler.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/CurrentValueSubject+UIActionBindings.swift4
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodCoordinator.swift4
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodInteractor.swift4
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsCellConfiguration.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsDataSourceConfiguration.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift4
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/ProxyConfigurationInteractorProtocol.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/AccessMethodProtocolPicker.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/ShadowsocksCipherPicker.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift21
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/SetupAccountCompletedCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/VPNSettingsCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Extensions/UITextField+Appearance.swift1
-rw-r--r--ios/MullvadVPN/Extensions/UIView+AutoLayoutBuilder.swift7
-rw-r--r--ios/MullvadVPN/Extensions/View+Size.swift4
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift3
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift3
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift2
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/TunnelStatusNotificationProvider.swift2
-rw-r--r--ios/MullvadVPN/Notifications/NotificationManager.swift2
-rw-r--r--ios/MullvadVPN/Notifications/NotificationProvider.swift4
-rw-r--r--ios/MullvadVPN/Operations/ProductsRequestOperation.swift2
-rw-r--r--ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift10
-rw-r--r--ios/MullvadVPN/SceneDelegate.swift2
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelInfo.swift3
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProvider.swift8
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift20
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderManager.swift2
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift2
-rw-r--r--ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift3
-rw-r--r--ios/MullvadVPN/StorePaymentManager/StorePaymentBlockObserver.swift2
-rw-r--r--ios/MullvadVPN/StorePaymentManager/StorePaymentEvent.swift6
-rw-r--r--ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift12
-rw-r--r--ios/MullvadVPN/StorePaymentManager/StorePaymentManagerDelegate.swift2
-rw-r--r--ios/MullvadVPN/StorePaymentManager/StorePaymentObserver.swift2
-rw-r--r--ios/MullvadVPN/StorePaymentManager/StoreTransactionLog.swift2
-rw-r--r--ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/BackgroundTaskProvider.swift8
-rw-r--r--ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/SetAccountOperation.swift38
-rw-r--r--ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift9
-rw-r--r--ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift8
-rw-r--r--ios/MullvadVPN/TunnelManager/Tunnel.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelBlockObserver.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelInteractor.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift48
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelObserver.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelState.swift6
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelStatusBlockObserver.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelStore.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/WgKeyRotation.swift4
-rw-r--r--ios/MullvadVPN/View controllers/Account/AccountInteractor.swift14
-rw-r--r--ios/MullvadVPN/View controllers/Account/AccountViewController.swift37
-rw-r--r--ios/MullvadVPN/View controllers/Account/PaymentAlertPresenter.swift5
-rw-r--r--ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionInteractor.swift2
-rw-r--r--ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionViewController.swift5
-rw-r--r--ios/MullvadVPN/View controllers/Alert/AlertPresenter.swift1
-rw-r--r--ios/MullvadVPN/View controllers/CreationAccount/Completed/SetupAccountCompletedController.swift4
-rw-r--r--ios/MullvadVPN/View controllers/CreationAccount/InAppPurchaseInteractor.swift2
-rw-r--r--ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeContentView.swift6
-rw-r--r--ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeInteractor.swift2
-rw-r--r--ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeViewController.swift4
-rw-r--r--ios/MullvadVPN/View controllers/DeviceList/DeviceManagementContentView.swift10
-rw-r--r--ios/MullvadVPN/View controllers/DeviceList/DeviceManagementInteractor.swift9
-rw-r--r--ios/MullvadVPN/View controllers/DeviceList/DeviceManagementViewController.swift54
-rw-r--r--ios/MullvadVPN/View controllers/Login/LoginInteractor.swift8
-rw-r--r--ios/MullvadVPN/View controllers/Login/LoginViewController.swift4
-rw-r--r--ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeInteractor.swift22
-rw-r--r--ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift45
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift8
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportReviewViewController.swift22
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift4
-rw-r--r--ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift4
-rw-r--r--ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewController.swift19
-rw-r--r--ios/MullvadVPN/View controllers/RelayFilter/RelayFilterCellFactory.swift3
-rw-r--r--ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift8
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift2
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift54
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationNode.swift6
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationRelays.swift2
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationSection.swift2
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift14
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift2
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/RelaySelection.swift2
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift3
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift2
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift4
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipContainerView.swift4
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/MapViewController.swift3
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelViewControllerInteractor.swift2
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift3
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/CustomDNSDataSource.swift2
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift2
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift3
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift2
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInteractor.swift4
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift2
-rw-r--r--ios/MullvadVPN/Views/CustomButton.swift2
-rw-r--r--ios/MullvadVPN/Views/CustomTextView.swift6
-rw-r--r--ios/MullvadVPN/Views/SpinnerActivityIndicatorView.swift12
-rw-r--r--ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift6
-rw-r--r--ios/MullvadVPNTests/MullvadSettings/InMemorySettingsStore.swift2
-rw-r--r--ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift2
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/Log/ConsolidatedApplicationLogTests.swift16
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnel.swift2
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift2
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/StartTunnelOperationTests.swift2
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStore+Stubs.swift4
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/View controllers/SelectLocation/CustomListRepositoryTests.swift2
-rw-r--r--ios/Operations/AsyncBlockOperation.swift13
-rw-r--r--ios/Operations/AsyncOperation.swift4
-rw-r--r--ios/Operations/AsyncOperationQueue.swift4
-rw-r--r--ios/Operations/BackgroundObserver.swift6
-rw-r--r--ios/Operations/GroupOperation.swift2
-rw-r--r--ios/Operations/OutputOperation.swift2
-rw-r--r--ios/Operations/ResultBlockOperation.swift14
-rw-r--r--ios/Operations/ResultOperation.swift6
-rw-r--r--ios/OperationsTests/AsyncBlockOperationTests.swift20
-rw-r--r--ios/OperationsTests/AsyncResultBlockOperationTests.swift12
-rw-r--r--ios/OperationsTests/OperationConditionTests.swift1
-rw-r--r--ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift6
-rw-r--r--ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteService.swift6
-rw-r--r--ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteServiceProtocol.swift10
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift4
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift4
-rw-r--r--ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift6
-rw-r--r--ios/PacketTunnelCore/Actor/AnyTask.swift2
-rw-r--r--ios/PacketTunnelCore/Actor/AutoCancellingTask.swift2
-rw-r--r--ios/PacketTunnelCore/Actor/EphemeralPeerNegotiationState.swift8
-rw-r--r--ios/PacketTunnelCore/Actor/ObservedState.swift8
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift1
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor.swift6
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift2
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift4
-rw-r--r--ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift6
-rw-r--r--ios/PacketTunnelCore/Actor/Protocols/TunnelAdapterProtocol.swift4
-rw-r--r--ios/PacketTunnelCore/Actor/StartOptions.swift4
-rw-r--r--ios/PacketTunnelCore/Actor/State.swift14
-rw-r--r--ios/PacketTunnelCore/Actor/Task+Duration.swift2
-rw-r--r--ios/PacketTunnelCore/Actor/Timings.swift2
-rw-r--r--ios/PacketTunnelCore/TunnelMonitor/DefaultPathObserverProtocol.swift6
-rw-r--r--ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorProtocol.swift4
-rw-r--r--ios/PacketTunnelCore/URLRequestProxy/ProxyURLResponse.swift6
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift2
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift2
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/TunnelMonitorStub.swift2
-rw-r--r--ios/Routing/Coordinator.swift9
-rw-r--r--ios/Routing/ModalPresentationConfiguration.swift1
-rw-r--r--ios/Routing/PresentationControllerDismissalInterceptor.swift2
-rw-r--r--ios/Routing/Router/AppRouteProtocol.swift4
-rw-r--r--ios/Routing/Router/ApplicationRouter.swift55
-rw-r--r--ios/Routing/Router/ApplicationRouterDelegate.swift8
-rw-r--r--ios/Routing/Router/ApplicationRouterTypes.swift20
-rw-r--r--ios/RoutingTests/RouterBlockDelegate.swift20
-rw-r--r--ios/RoutingTests/RoutingTests.swift9
297 files changed, 1117 insertions, 865 deletions
diff --git a/ios/MullvadLogging/LogFileOutputStream.swift b/ios/MullvadLogging/LogFileOutputStream.swift
index 7f4eec3a0b..6d63d43b77 100644
--- a/ios/MullvadLogging/LogFileOutputStream.swift
+++ b/ios/MullvadLogging/LogFileOutputStream.swift
@@ -13,7 +13,7 @@ import MullvadTypes
/// the first place, or when writing to it.
private let reopenFileLogInterval: Duration = .seconds(5)
-class LogFileOutputStream: TextOutputStream {
+class LogFileOutputStream: TextOutputStream, @unchecked Sendable {
private let queue = DispatchQueue(label: "LogFileOutputStreamQueue", qos: .utility)
private let baseFileURL: URL
diff --git a/ios/MullvadLogging/OSLogHandler.swift b/ios/MullvadLogging/OSLogHandler.swift
index 308e351249..d9778e7f87 100644
--- a/ios/MullvadLogging/OSLogHandler.swift
+++ b/ios/MullvadLogging/OSLogHandler.swift
@@ -22,7 +22,7 @@ public struct OSLogHandler: LogHandler {
let category: String
}
- private static var osLogRegistry: [RegistryKey: OSLog] = [:]
+ nonisolated(unsafe) private static var osLogRegistry: [RegistryKey: OSLog] = [:]
private static let registryLock = NSLock()
private static func getOSLog(subsystem: String, category: String) -> OSLog {
diff --git a/ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift b/ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift
index 7dbf88dbb5..a8d8899752 100644
--- a/ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift
+++ b/ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift
@@ -9,7 +9,7 @@
import Combine
import MullvadSettings
-public struct AccessMethodRepositoryStub: AccessMethodRepositoryDataSource {
+public struct AccessMethodRepositoryStub: AccessMethodRepositoryDataSource, @unchecked Sendable {
public var directAccess: PersistentAccessMethod
public var accessMethodsPublisher: AnyPublisher<[PersistentAccessMethod], Never> {
diff --git a/ios/MullvadMockData/MullvadREST/RESTRequestExecutor+Stubs.swift b/ios/MullvadMockData/MullvadREST/RESTRequestExecutor+Stubs.swift
index 25aef84e5b..c044269792 100644
--- a/ios/MullvadMockData/MullvadREST/RESTRequestExecutor+Stubs.swift
+++ b/ios/MullvadMockData/MullvadREST/RESTRequestExecutor+Stubs.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadREST
import MullvadTypes
-struct RESTRequestExecutorStub<Success>: RESTRequestExecutor {
+struct RESTRequestExecutorStub<Success: Sendable>: RESTRequestExecutor {
var success: (() -> Success)?
func execute(completionHandler: @escaping (Result<Success, Error>) -> Void) -> Cancellable {
diff --git a/ios/MullvadREST/ApiHandlers/APIAvailabilityTestRequest.swift b/ios/MullvadREST/ApiHandlers/APIAvailabilityTestRequest.swift
index 03c180c349..8292ca7f45 100644
--- a/ios/MullvadREST/ApiHandlers/APIAvailabilityTestRequest.swift
+++ b/ios/MullvadREST/ApiHandlers/APIAvailabilityTestRequest.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadTypes
extension REST {
- public struct APIAvailabilityTestRequest {
+ public struct APIAvailabilityTestRequest: Sendable {
let transport: RESTTransport
public init(transport: RESTTransport) {
@@ -21,7 +21,7 @@ extension REST {
///
/// - Parameter completion: Completes with `nil` if the request was successful, and `Error` otherwise.
/// - Returns: A cancellable token to cancel the request inflight.
- public func makeRequest(completion: @escaping (Swift.Error?) -> Void) -> Cancellable {
+ public func makeRequest(completion: @escaping @Sendable (Swift.Error?) -> Void) -> Cancellable {
do {
let factory = RequestFactory(
hostname: defaultAPIHostname,
diff --git a/ios/MullvadREST/ApiHandlers/AddressCache.swift b/ios/MullvadREST/ApiHandlers/AddressCache.swift
index 8db610fc48..2d7a988c7a 100644
--- a/ios/MullvadREST/ApiHandlers/AddressCache.swift
+++ b/ios/MullvadREST/ApiHandlers/AddressCache.swift
@@ -11,7 +11,7 @@ import MullvadLogging
import MullvadTypes
extension REST {
- public final class AddressCache {
+ public final class AddressCache: @unchecked Sendable {
/// Logger.
private let logger = Logger(label: "AddressCache")
diff --git a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift
index 691781ef4c..23ec4d872d 100644
--- a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift
@@ -10,16 +10,16 @@ import Foundation
import MullvadTypes
import WireGuardKitTypes
-public protocol APIQuerying {
+public protocol APIQuerying: Sendable {
func getAddressList(
retryStrategy: REST.RetryStrategy,
- completionHandler: @escaping ProxyCompletionHandler<[AnyIPEndpoint]>
+ completionHandler: @escaping @Sendable ProxyCompletionHandler<[AnyIPEndpoint]>
) -> Cancellable
func getRelays(
etag: String?,
retryStrategy: REST.RetryStrategy,
- completionHandler: @escaping ProxyCompletionHandler<REST.ServerRelaysCacheResponse>
+ completionHandler: @escaping @Sendable ProxyCompletionHandler<REST.ServerRelaysCacheResponse>
) -> Cancellable
func createApplePayment(
@@ -30,19 +30,19 @@ public protocol APIQuerying {
func sendProblemReport(
_ body: REST.ProblemReportRequest,
retryStrategy: REST.RetryStrategy,
- completionHandler: @escaping ProxyCompletionHandler<Void>
+ completionHandler: @escaping @Sendable ProxyCompletionHandler<Void>
) -> Cancellable
func submitVoucher(
voucherCode: String,
accountNumber: String,
retryStrategy: REST.RetryStrategy,
- completionHandler: @escaping ProxyCompletionHandler<REST.SubmitVoucherResponse>
+ completionHandler: @escaping @Sendable ProxyCompletionHandler<REST.SubmitVoucherResponse>
) -> Cancellable
}
extension REST {
- public final class APIProxy: Proxy<AuthProxyConfiguration>, APIQuerying {
+ public final class APIProxy: Proxy<AuthProxyConfiguration>, APIQuerying, @unchecked Sendable {
public init(configuration: AuthProxyConfiguration) {
super.init(
name: "APIProxy",
@@ -57,7 +57,7 @@ extension REST {
public func getAddressList(
retryStrategy: REST.RetryStrategy,
- completionHandler: @escaping ProxyCompletionHandler<[AnyIPEndpoint]>
+ completionHandler: @escaping @Sendable ProxyCompletionHandler<[AnyIPEndpoint]>
) -> Cancellable {
let requestHandler = AnyRequestHandler { endpoint in
try self.requestFactory.createRequest(
@@ -84,7 +84,7 @@ extension REST {
public func getRelays(
etag: String?,
retryStrategy: REST.RetryStrategy,
- completionHandler: @escaping ProxyCompletionHandler<ServerRelaysCacheResponse>
+ completionHandler: @escaping @Sendable ProxyCompletionHandler<ServerRelaysCacheResponse>
) -> Cancellable {
let requestHandler = AnyRequestHandler { endpoint in
var requestBuilder = try self.requestFactory.createRequestBuilder(
@@ -240,7 +240,7 @@ extension REST {
voucherCode: String,
accountNumber: String,
retryStrategy: REST.RetryStrategy,
- completionHandler: @escaping ProxyCompletionHandler<SubmitVoucherResponse>
+ completionHandler: @escaping @Sendable ProxyCompletionHandler<SubmitVoucherResponse>
) -> Cancellable {
let requestHandler = AnyRequestHandler(
createURLRequest: { endpoint, authorization in
@@ -283,16 +283,16 @@ extension REST {
// MARK: - Response types
- public enum ServerRelaysCacheResponse {
+ public enum ServerRelaysCacheResponse: Sendable {
case notModified
case newContent(_ etag: String?, _ rawData: Data)
}
- private struct CreateApplePaymentRequest: Encodable {
+ private struct CreateApplePaymentRequest: Encodable, Sendable {
let receiptString: Data
}
- public enum CreateApplePaymentResponse {
+ public enum CreateApplePaymentResponse: Sendable {
case noTimeAdded(_ expiry: Date)
case timeAdded(_ timeAdded: Int, _ newExpiry: Date)
@@ -322,12 +322,12 @@ extension REST {
}
}
- private struct CreateApplePaymentRawResponse: Decodable {
+ private struct CreateApplePaymentRawResponse: Decodable, Sendable {
let timeAdded: Int
let newExpiry: Date
}
- public struct ProblemReportRequest: Encodable {
+ public struct ProblemReportRequest: Encodable, Sendable {
public let address: String
public let message: String
public let log: String
@@ -341,11 +341,11 @@ extension REST {
}
}
- private struct SubmitVoucherRequest: Encodable {
+ private struct SubmitVoucherRequest: Encodable, Sendable {
let voucherCode: String
}
- public struct SubmitVoucherResponse: Decodable {
+ public struct SubmitVoucherResponse: Decodable, Sendable {
public let timeAdded: Int
public let newExpiry: Date
diff --git a/ios/MullvadREST/ApiHandlers/RESTAccessTokenManager.swift b/ios/MullvadREST/ApiHandlers/RESTAccessTokenManager.swift
index d15bd6b9cb..6c5a59da4e 100644
--- a/ios/MullvadREST/ApiHandlers/RESTAccessTokenManager.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTAccessTokenManager.swift
@@ -11,17 +11,17 @@ import MullvadLogging
import MullvadTypes
import Operations
-public protocol RESTAccessTokenManagement {
+public protocol RESTAccessTokenManagement: Sendable {
func getAccessToken(
accountNumber: String,
- completionHandler: @escaping ProxyCompletionHandler<REST.AccessTokenData>
+ completionHandler: @escaping @Sendable ProxyCompletionHandler<REST.AccessTokenData>
) -> Cancellable
func invalidateAllTokens()
}
extension REST {
- public final class AccessTokenManager: RESTAccessTokenManagement {
+ public final class AccessTokenManager: RESTAccessTokenManagement, @unchecked Sendable {
private let logger = Logger(label: "REST.AccessTokenManager")
private let operationQueue = AsyncOperationQueue.makeSerial()
private let dispatchQueue = DispatchQueue(label: "REST.AccessTokenManager.dispatchQueue")
@@ -34,7 +34,7 @@ extension REST {
public func getAccessToken(
accountNumber: String,
- completionHandler: @escaping ProxyCompletionHandler<REST.AccessTokenData>
+ completionHandler: @escaping @Sendable ProxyCompletionHandler<REST.AccessTokenData>
) -> Cancellable {
let operation =
ResultBlockOperation<REST.AccessTokenData>(dispatchQueue: dispatchQueue) { finish -> Cancellable in
diff --git a/ios/MullvadREST/ApiHandlers/RESTAccountsProxy.swift b/ios/MullvadREST/ApiHandlers/RESTAccountsProxy.swift
index 568e24face..49c24bcd8f 100644
--- a/ios/MullvadREST/ApiHandlers/RESTAccountsProxy.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTAccountsProxy.swift
@@ -9,10 +9,10 @@
import Foundation
import MullvadTypes
-public protocol RESTAccountHandling {
+public protocol RESTAccountHandling: Sendable {
func createAccount(
retryStrategy: REST.RetryStrategy,
- completion: @escaping ProxyCompletionHandler<REST.NewAccountData>
+ completion: @escaping @Sendable ProxyCompletionHandler<REST.NewAccountData>
) -> Cancellable
func getAccountData(accountNumber: String) -> any RESTRequestExecutor<Account>
@@ -25,7 +25,7 @@ public protocol RESTAccountHandling {
}
extension REST {
- public final class AccountsProxy: Proxy<AuthProxyConfiguration>, RESTAccountHandling {
+ public final class AccountsProxy: Proxy<AuthProxyConfiguration>, RESTAccountHandling, @unchecked Sendable {
public init(configuration: AuthProxyConfiguration) {
super.init(
name: "AccountsProxy",
@@ -135,7 +135,7 @@ extension REST {
}
}
- public struct NewAccountData: Decodable {
+ public struct NewAccountData: Decodable, Sendable {
public let id: String
public let expiry: Date
public let maxPorts: Int
diff --git a/ios/MullvadREST/ApiHandlers/RESTAuthenticationProxy.swift b/ios/MullvadREST/ApiHandlers/RESTAuthenticationProxy.swift
index cbcd6fdc07..f0b3c59133 100644
--- a/ios/MullvadREST/ApiHandlers/RESTAuthenticationProxy.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTAuthenticationProxy.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadTypes
extension REST {
- public final class AuthenticationProxy: Proxy<ProxyConfiguration> {
+ public final class AuthenticationProxy: Proxy<ProxyConfiguration>, @unchecked Sendable {
public init(configuration: ProxyConfiguration) {
super.init(
name: "AuthenticationProxy",
@@ -57,12 +57,12 @@ extension REST {
}
}
- public struct AccessTokenData: Decodable {
+ public struct AccessTokenData: Decodable, Sendable {
let accessToken: String
let expiry: Date
}
- private struct AccessTokenRequest: Encodable {
+ private struct AccessTokenRequest: Encodable, Sendable {
let accountNumber: String
}
}
diff --git a/ios/MullvadREST/ApiHandlers/RESTAuthorization.swift b/ios/MullvadREST/ApiHandlers/RESTAuthorization.swift
index 340bd63f5c..1351d9f0cc 100644
--- a/ios/MullvadREST/ApiHandlers/RESTAuthorization.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTAuthorization.swift
@@ -11,7 +11,7 @@ import MullvadTypes
import Operations
protocol RESTAuthorizationProvider {
- func getAuthorization(completion: @escaping (Result<REST.Authorization, Swift.Error>) -> Void)
+ func getAuthorization(completion: @escaping @Sendable (Result<REST.Authorization, Swift.Error>) -> Void)
-> Cancellable
}
@@ -28,7 +28,7 @@ extension REST {
}
func getAuthorization(
- completion: @escaping (Result<REST.Authorization, Swift.Error>)
+ completion: @escaping @Sendable (Result<REST.Authorization, Swift.Error>)
-> Void
) -> Cancellable {
accessTokenManager.getAccessToken(accountNumber: accountNumber) { result in
diff --git a/ios/MullvadREST/ApiHandlers/RESTDefaults.swift b/ios/MullvadREST/ApiHandlers/RESTDefaults.swift
index 8875096931..c56deb5ca3 100644
--- a/ios/MullvadREST/ApiHandlers/RESTDefaults.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTDefaults.swift
@@ -13,7 +13,7 @@ import MullvadTypes
extension REST {
/// The API hostname and endpoint are defined in the Info.plist of the MullvadREST framework bundle
/// This is due to not being able to target `Bundle.main` from a Unit Test environment as it gets its own bundle that would not contain the above variables.
- private static let infoDictionary = Bundle(for: AddressCache.self).infoDictionary!
+ nonisolated(unsafe) private static let infoDictionary = Bundle(for: AddressCache.self).infoDictionary!
/// Default API hostname.
public static let defaultAPIHostname = infoDictionary["ApiHostName"] as! String
diff --git a/ios/MullvadREST/ApiHandlers/RESTDevicesProxy.swift b/ios/MullvadREST/ApiHandlers/RESTDevicesProxy.swift
index dd49179c30..402767a6ec 100644
--- a/ios/MullvadREST/ApiHandlers/RESTDevicesProxy.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTDevicesProxy.swift
@@ -8,34 +8,34 @@
import Foundation
import MullvadTypes
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
-public protocol DeviceHandling {
+public protocol DeviceHandling: Sendable {
func getDevice(
accountNumber: String,
identifier: String,
retryStrategy: REST.RetryStrategy,
- completion: @escaping ProxyCompletionHandler<Device>
+ completion: @escaping @Sendable ProxyCompletionHandler<Device>
) -> Cancellable
func getDevices(
accountNumber: String,
retryStrategy: REST.RetryStrategy,
- completion: @escaping ProxyCompletionHandler<[Device]>
+ completion: @escaping @Sendable ProxyCompletionHandler<[Device]>
) -> Cancellable
func createDevice(
accountNumber: String,
request: REST.CreateDeviceRequest,
retryStrategy: REST.RetryStrategy,
- completion: @escaping ProxyCompletionHandler<Device>
+ completion: @escaping @Sendable ProxyCompletionHandler<Device>
) -> Cancellable
func deleteDevice(
accountNumber: String,
identifier: String,
retryStrategy: REST.RetryStrategy,
- completion: @escaping ProxyCompletionHandler<Bool>
+ completion: @escaping @Sendable ProxyCompletionHandler<Bool>
) -> Cancellable
func rotateDeviceKey(
@@ -43,12 +43,12 @@ public protocol DeviceHandling {
identifier: String,
publicKey: PublicKey,
retryStrategy: REST.RetryStrategy,
- completion: @escaping ProxyCompletionHandler<Device>
+ completion: @escaping @Sendable ProxyCompletionHandler<Device>
) -> Cancellable
}
extension REST {
- public final class DevicesProxy: Proxy<AuthProxyConfiguration>, DeviceHandling {
+ public final class DevicesProxy: Proxy<AuthProxyConfiguration>, DeviceHandling, @unchecked Sendable {
public init(configuration: AuthProxyConfiguration) {
super.init(
name: "DevicesProxy",
@@ -161,7 +161,7 @@ extension REST {
accountNumber: String,
request: CreateDeviceRequest,
retryStrategy: REST.RetryStrategy,
- completion: @escaping ProxyCompletionHandler<Device>
+ completion: @escaping @Sendable ProxyCompletionHandler<Device>
) -> Cancellable {
let requestHandler = AnyRequestHandler(
createURLRequest: { endpoint, authorization in
@@ -262,7 +262,7 @@ extension REST {
identifier: String,
publicKey: PublicKey,
retryStrategy: REST.RetryStrategy,
- completion: @escaping ProxyCompletionHandler<Device>
+ completion: @escaping @Sendable ProxyCompletionHandler<Device>
) -> Cancellable {
let requestHandler = AnyRequestHandler(
createURLRequest: { endpoint, authorization in
@@ -310,7 +310,7 @@ extension REST {
}
}
- public struct CreateDeviceRequest: Encodable {
+ public struct CreateDeviceRequest: Encodable, Sendable {
let publicKey: PublicKey
let hijackDNS: Bool
@@ -332,7 +332,7 @@ extension REST {
}
}
- private struct RotateDeviceKeyRequest: Encodable {
+ private struct RotateDeviceKeyRequest: Encodable, Sendable {
let publicKey: PublicKey
private enum CodingKeys: String, CodingKey {
diff --git a/ios/MullvadREST/ApiHandlers/RESTError.swift b/ios/MullvadREST/ApiHandlers/RESTError.swift
index 1fdb255b81..5fdd88e0d2 100644
--- a/ios/MullvadREST/ApiHandlers/RESTError.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTError.swift
@@ -11,7 +11,7 @@ import MullvadTypes
extension REST {
/// An error type returned by REST API classes.
- public enum Error: LocalizedError, WrappingError {
+ public enum Error: LocalizedError, WrappingError, Sendable {
/// Failure to create URL request.
case createURLRequest(Swift.Error)
@@ -80,7 +80,7 @@ extension REST {
}
}
- public struct ServerErrorResponse: Decodable {
+ public struct ServerErrorResponse: Decodable, Sendable {
public let code: ServerResponseCode
public let detail: String?
@@ -103,7 +103,7 @@ extension REST {
}
}
- public struct ServerResponseCode: RawRepresentable, Equatable {
+ public struct ServerResponseCode: RawRepresentable, Equatable, Sendable {
public static let invalidAccount = ServerResponseCode(rawValue: "INVALID_ACCOUNT")
public static let keyLimitReached = ServerResponseCode(rawValue: "KEY_LIMIT_REACHED")
public static let publicKeyNotFound = ServerResponseCode(rawValue: "PUBKEY_NOT_FOUND")
diff --git a/ios/MullvadREST/ApiHandlers/RESTNetworkOperation.swift b/ios/MullvadREST/ApiHandlers/RESTNetworkOperation.swift
index 0321c7be79..9d0f074f67 100644
--- a/ios/MullvadREST/ApiHandlers/RESTNetworkOperation.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTNetworkOperation.swift
@@ -12,7 +12,7 @@ import MullvadTypes
import Operations
extension REST {
- class NetworkOperation<Success>: ResultOperation<Success> {
+ class NetworkOperation<Success: Sendable>: ResultOperation<Success>, @unchecked Sendable {
private let requestHandler: RESTRequestHandler
private let responseHandler: any RESTResponseHandler<Success>
diff --git a/ios/MullvadREST/ApiHandlers/RESTProxy.swift b/ios/MullvadREST/ApiHandlers/RESTProxy.swift
index 49d4fc8968..f5815b6690 100644
--- a/ios/MullvadREST/ApiHandlers/RESTProxy.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTProxy.swift
@@ -10,10 +10,10 @@ import Foundation
import MullvadTypes
import Operations
-public typealias ProxyCompletionHandler<Success> = (Result<Success, Swift.Error>) -> Void
+public typealias ProxyCompletionHandler<Success: Sendable> = @Sendable (Result<Success, Swift.Error>) -> Void
extension REST {
- public class Proxy<ConfigurationType: ProxyConfiguration> {
+ public class Proxy<ConfigurationType: ProxyConfiguration>: @unchecked Sendable {
/// Synchronization queue used by network operations.
let dispatchQueue: DispatchQueue
@@ -43,7 +43,7 @@ extension REST {
self.responseDecoder = responseDecoder
}
- func makeRequestExecutor<Success>(
+ func makeRequestExecutor<Success: Sendable>(
name: String,
requestHandler: RESTRequestHandler,
responseHandler: some RESTResponseHandler<Success>
@@ -61,7 +61,7 @@ extension REST {
}
/// Factory object producing instances of `NetworkOperation`.
- private struct NetworkOperationFactory<Success, ConfigurationType: ProxyConfiguration> {
+ private struct NetworkOperationFactory<Success: Sendable, ConfigurationType: ProxyConfiguration> {
let dispatchQueue: DispatchQueue
let configuration: ConfigurationType
@@ -87,7 +87,7 @@ extension REST {
}
/// Network request executor that supports block-based and async execution flows.
- private struct RequestExecutor<Success, ConfigurationType: ProxyConfiguration>: RESTRequestExecutor {
+ private struct RequestExecutor<Success: Sendable, ConfigurationType: ProxyConfiguration>: RESTRequestExecutor {
let operationFactory: NetworkOperationFactory<Success, ConfigurationType>
let operationQueue: AsyncOperationQueue
@@ -120,7 +120,7 @@ extension REST {
}
}
- func execute(completionHandler: @escaping ProxyCompletionHandler<Success>) -> Cancellable {
+ func execute(completionHandler: @escaping @Sendable ProxyCompletionHandler<Success>) -> Cancellable {
return execute(retryStrategy: .noRetry, completionHandler: completionHandler)
}
@@ -129,7 +129,7 @@ extension REST {
}
}
- public class ProxyConfiguration {
+ public class ProxyConfiguration: @unchecked Sendable {
public let transportProvider: RESTTransportProvider
public let addressCacheStore: AddressCache
@@ -142,7 +142,7 @@ extension REST {
}
}
- public class AuthProxyConfiguration: ProxyConfiguration {
+ public class AuthProxyConfiguration: ProxyConfiguration, @unchecked Sendable {
public let accessTokenManager: RESTAccessTokenManagement
public init(
diff --git a/ios/MullvadREST/ApiHandlers/RESTRequestExecutor.swift b/ios/MullvadREST/ApiHandlers/RESTRequestExecutor.swift
index a705d1cda2..3b7a01f1c8 100644
--- a/ios/MullvadREST/ApiHandlers/RESTRequestExecutor.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTRequestExecutor.swift
@@ -10,15 +10,15 @@ import Foundation
import protocol MullvadTypes.Cancellable
public protocol RESTRequestExecutor<Success> {
- associatedtype Success
+ associatedtype Success: Sendable
/// Execute new network request with `.noRetry` strategy and receive the result in a completion handler on main queue.
- func execute(completionHandler: @escaping (Result<Success, Swift.Error>) -> Void) -> Cancellable
+ func execute(completionHandler: @escaping @Sendable (Result<Success, Swift.Error>) -> Void) -> Cancellable
/// Execute new network request and receive the result in a completion handler on main queue.
func execute(
retryStrategy: REST.RetryStrategy,
- completionHandler: @escaping (Result<Success, Swift.Error>) -> Void
+ completionHandler: @escaping @Sendable (Result<Success, Swift.Error>) -> Void
) -> Cancellable
/// Execute new network request with `.noRetry` strategy and receive the result back via async flow.
diff --git a/ios/MullvadREST/ApiHandlers/RESTRequestHandler.swift b/ios/MullvadREST/ApiHandlers/RESTRequestHandler.swift
index a40212dcf5..169cf7e72d 100644
--- a/ios/MullvadREST/ApiHandlers/RESTRequestHandler.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTRequestHandler.swift
@@ -29,7 +29,7 @@ extension REST {
let authorizationProvider: RESTAuthorizationProvider?
- init(createURLRequest: @escaping (AnyIPEndpoint) throws -> REST.Request) {
+ init(createURLRequest: @escaping @Sendable (AnyIPEndpoint) throws -> REST.Request) {
_createURLRequest = { endpoint, _ in
try createURLRequest(endpoint)
}
@@ -37,7 +37,7 @@ extension REST {
}
init(
- createURLRequest: @escaping (AnyIPEndpoint, REST.Authorization) throws -> REST.Request,
+ createURLRequest: @escaping @Sendable (AnyIPEndpoint, REST.Authorization) throws -> REST.Request,
authorizationProvider: RESTAuthorizationProvider
) {
_createURLRequest = { endpoint, authorization in
diff --git a/ios/MullvadREST/ApiHandlers/RESTTaskIdentifier.swift b/ios/MullvadREST/ApiHandlers/RESTTaskIdentifier.swift
index f892483567..e62030ed3b 100644
--- a/ios/MullvadREST/ApiHandlers/RESTTaskIdentifier.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTTaskIdentifier.swift
@@ -10,7 +10,7 @@ import Foundation
extension REST {
private static let nslock = NSLock()
- private static var taskCount: UInt32 = 0
+ nonisolated(unsafe) private static var taskCount: UInt32 = 0
static func getTaskIdentifier(name: String) -> String {
nslock.lock()
diff --git a/ios/MullvadREST/ApiHandlers/SSLPinningURLSessionDelegate.swift b/ios/MullvadREST/ApiHandlers/SSLPinningURLSessionDelegate.swift
index a5fd867770..e0021e8a18 100644
--- a/ios/MullvadREST/ApiHandlers/SSLPinningURLSessionDelegate.swift
+++ b/ios/MullvadREST/ApiHandlers/SSLPinningURLSessionDelegate.swift
@@ -11,7 +11,7 @@ import MullvadLogging
import Network
import Security
-final class SSLPinningURLSessionDelegate: NSObject, URLSessionDelegate {
+final class SSLPinningURLSessionDelegate: NSObject, URLSessionDelegate, @unchecked Sendable {
private let sslHostname: String
private let trustedRootCertificates: [SecCertificate]
private let addressCache: REST.AddressCache
@@ -29,7 +29,7 @@ final class SSLPinningURLSessionDelegate: NSObject, URLSessionDelegate {
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
- completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
+ completionHandler: @escaping @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let serverTrust = challenge.protectionSpace.serverTrust {
diff --git a/ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift b/ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift
index bab0962bba..fbc9c56604 100644
--- a/ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift
+++ b/ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift
@@ -11,7 +11,7 @@ import MullvadTypes
import Network
extension REST {
- public struct ServerLocation: Codable, Equatable {
+ public struct ServerLocation: Codable, Equatable, Sendable {
public let country: String
public let city: String
public let latitude: Double
@@ -25,7 +25,7 @@ extension REST {
}
}
- public struct BridgeRelay: Codable, Equatable {
+ public struct BridgeRelay: Codable, Equatable, Sendable {
public let hostname: String
public let active: Bool
public let owned: Bool
@@ -50,7 +50,7 @@ extension REST {
}
}
- public struct ServerRelay: Codable, Equatable {
+ public struct ServerRelay: Codable, Equatable, Sendable {
public let hostname: String
public let active: Bool
public let owned: Bool
@@ -99,7 +99,7 @@ extension REST {
}
}
- public struct ServerWireguardTunnels: Codable, Equatable {
+ public struct ServerWireguardTunnels: Codable, Equatable, Sendable {
public let ipv4Gateway: IPv4Address
public let ipv6Gateway: IPv6Address
public let portRanges: [[UInt16]]
@@ -121,19 +121,19 @@ extension REST {
}
}
- public struct ServerShadowsocks: Codable, Equatable {
+ public struct ServerShadowsocks: Codable, Equatable, Sendable {
public let `protocol`: String
public let port: UInt16
public let cipher: String
public let password: String
}
- public struct ServerBridges: Codable, Equatable {
+ public struct ServerBridges: Codable, Equatable, Sendable {
public let shadowsocks: [ServerShadowsocks]
public let relays: [BridgeRelay]
}
- public struct ServerRelaysResponse: Codable, Equatable {
+ public struct ServerRelaysResponse: Codable, Equatable, Sendable {
public let locations: [String: ServerLocation]
public let wireguard: ServerWireguardTunnels
public let bridge: ServerBridges
diff --git a/ios/MullvadREST/Relay/IPOverrideWrapper.swift b/ios/MullvadREST/Relay/IPOverrideWrapper.swift
index 3e11591317..ac4946ee80 100644
--- a/ios/MullvadREST/Relay/IPOverrideWrapper.swift
+++ b/ios/MullvadREST/Relay/IPOverrideWrapper.swift
@@ -9,7 +9,7 @@
import MullvadSettings
import MullvadTypes
-public class IPOverrideWrapper: RelayCacheProtocol {
+public final class IPOverrideWrapper: RelayCacheProtocol {
private let relayCache: RelayCacheProtocol
private let ipOverrideRepository: any IPOverrideRepositoryProtocol
diff --git a/ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift b/ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift
index 4250a7ffb6..afc4eee437 100644
--- a/ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift
+++ b/ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift
@@ -8,7 +8,7 @@
import Foundation
-public enum NoRelaysSatisfyingConstraintsReason {
+public enum NoRelaysSatisfyingConstraintsReason: Sendable {
case filterConstraintNotMatching
case invalidPort
case entryEqualsExit
@@ -19,7 +19,7 @@ public enum NoRelaysSatisfyingConstraintsReason {
case relayConstraintNotMatching
}
-public struct NoRelaysSatisfyingConstraintsError: LocalizedError {
+public struct NoRelaysSatisfyingConstraintsError: LocalizedError, Sendable {
public let reason: NoRelaysSatisfyingConstraintsReason
public var errorDescription: String? {
diff --git a/ios/MullvadREST/Relay/RelayCache.swift b/ios/MullvadREST/Relay/RelayCache.swift
index 966d3a810d..e71c3ee347 100644
--- a/ios/MullvadREST/Relay/RelayCache.swift
+++ b/ios/MullvadREST/Relay/RelayCache.swift
@@ -9,7 +9,7 @@
import Foundation
import MullvadTypes
-public protocol RelayCacheProtocol {
+public protocol RelayCacheProtocol: Sendable {
/// Reads from a cached list,
/// which falls back to reading from prebundled relays if there was no cache hit
func read() throws -> StoredRelays
@@ -23,9 +23,9 @@ public protocol RelayCacheProtocol {
/// - Warning: `RelayCache` should not be used directly. It should be used through `IPOverrideWrapper` to have
/// ip overrides applied.
-public final class RelayCache: RelayCacheProtocol {
+public final class RelayCache: RelayCacheProtocol, Sendable {
private let fileURL: URL
- private let fileCache: any FileCacheProtocol<StoredRelays>
+ nonisolated(unsafe) private let fileCache: any FileCacheProtocol<StoredRelays>
/// Designated initializer
public init(cacheDirectory: URL) {
diff --git a/ios/MullvadREST/Relay/RelaySelectorProtocol.swift b/ios/MullvadREST/Relay/RelaySelectorProtocol.swift
index 92d48a8239..d7070055e7 100644
--- a/ios/MullvadREST/Relay/RelaySelectorProtocol.swift
+++ b/ios/MullvadREST/Relay/RelaySelectorProtocol.swift
@@ -19,7 +19,7 @@ public protocol RelaySelectorProtocol {
}
/// Struct describing the selected relay.
-public struct SelectedRelay: Equatable, Codable {
+public struct SelectedRelay: Equatable, Codable, Sendable {
/// Selected relay endpoint.
public let endpoint: MullvadEndpoint
@@ -43,7 +43,7 @@ extension SelectedRelay: CustomDebugStringConvertible {
}
}
-public struct SelectedRelays: Equatable, Codable {
+public struct SelectedRelays: Equatable, Codable, Sendable {
public let entry: SelectedRelay?
public let exit: SelectedRelay
public let retryAttempt: UInt
diff --git a/ios/MullvadREST/Relay/RelaySelectorWrapper.swift b/ios/MullvadREST/Relay/RelaySelectorWrapper.swift
index e7a15aa78f..200704cf7c 100644
--- a/ios/MullvadREST/Relay/RelaySelectorWrapper.swift
+++ b/ios/MullvadREST/Relay/RelaySelectorWrapper.swift
@@ -9,7 +9,7 @@
import MullvadSettings
import MullvadTypes
-public final class RelaySelectorWrapper: RelaySelectorProtocol {
+public final class RelaySelectorWrapper: RelaySelectorProtocol, Sendable {
let relayCache: RelayCacheProtocol
public init(relayCache: RelayCacheProtocol) {
diff --git a/ios/MullvadREST/RetryStrategy/Jittered.swift b/ios/MullvadREST/RetryStrategy/Jittered.swift
index 0f930b3aab..c13b9e2654 100644
--- a/ios/MullvadREST/RetryStrategy/Jittered.swift
+++ b/ios/MullvadREST/RetryStrategy/Jittered.swift
@@ -34,7 +34,7 @@ struct Transformer<Inner: IteratorProtocol>: IteratorProtocol {
private var inner: Inner
private let transformer: (Inner.Element?) -> Inner.Element?
- init(inner: Inner, transform: @escaping (Inner.Element?) -> Inner.Element?) {
+ init(inner: Inner, transform: @escaping @Sendable (Inner.Element?) -> Inner.Element?) {
self.inner = inner
self.transformer = transform
}
diff --git a/ios/MullvadREST/RetryStrategy/RetryStrategy.swift b/ios/MullvadREST/RetryStrategy/RetryStrategy.swift
index eac6441039..896cceadca 100644
--- a/ios/MullvadREST/RetryStrategy/RetryStrategy.swift
+++ b/ios/MullvadREST/RetryStrategy/RetryStrategy.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadTypes
extension REST {
- public struct RetryStrategy {
+ public struct RetryStrategy: Sendable {
public var maxRetryCount: Int
public var delay: RetryDelay
public var applyJitter: Bool
@@ -42,34 +42,34 @@ extension REST {
}
/// Strategy configured to never retry.
- public static var noRetry = RetryStrategy(
+ public static let noRetry = RetryStrategy(
maxRetryCount: 0,
delay: .never,
applyJitter: false
)
/// Strategy configured with 2 retry attempts and exponential backoff.
- public static var `default` = RetryStrategy(
+ public static let `default` = RetryStrategy(
maxRetryCount: 2,
delay: defaultRetryDelay,
applyJitter: true
)
/// Strategy configured with 10 retry attempts and exponential backoff.
- public static var aggressive = RetryStrategy(
+ public static let aggressive = RetryStrategy(
maxRetryCount: 10,
delay: defaultRetryDelay,
applyJitter: true
)
/// Default retry delay.
- public static var defaultRetryDelay: RetryDelay = .exponentialBackoff(
+ public static let defaultRetryDelay: RetryDelay = .exponentialBackoff(
initial: .seconds(2),
multiplier: 2,
maxDelay: .seconds(8)
)
- public static var postQuantumKeyExchange = RetryStrategy(
+ public static let postQuantumKeyExchange = RetryStrategy(
maxRetryCount: 10,
delay: .exponentialBackoff(
initial: .seconds(10),
@@ -79,7 +79,7 @@ extension REST {
applyJitter: true
)
- public static var failedMigrationRecovery = RetryStrategy(
+ public static let failedMigrationRecovery = RetryStrategy(
maxRetryCount: .max,
delay: .exponentialBackoff(
initial: .seconds(5),
@@ -90,7 +90,7 @@ extension REST {
)
}
- public enum RetryDelay: Equatable {
+ public enum RetryDelay: Equatable, Sendable {
/// Never wait to retry.
case never
diff --git a/ios/MullvadREST/Transport/AccessMethodIterator.swift b/ios/MullvadREST/Transport/AccessMethodIterator.swift
index 245bca30f5..8a3bf2b84a 100644
--- a/ios/MullvadREST/Transport/AccessMethodIterator.swift
+++ b/ios/MullvadREST/Transport/AccessMethodIterator.swift
@@ -10,7 +10,7 @@ import Combine
import Foundation
import MullvadSettings
-class AccessMethodIterator {
+final class AccessMethodIterator: @unchecked Sendable {
private let dataSource: AccessMethodRepositoryDataSource
private var index = 0
diff --git a/ios/MullvadREST/Transport/Direct/URLSessionTransport.swift b/ios/MullvadREST/Transport/Direct/URLSessionTransport.swift
index 77c5b48e59..ac40ded63d 100644
--- a/ios/MullvadREST/Transport/Direct/URLSessionTransport.swift
+++ b/ios/MullvadREST/Transport/Direct/URLSessionTransport.swift
@@ -24,7 +24,7 @@ public final class URLSessionTransport: RESTTransport {
public func sendRequest(
_ request: URLRequest,
- completion: @escaping (Data?, URLResponse?, Swift.Error?) -> Void
+ completion: @escaping @Sendable (Data?, URLResponse?, Swift.Error?) -> Void
) -> Cancellable {
let dataTask = urlSession.dataTask(with: request, completionHandler: completion)
dataTask.resume()
diff --git a/ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift b/ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift
index 2910a14eed..748e48e13a 100644
--- a/ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift
+++ b/ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift
@@ -9,7 +9,7 @@ import Foundation
import MullvadRustRuntime
import MullvadTypes
-public final class EncryptedDNSTransport: RESTTransport {
+public final class EncryptedDNSTransport: RESTTransport, @unchecked Sendable {
public var name: String {
"encrypted-dns-url-session"
}
@@ -39,7 +39,7 @@ public final class EncryptedDNSTransport: RESTTransport {
/// most of the time starting the DNS proxy was already spent.
public func sendRequest(
_ request: URLRequest,
- completion: @escaping (Data?, URLResponse?, (any Error)?) -> Void
+ completion: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void
) -> any Cancellable {
dispatchQueue.async { [weak self] in
guard let self else { return }
@@ -58,7 +58,7 @@ public final class EncryptedDNSTransport: RESTTransport {
return components?.url
}
- let wrappedCompletionHandler: (Data?, URLResponse?, (any Error)?)
+ let wrappedCompletionHandler: @Sendable (Data?, URLResponse?, (any Error)?)
-> Void = { [weak self] data, response, maybeError in
if maybeError != nil {
self?.encryptedDnsProxy.stop()
@@ -77,7 +77,7 @@ public final class EncryptedDNSTransport: RESTTransport {
}
return AnyCancellable { [weak self] in
- self?.dispatchQueue.async {
+ self?.dispatchQueue.async { [weak self] in
self?.dnsProxyTask?.cancel()
}
}
diff --git a/ios/MullvadREST/Transport/RESTTransport.swift b/ios/MullvadREST/Transport/RESTTransport.swift
index eb87b6db99..e7c1c8cde8 100644
--- a/ios/MullvadREST/Transport/RESTTransport.swift
+++ b/ios/MullvadREST/Transport/RESTTransport.swift
@@ -9,8 +9,9 @@
import Foundation
import MullvadTypes
-public protocol RESTTransport {
+public protocol RESTTransport: Sendable {
var name: String { get }
- func sendRequest(_ request: URLRequest, completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> Cancellable
+ func sendRequest(_ request: URLRequest, completion: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void)
+ -> Cancellable
}
diff --git a/ios/MullvadREST/Transport/RESTTransportProvider.swift b/ios/MullvadREST/Transport/RESTTransportProvider.swift
index 33e661bc9c..e7c2a36d8c 100644
--- a/ios/MullvadREST/Transport/RESTTransportProvider.swift
+++ b/ios/MullvadREST/Transport/RESTTransportProvider.swift
@@ -17,7 +17,7 @@ extension REST {
public struct AnyTransportProvider: RESTTransportProvider {
private let block: () -> RESTTransport?
- public init(_ block: @escaping () -> RESTTransport?) {
+ public init(_ block: @escaping @Sendable () -> RESTTransport?) {
self.block = block
}
diff --git a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfiguration.swift b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfiguration.swift
index 4c9b1a7d6b..36119d2af8 100644
--- a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfiguration.swift
+++ b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfiguration.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadTypes
import Network
-public struct ShadowsocksConfiguration: Codable, Equatable {
+public struct ShadowsocksConfiguration: Codable, Equatable, Sendable {
public let address: AnyIPAddress
public let port: UInt16
public let password: String
diff --git a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfigurationCache.swift b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfigurationCache.swift
index e5c68b631c..455be3b103 100644
--- a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfigurationCache.swift
+++ b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfigurationCache.swift
@@ -9,14 +9,14 @@
import Foundation
import MullvadTypes
-public protocol ShadowsocksConfigurationCacheProtocol {
+public protocol ShadowsocksConfigurationCacheProtocol: Sendable {
func read() throws -> ShadowsocksConfiguration
func write(_ configuration: ShadowsocksConfiguration) throws
func clear() throws
}
/// Holds a shadowsocks configuration object backed by a caching mechanism shared across processes
-public final class ShadowsocksConfigurationCache: ShadowsocksConfigurationCacheProtocol {
+public final class ShadowsocksConfigurationCache: ShadowsocksConfigurationCacheProtocol, @unchecked Sendable {
private let configurationLock = NSLock()
private var cachedConfiguration: ShadowsocksConfiguration?
private let fileCache: FileCache<ShadowsocksConfiguration>
diff --git a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift
index c35b0692b5..44092b1192 100644
--- a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift
+++ b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift
@@ -10,18 +10,18 @@ import Foundation
import MullvadSettings
import MullvadTypes
-public protocol ShadowsocksLoaderProtocol {
+public protocol ShadowsocksLoaderProtocol: Sendable {
func load() throws -> ShadowsocksConfiguration
func clear() throws
}
-public class ShadowsocksLoader: ShadowsocksLoaderProtocol {
+public final class ShadowsocksLoader: ShadowsocksLoaderProtocol, Sendable {
let cache: ShadowsocksConfigurationCacheProtocol
let relaySelector: ShadowsocksRelaySelectorProtocol
let settingsUpdater: SettingsUpdater
- private var observer: SettingsObserverBlock!
- private var tunnelSettings = LatestTunnelSettings()
+ nonisolated(unsafe) private var observer: SettingsObserverBlock!
+ nonisolated(unsafe) private var tunnelSettings = LatestTunnelSettings()
private let settingsStrategy = TunnelSettingsStrategy()
deinit {
diff --git a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift
index c0d83bc701..9d5156a94c 100644
--- a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift
+++ b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadSettings
import MullvadTypes
-public protocol ShadowsocksRelaySelectorProtocol {
+public protocol ShadowsocksRelaySelectorProtocol: Sendable {
func selectRelay(with settings: LatestTunnelSettings) throws -> REST.BridgeRelay?
func getBridges() throws -> REST.ServerShadowsocks?
diff --git a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksTransport.swift b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksTransport.swift
index 16d4197856..684185a994 100644
--- a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksTransport.swift
+++ b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksTransport.swift
@@ -44,7 +44,7 @@ public final class ShadowsocksTransport: RESTTransport {
public func sendRequest(
_ request: URLRequest,
- completion: @escaping (Data?, URLResponse?, Swift.Error?) -> Void
+ completion: @escaping @Sendable (Data?, URLResponse?, Swift.Error?) -> Void
) -> Cancellable {
// Start the Shadowsocks proxy in order to get a local port
shadowsocksProxy.start()
diff --git a/ios/MullvadREST/Transport/Socks5/NWConnection+Extensions.swift b/ios/MullvadREST/Transport/Socks5/NWConnection+Extensions.swift
index d99f9e8081..7d4dc674bf 100644
--- a/ios/MullvadREST/Transport/Socks5/NWConnection+Extensions.swift
+++ b/ios/MullvadREST/Transport/Socks5/NWConnection+Extensions.swift
@@ -16,7 +16,7 @@ extension NWConnection {
- exactLength: exact number of bytes to read.
- completion: a completion handler.
*/
- func receive(exactLength: Int, completion: @escaping (Data?, ContentContext?, Bool, NWError?) -> Void) {
+ func receive(exactLength: Int, completion: @Sendable @escaping (Data?, ContentContext?, Bool, NWError?) -> Void) {
receive(minimumIncompleteLength: exactLength, maximumLength: exactLength, completion: completion)
}
}
diff --git a/ios/MullvadREST/Transport/Socks5/Socks5AddressType.swift b/ios/MullvadREST/Transport/Socks5/Socks5AddressType.swift
index ad013d1ae9..8fe190baba 100644
--- a/ios/MullvadREST/Transport/Socks5/Socks5AddressType.swift
+++ b/ios/MullvadREST/Transport/Socks5/Socks5AddressType.swift
@@ -8,7 +8,7 @@
import Foundation
/// Address type supported by socks protocol
-enum Socks5AddressType: UInt8 {
+enum Socks5AddressType: UInt8, Sendable {
case ipv4 = 0x01
case domainName = 0x03
case ipv6 = 0x04
diff --git a/ios/MullvadREST/Transport/Socks5/Socks5Authentication.swift b/ios/MullvadREST/Transport/Socks5/Socks5Authentication.swift
index a72dfc6108..83f9316b8d 100644
--- a/ios/MullvadREST/Transport/Socks5/Socks5Authentication.swift
+++ b/ios/MullvadREST/Transport/Socks5/Socks5Authentication.swift
@@ -14,13 +14,13 @@ enum Socks5AuthenticationMethod: UInt8 {
case usernamePassword = 0x02
}
-struct Socks5Authentication {
+struct Socks5Authentication: Sendable {
let connection: NWConnection
let endpoint: Socks5Endpoint
let configuration: Socks5Configuration
- typealias AuthenticationComplete = () -> Void
- typealias AuthenticationFailure = (Error) -> Void
+ typealias AuthenticationComplete = @Sendable () -> Void
+ typealias AuthenticationFailure = @Sendable (Error) -> Void
func authenticate(onComplete: @escaping AuthenticationComplete, onFailure: @escaping AuthenticationFailure) {
guard let username = configuration.username, let password = configuration.password else {
diff --git a/ios/MullvadREST/Transport/Socks5/Socks5Configuration.swift b/ios/MullvadREST/Transport/Socks5/Socks5Configuration.swift
index 9bf2eb87dc..03aa623a1f 100644
--- a/ios/MullvadREST/Transport/Socks5/Socks5Configuration.swift
+++ b/ios/MullvadREST/Transport/Socks5/Socks5Configuration.swift
@@ -11,7 +11,7 @@ import MullvadTypes
/// Socks5 configuration.
/// - See: ``URLSessionSocks5Transport``
-public struct Socks5Configuration: Equatable {
+public struct Socks5Configuration: Equatable, Sendable {
/// The socks proxy endpoint.
public var proxyEndpoint: AnyIPEndpoint
diff --git a/ios/MullvadREST/Transport/Socks5/Socks5ConnectNegotiation.swift b/ios/MullvadREST/Transport/Socks5/Socks5ConnectNegotiation.swift
index cbd4f0875e..bff35c5860 100644
--- a/ios/MullvadREST/Transport/Socks5/Socks5ConnectNegotiation.swift
+++ b/ios/MullvadREST/Transport/Socks5/Socks5ConnectNegotiation.swift
@@ -17,10 +17,10 @@ struct Socks5ConnectNegotiation {
let endpoint: Socks5Endpoint
/// Completion handler invoked on success.
- let onComplete: (Socks5ConnectReply) -> Void
+ let onComplete: @Sendable (Socks5ConnectReply) -> Void
/// Failure handler invoked on error.
- let onFailure: (Error) -> Void
+ let onFailure: @Sendable (Error) -> Void
/// Initiate negotiation by sending a connect command to the socks proxy.
func perform() {
diff --git a/ios/MullvadREST/Transport/Socks5/Socks5Connection.swift b/ios/MullvadREST/Transport/Socks5/Socks5Connection.swift
index be232f8437..670a9bc63c 100644
--- a/ios/MullvadREST/Transport/Socks5/Socks5Connection.swift
+++ b/ios/MullvadREST/Transport/Socks5/Socks5Connection.swift
@@ -9,7 +9,7 @@ import Foundation
import Network
/// A bidirectional data connection between a local endpoint and remote endpoint over socks proxy.
-final class Socks5Connection {
+final class Socks5Connection: Sendable {
/// The remote endpoint to which the client wants to establish connection over the socks proxy.
let remoteServerEndpoint: Socks5Endpoint
let configuration: Socks5Configuration
@@ -75,7 +75,7 @@ final class Socks5Connection {
- Parameter newStateHandler: state handler block.
*/
- func setStateHandler(_ newStateHandler: ((Socks5Connection, State) -> Void)?) {
+ func setStateHandler(_ newStateHandler: (@Sendable (Socks5Connection, State) -> Void)?) {
queue.async { [self] in
stateHandler = newStateHandler
}
@@ -110,8 +110,8 @@ final class Socks5Connection {
private let queue: DispatchQueue
private let localConnection: NWConnection
private let remoteConnection: NWConnection
- private var stateHandler: ((Socks5Connection, State) -> Void)?
- private var state: State = .initialized {
+ nonisolated(unsafe) private var stateHandler: (@Sendable (Socks5Connection, State) -> Void)?
+ nonisolated(unsafe) private var state: State = .initialized {
didSet {
stateHandler?(self, state)
}
diff --git a/ios/MullvadREST/Transport/Socks5/Socks5DataStreamHandler.swift b/ios/MullvadREST/Transport/Socks5/Socks5DataStreamHandler.swift
index 1089cecb8f..7eedcbcdc9 100644
--- a/ios/MullvadREST/Transport/Socks5/Socks5DataStreamHandler.swift
+++ b/ios/MullvadREST/Transport/Socks5/Socks5DataStreamHandler.swift
@@ -9,7 +9,7 @@ import Foundation
import Network
/// The object handling bidirectional streaming of data between local and remote connection.
-struct Socks5DataStreamHandler {
+struct Socks5DataStreamHandler: Sendable {
/// How many bytes the handler can receive at one time, when streaming data between local and remote connection.
static let maxBytesToRead = Int(UInt16.max)
@@ -20,7 +20,7 @@ struct Socks5DataStreamHandler {
let remoteConnection: NWConnection
/// Error handler.
- let errorHandler: (Error) -> Void
+ let errorHandler: @Sendable (Error) -> Void
/// Start streaming data between local and remote connection.
func start() {
diff --git a/ios/MullvadREST/Transport/Socks5/Socks5Endpoint.swift b/ios/MullvadREST/Transport/Socks5/Socks5Endpoint.swift
index 1587991bf7..dd967cb09e 100644
--- a/ios/MullvadREST/Transport/Socks5/Socks5Endpoint.swift
+++ b/ios/MullvadREST/Transport/Socks5/Socks5Endpoint.swift
@@ -10,7 +10,7 @@ import MullvadTypes
import Network
/// A network endpoint specified by DNS name and port.
-public struct Socks5HostEndpoint {
+public struct Socks5HostEndpoint: Sendable {
/// The endpoint's hostname.
public let hostname: String
@@ -43,7 +43,7 @@ public struct Socks5HostEndpoint {
}
/// The endpoint type used by objects implementing socks protocol.
-public enum Socks5Endpoint {
+public enum Socks5Endpoint: Sendable {
/// IPv4 endpoint.
case ipv4(IPv4Endpoint)
diff --git a/ios/MullvadREST/Transport/Socks5/Socks5EndpointReader.swift b/ios/MullvadREST/Transport/Socks5/Socks5EndpointReader.swift
index aff939e8be..b27bd8a3d0 100644
--- a/ios/MullvadREST/Transport/Socks5/Socks5EndpointReader.swift
+++ b/ios/MullvadREST/Transport/Socks5/Socks5EndpointReader.swift
@@ -10,7 +10,7 @@ import MullvadTypes
import Network
/// The object reading the endpoint data from connection.
-struct Socks5EndpointReader {
+struct Socks5EndpointReader: Sendable {
/// Connection to the socks proxy.
let connection: NWConnection
@@ -18,10 +18,10 @@ struct Socks5EndpointReader {
let addressType: Socks5AddressType
/// Completion handler called upon success.
- let onComplete: (Socks5Endpoint) -> Void
+ let onComplete: @Sendable (Socks5Endpoint) -> Void
/// Failure handler.
- let onFailure: (Error) -> Void
+ let onFailure: @Sendable (Error) -> Void
/// Start reading endpoint from connection.
func perform() {
@@ -69,7 +69,7 @@ struct Socks5EndpointReader {
}
}
- private func readBoundDomainNameLength(completion: @escaping (Int) -> Void) {
+ private func readBoundDomainNameLength(completion: @escaping @Sendable (Int) -> Void) {
// The length of domain length parameter in bytes.
let domainLengthLength = MemoryLayout<UInt8>.size
diff --git a/ios/MullvadREST/Transport/Socks5/Socks5Error.swift b/ios/MullvadREST/Transport/Socks5/Socks5Error.swift
index 8be764cbb1..62e7c4711d 100644
--- a/ios/MullvadREST/Transport/Socks5/Socks5Error.swift
+++ b/ios/MullvadREST/Transport/Socks5/Socks5Error.swift
@@ -9,7 +9,7 @@ import Foundation
import Network
/// The errors returned by objects implementing socks proxy.
-public enum Socks5Error: Error {
+public enum Socks5Error: Error, Sendable {
/// Unexpected end of stream.
case unexpectedEndOfStream
diff --git a/ios/MullvadREST/Transport/Socks5/Socks5ForwardingProxy.swift b/ios/MullvadREST/Transport/Socks5/Socks5ForwardingProxy.swift
index 29a1b2f70a..503e033ff7 100644
--- a/ios/MullvadREST/Transport/Socks5/Socks5ForwardingProxy.swift
+++ b/ios/MullvadREST/Transport/Socks5/Socks5ForwardingProxy.swift
@@ -19,7 +19,7 @@ import Network
Refer to RFC1928 for more info on socks5: <https://datatracker.ietf.org/doc/html/rfc1928>
*/
-public final class Socks5ForwardingProxy {
+public final class Socks5ForwardingProxy: Sendable {
/// Socks proxy endpoint.
public let socksProxyEndpoint: NWEndpoint
@@ -70,7 +70,7 @@ public final class Socks5ForwardingProxy {
- Parameter completion: completion handler that is called once the TCP listener is ready in the first time or failed before moving to the ready state.
Invoked on main queue.
*/
- public func start(completion: @escaping (Error?) -> Void) {
+ public func start(completion: @escaping @Sendable (Error?) -> Void) {
queue.async {
self.startListener { error in
DispatchQueue.main.async {
@@ -85,7 +85,7 @@ public final class Socks5ForwardingProxy {
- Parameter completion: completion handler that's called immediately after cancelling the TCP listener. Invoked on main queue.
*/
- public func stop(completion: (() -> Void)? = nil) {
+ public func stop(completion: (@Sendable () -> Void)? = nil) {
queue.async {
self.stopInner()
@@ -100,7 +100,7 @@ public final class Socks5ForwardingProxy {
- Parameter errorHandler: an error handler block. Invoked on main queue.
*/
- public func setErrorHandler(_ errorHandler: ((Error) -> Void)?) {
+ public func setErrorHandler(_ errorHandler: (@Sendable (Error) -> Void)?) {
queue.async {
self.errorHandler = errorHandler
}
@@ -108,7 +108,7 @@ public final class Socks5ForwardingProxy {
// MARK: - Private
- private enum State {
+ private enum State: @unchecked Sendable {
/// Proxy is starting up.
case starting(listener: NWListener, completion: (Error?) -> Void)
@@ -120,15 +120,15 @@ public final class Socks5ForwardingProxy {
}
private let queue = DispatchQueue(label: "Socks5ForwardingProxy-queue")
- private var state: State = .stopped
- private var errorHandler: ((Error) -> Void)?
+ nonisolated(unsafe) private var state: State = .stopped
+ nonisolated(unsafe) private var errorHandler: (@Sendable (Error) -> Void)?
/**
Start TCP listener.
- Parameter completion: completion handler that is called once the TCP listener is ready or failed.
*/
- private func startListener(completion: @escaping (Error?) -> Void) {
+ private func startListener(completion: @escaping @Sendable (Error?) -> Void) {
switch state {
case .started:
completion(nil)
diff --git a/ios/MullvadREST/Transport/Socks5/Socks5HandshakeNegotiation.swift b/ios/MullvadREST/Transport/Socks5/Socks5HandshakeNegotiation.swift
index 7b1059de1a..a06eecb0a6 100644
--- a/ios/MullvadREST/Transport/Socks5/Socks5HandshakeNegotiation.swift
+++ b/ios/MullvadREST/Transport/Socks5/Socks5HandshakeNegotiation.swift
@@ -9,11 +9,11 @@ import Foundation
import Network
/// The object handling a handshake negotiation with socks proxy.
-struct Socks5HandshakeNegotiation {
+struct Socks5HandshakeNegotiation: Sendable {
let connection: NWConnection
let handshake: Socks5Handshake
- let onComplete: (Socks5HandshakeReply) -> Void
- let onFailure: (Error) -> Void
+ let onComplete: @Sendable (Socks5HandshakeReply) -> Void
+ let onFailure: @Sendable (Error) -> Void
func perform() {
connection.send(content: handshake.rawData, completion: .contentProcessed { [self] error in
diff --git a/ios/MullvadREST/Transport/Socks5/Socks5StatusCode.swift b/ios/MullvadREST/Transport/Socks5/Socks5StatusCode.swift
index 9832c156a1..6c2a5f7995 100644
--- a/ios/MullvadREST/Transport/Socks5/Socks5StatusCode.swift
+++ b/ios/MullvadREST/Transport/Socks5/Socks5StatusCode.swift
@@ -8,7 +8,7 @@
import Foundation
/// Status code used in socks protocol.
-public enum Socks5StatusCode: UInt8 {
+public enum Socks5StatusCode: UInt8, Sendable {
case succeeded = 0x00
case failure = 0x01
case connectionNotAllowedByRuleset = 0x02
diff --git a/ios/MullvadREST/Transport/Socks5/URLSessionSocks5Transport.swift b/ios/MullvadREST/Transport/Socks5/URLSessionSocks5Transport.swift
index 6c3a67159b..feefa588a4 100644
--- a/ios/MullvadREST/Transport/Socks5/URLSessionSocks5Transport.swift
+++ b/ios/MullvadREST/Transport/Socks5/URLSessionSocks5Transport.swift
@@ -11,7 +11,7 @@ import MullvadLogging
import MullvadTypes
/// Transport that passes URL requests over the local socks forwarding proxy.
-public class URLSessionSocks5Transport: RESTTransport {
+public final class URLSessionSocks5Transport: RESTTransport, Sendable {
/// Socks5 forwarding proxy.
private let socksProxy: Socks5ForwardingProxy
@@ -25,7 +25,7 @@ public class URLSessionSocks5Transport: RESTTransport {
"socks5-url-session"
}
- private let logger = Logger(label: "URLSessionSocks5Transport")
+ nonisolated(unsafe) private let logger = Logger(label: "URLSessionSocks5Transport")
/**
Instantiates new socks5 transport.
@@ -57,7 +57,7 @@ public class URLSessionSocks5Transport: RESTTransport {
public func sendRequest(
_ request: URLRequest,
- completion: @escaping (Data?, URLResponse?, Error?) -> Void
+ completion: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void
) -> Cancellable {
// Listen port should be set when socks proxy is ready. Otherwise start proxy and only then start the data task.
if let localPort = socksProxy.listenPort {
@@ -70,9 +70,9 @@ public class URLSessionSocks5Transport: RESTTransport {
/// Starts socks proxy then executes the data task.
private func sendDeferred(
request: URLRequest,
- completion: @escaping (Data?, URLResponse?, Error?) -> Void
+ completion: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void
) -> Cancellable {
- let chain = CancellableChain()
+ nonisolated(unsafe) let chain = CancellableChain()
socksProxy.start { [weak self, weak socksProxy] error in
if let error {
@@ -94,7 +94,7 @@ public class URLSessionSocks5Transport: RESTTransport {
private func startDataTask(
request: URLRequest,
localPort: UInt16,
- completion: @escaping (Data?, URLResponse?, Error?) -> Void
+ completion: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void
) -> Cancellable {
// Copy the URL request and rewrite the host and port to point to the socks5 forwarding proxy instance
var newRequest = request
diff --git a/ios/MullvadREST/Transport/TransportProvider.swift b/ios/MullvadREST/Transport/TransportProvider.swift
index b511bdfa6b..b92878a255 100644
--- a/ios/MullvadREST/Transport/TransportProvider.swift
+++ b/ios/MullvadREST/Transport/TransportProvider.swift
@@ -10,12 +10,12 @@ import Foundation
import Logging
import MullvadTypes
-public final class TransportProvider: RESTTransportProvider {
+public final class TransportProvider: RESTTransportProvider, Sendable {
private let urlSessionTransport: URLSessionTransport
private let addressCache: REST.AddressCache
- private var transportStrategy: TransportStrategy
- private var currentTransport: RESTTransport?
- private var currentTransportType: TransportStrategy.Transport
+ nonisolated(unsafe) private var transportStrategy: TransportStrategy
+ nonisolated(unsafe) private var currentTransport: RESTTransport?
+ nonisolated(unsafe) private var currentTransportType: TransportStrategy.Transport
private let parallelRequestsMutex = NSLock()
private let encryptedDNSTransport: RESTTransport
@@ -123,7 +123,7 @@ private extension URLError {
/// Interstitial implementation of `RESTTransport` that intercepts the completion of the wrapped transport.
private struct TransportWrapper: RESTTransport {
let wrapped: RESTTransport
- let onComplete: (Error?) -> Void
+ let onComplete: @Sendable (Error?) -> Void
var name: String {
return wrapped.name
@@ -131,7 +131,7 @@ private struct TransportWrapper: RESTTransport {
func sendRequest(
_ request: URLRequest,
- completion: @escaping (Data?, URLResponse?, Error?) -> Void
+ completion: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void
) -> Cancellable {
return wrapped.sendRequest(request) { data, response, error in
onComplete(error)
diff --git a/ios/MullvadREST/Transport/TransportStrategy.swift b/ios/MullvadREST/Transport/TransportStrategy.swift
index 394e7cac40..00037786d7 100644
--- a/ios/MullvadREST/Transport/TransportStrategy.swift
+++ b/ios/MullvadREST/Transport/TransportStrategy.swift
@@ -11,7 +11,7 @@ import Logging
import MullvadSettings
import MullvadTypes
-public struct TransportStrategy: Equatable {
+public struct TransportStrategy: Equatable, Sendable {
/// The different transports suggested by the strategy
public enum Transport: Equatable {
/// Connecting a direct connection
diff --git a/ios/MullvadRESTTests/Mocks/AnyTransport.swift b/ios/MullvadRESTTests/Mocks/AnyTransport.swift
index b21a72b33f..7d5ff242bd 100644
--- a/ios/MullvadRESTTests/Mocks/AnyTransport.swift
+++ b/ios/MullvadRESTTests/Mocks/AnyTransport.swift
@@ -6,12 +6,12 @@
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//
-import Foundation
+@preconcurrency import Foundation
@testable import MullvadREST
import MullvadTypes
/// Mock implementation of REST transport that can be used to handle requests without doing any actual networking.
-class AnyTransport: RESTTransport {
+class AnyTransport: RESTTransport, @unchecked Sendable {
typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void
private let handleRequest: () -> AnyResponse
@@ -19,7 +19,7 @@ class AnyTransport: RESTTransport {
private let completionLock = NSLock()
private var completionHandlers: [UUID: CompletionHandler] = [:]
- init(block: @escaping () -> AnyResponse) {
+ init(block: @escaping @Sendable () -> AnyResponse) {
handleRequest = block
}
@@ -29,7 +29,7 @@ class AnyTransport: RESTTransport {
func sendRequest(
_ request: URLRequest,
- completion: @escaping (Data?, URLResponse?, Error?) -> Void
+ completion: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void
) -> Cancellable {
let response = handleRequest()
let id = storeCompletion(completionHandler: completion)
diff --git a/ios/MullvadRESTTests/Mocks/RESTTransportStub.swift b/ios/MullvadRESTTests/Mocks/RESTTransportStub.swift
index 5e0915554b..2b7cee7711 100644
--- a/ios/MullvadRESTTests/Mocks/RESTTransportStub.swift
+++ b/ios/MullvadRESTTests/Mocks/RESTTransportStub.swift
@@ -19,7 +19,7 @@ struct RESTTransportStub: RESTTransport {
func sendRequest(
_ request: URLRequest,
- completion: @escaping (Data?, URLResponse?, Error?) -> Void
+ completion: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void
) -> Cancellable {
completion(data, response, error)
return AnyCancellable()
diff --git a/ios/MullvadRESTTests/Mocks/TimeServerProxy.swift b/ios/MullvadRESTTests/Mocks/TimeServerProxy.swift
index 76f8da18a8..2d3093e60e 100644
--- a/ios/MullvadRESTTests/Mocks/TimeServerProxy.swift
+++ b/ios/MullvadRESTTests/Mocks/TimeServerProxy.swift
@@ -10,7 +10,7 @@ import Foundation
@testable import MullvadREST
/// Simple API proxy used for testing purposes.
-final class TimeServerProxy: REST.Proxy<REST.ProxyConfiguration> {
+final class TimeServerProxy: REST.Proxy<REST.ProxyConfiguration>, @unchecked Sendable {
init(configuration: REST.ProxyConfiguration) {
super.init(
name: "TimeServerProxy",
diff --git a/ios/MullvadRESTTests/RequestExecutorTests.swift b/ios/MullvadRESTTests/RequestExecutorTests.swift
index 644e6bf247..d0bcdc78d7 100644
--- a/ios/MullvadRESTTests/RequestExecutorTests.swift
+++ b/ios/MullvadRESTTests/RequestExecutorTests.swift
@@ -11,9 +11,10 @@
@testable import MullvadTypes
import XCTest
+@MainActor
final class RequestExecutorTests: XCTestCase {
let addressCache = REST.AddressCache(canWriteToCache: false, fileCache: MemoryCache())
- var timerServerProxy: TimeServerProxy!
+ nonisolated(unsafe) var timerServerProxy: TimeServerProxy!
override func setUp() {
super.setUp()
diff --git a/ios/MullvadRustRuntime/ShadowSocksProxy.swift b/ios/MullvadRustRuntime/ShadowSocksProxy.swift
index cd83f2129a..19f07f1a42 100644
--- a/ios/MullvadRustRuntime/ShadowSocksProxy.swift
+++ b/ios/MullvadRustRuntime/ShadowSocksProxy.swift
@@ -11,7 +11,7 @@ import MullvadRustRuntimeProxy
import Network
/// A Swift wrapper around a Rust implementation of Shadowsocks proxy instance
-public class ShadowsocksProxy {
+public class ShadowsocksProxy: @unchecked Sendable {
private var proxyConfig: ProxyHandle
private let forwardAddress: IPAddress
private let forwardPort: UInt16
diff --git a/ios/MullvadRustRuntimeTests/UnsafeListener.swift b/ios/MullvadRustRuntimeTests/UnsafeListener.swift
index 2ccf0c1aaf..80ab97289a 100644
--- a/ios/MullvadRustRuntimeTests/UnsafeListener.swift
+++ b/ios/MullvadRustRuntimeTests/UnsafeListener.swift
@@ -9,7 +9,7 @@
import Network
/// > Warning: Do not use this implementation in production code. See the warning in `start()`.
-class UnsafeListener<T: Connection> {
+class UnsafeListener<T: Connection>: @unchecked Sendable {
private let dispatchQueue = DispatchQueue(label: "com.test.unsafeListener")
private let listener: NWListener
diff --git a/ios/MullvadSettings/AccessMethodRepository.swift b/ios/MullvadSettings/AccessMethodRepository.swift
index 74591bcbfe..652e7d7488 100644
--- a/ios/MullvadSettings/AccessMethodRepository.swift
+++ b/ios/MullvadSettings/AccessMethodRepository.swift
@@ -11,7 +11,7 @@ import Foundation
import MullvadLogging
import MullvadTypes
-public class AccessMethodRepository: AccessMethodRepositoryProtocol {
+public class AccessMethodRepository: AccessMethodRepositoryProtocol, @unchecked Sendable {
public static let directId = UUID(uuidString: "C9DB7457-2A55-42C3-A926-C07F82131994")!
public static let bridgeId = UUID(uuidString: "8586E75A-CA7B-4432-B70D-EE65F3F95084")!
public static let encryptedDNSId = UUID(uuidString: "831CB1F8-1829-42DD-B9DC-82902F298EC0")!
diff --git a/ios/MullvadSettings/AccessMethodRepositoryProtocol.swift b/ios/MullvadSettings/AccessMethodRepositoryProtocol.swift
index 0239919f4c..21d8067790 100644
--- a/ios/MullvadSettings/AccessMethodRepositoryProtocol.swift
+++ b/ios/MullvadSettings/AccessMethodRepositoryProtocol.swift
@@ -8,7 +8,7 @@
import Combine
-public protocol AccessMethodRepositoryDataSource {
+public protocol AccessMethodRepositoryDataSource: Sendable {
/// Publisher that propagates a snapshot of all access methods upon modifications.
var accessMethodsPublisher: AnyPublisher<[PersistentAccessMethod], Never> { get }
diff --git a/ios/MullvadSettings/DAITASettings.swift b/ios/MullvadSettings/DAITASettings.swift
index 7fd3170bf9..ca0aa059b3 100644
--- a/ios/MullvadSettings/DAITASettings.swift
+++ b/ios/MullvadSettings/DAITASettings.swift
@@ -9,7 +9,7 @@
import Foundation
/// Whether DAITA is enabled.
-public enum DAITAState: Codable {
+public enum DAITAState: Codable, Sendable {
case on
case off
@@ -24,7 +24,7 @@ public enum DAITAState: Codable {
}
/// Whether "direct only" is enabled, meaning no automatic routing to DAITA relays.
-public enum DirectOnlyState: Codable {
+public enum DirectOnlyState: Codable, Sendable {
case on
case off
@@ -43,7 +43,7 @@ public enum DAITASettingsCompatibilityError {
case singlehop, multihop
}
-public struct DAITASettings: Codable, Equatable {
+public struct DAITASettings: Codable, Equatable, Sendable {
@available(*, deprecated, renamed: "daitaState")
public let state: DAITAState = .off
diff --git a/ios/MullvadSettings/DNSSettings.swift b/ios/MullvadSettings/DNSSettings.swift
index 4b7a3ae7cb..f515b0c601 100644
--- a/ios/MullvadSettings/DNSSettings.swift
+++ b/ios/MullvadSettings/DNSSettings.swift
@@ -11,7 +11,7 @@ import MullvadTypes
import Network
/// A struct describing Mullvad DNS blocking options.
-public struct DNSBlockingOptions: OptionSet, Codable {
+public struct DNSBlockingOptions: OptionSet, Codable, Sendable {
public let rawValue: UInt32
public static let blockAdvertising = DNSBlockingOptions(rawValue: 1 << 0)
@@ -48,7 +48,7 @@ public struct DNSBlockingOptions: OptionSet, Codable {
}
/// A struct that holds DNS settings.
-public struct DNSSettings: Codable, Equatable {
+public struct DNSSettings: Codable, Equatable, Sendable {
/// Maximum number of allowed DNS domains.
public static let maxAllowedCustomDNSDomains = 3
diff --git a/ios/MullvadSettings/DeviceState.swift b/ios/MullvadSettings/DeviceState.swift
index 052511d328..37b14927a1 100644
--- a/ios/MullvadSettings/DeviceState.swift
+++ b/ios/MullvadSettings/DeviceState.swift
@@ -8,7 +8,7 @@
import Foundation
-public enum DeviceState: Codable, Equatable {
+public enum DeviceState: Codable, Equatable, Sendable {
case loggedIn(StoredAccountData, StoredDeviceData)
case loggedOut
case revoked
diff --git a/ios/MullvadSettings/IPOverride.swift b/ios/MullvadSettings/IPOverride.swift
index c25fdf4604..1d3cec101c 100644
--- a/ios/MullvadSettings/IPOverride.swift
+++ b/ios/MullvadSettings/IPOverride.swift
@@ -8,7 +8,7 @@
import Network
-public struct RelayOverrides: Codable {
+public struct RelayOverrides: Codable, Sendable {
public let overrides: [IPOverride]
private enum CodingKeys: String, CodingKey {
@@ -20,7 +20,7 @@ public struct IPOverrideFormatError: LocalizedError {
public let errorDescription: String?
}
-public struct IPOverride: Codable, Equatable {
+public struct IPOverride: Codable, Equatable, Sendable {
public let hostname: String
public var ipv4Address: IPv4Address?
public var ipv6Address: IPv6Address?
diff --git a/ios/MullvadSettings/IPOverrideRepository.swift b/ios/MullvadSettings/IPOverrideRepository.swift
index 441ff6c35e..234ffdfbfb 100644
--- a/ios/MullvadSettings/IPOverrideRepository.swift
+++ b/ios/MullvadSettings/IPOverrideRepository.swift
@@ -6,10 +6,10 @@
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//
-import Combine
+@preconcurrency import Combine
import MullvadLogging
-public protocol IPOverrideRepositoryProtocol {
+public protocol IPOverrideRepositoryProtocol: Sendable {
var overridesPublisher: AnyPublisher<[IPOverride], Never> { get }
func add(_ overrides: [IPOverride])
func fetchAll() -> [IPOverride]
@@ -17,13 +17,13 @@ public protocol IPOverrideRepositoryProtocol {
func parse(data: Data) throws -> [IPOverride]
}
-public class IPOverrideRepository: IPOverrideRepositoryProtocol {
+public final class IPOverrideRepository: IPOverrideRepositoryProtocol {
private let overridesSubject: CurrentValueSubject<[IPOverride], Never> = .init([])
public var overridesPublisher: AnyPublisher<[IPOverride], Never> {
overridesSubject.eraseToAnyPublisher()
}
- private let logger = Logger(label: "IPOverrideRepository")
+ nonisolated(unsafe) private let logger = Logger(label: "IPOverrideRepository")
private let readWriteLock = NSLock()
public init() {}
diff --git a/ios/MullvadSettings/KeychainSettingsStore.swift b/ios/MullvadSettings/KeychainSettingsStore.swift
index ba0612de0d..30b054d100 100644
--- a/ios/MullvadSettings/KeychainSettingsStore.swift
+++ b/ios/MullvadSettings/KeychainSettingsStore.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadTypes
import Security
-public class KeychainSettingsStore: SettingsStore {
+final public class KeychainSettingsStore: SettingsStore, Sendable {
public let serviceName: String
public let accessGroup: String
diff --git a/ios/MullvadSettings/Migration.swift b/ios/MullvadSettings/Migration.swift
index 0d9678deb5..e8cc6a0564 100644
--- a/ios/MullvadSettings/Migration.swift
+++ b/ios/MullvadSettings/Migration.swift
@@ -12,6 +12,6 @@ public protocol Migration {
func migrate(
with store: SettingsStore,
parser: SettingsParser,
- completion: @escaping (Error?) -> Void
+ completion: @escaping @Sendable (Error?) -> Void
)
}
diff --git a/ios/MullvadSettings/MigrationManager.swift b/ios/MullvadSettings/MigrationManager.swift
index c13fd9baa5..c16209f4d7 100644
--- a/ios/MullvadSettings/MigrationManager.swift
+++ b/ios/MullvadSettings/MigrationManager.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadLogging
import MullvadTypes
-public enum SettingsMigrationResult {
+public enum SettingsMigrationResult: Sendable {
/// Nothing to migrate.
case nothing
@@ -42,7 +42,7 @@ public struct MigrationManager {
/// - migrationCompleted: Completion handler called with a migration result.
public func migrateSettings(
store: SettingsStore,
- migrationCompleted: @escaping (SettingsMigrationResult) -> Void
+ migrationCompleted: @escaping @Sendable (SettingsMigrationResult) -> Void
) {
let fileCoordinator = NSFileCoordinator(filePresenter: nil)
var error: NSError?
@@ -79,7 +79,7 @@ public struct MigrationManager {
private func upgradeSettingsToLatestVersion(
store: SettingsStore,
- migrationCompleted: @escaping (SettingsMigrationResult) -> Void
+ migrationCompleted: @escaping @Sendable (SettingsMigrationResult) -> Void
) throws {
let parser = SettingsParser(decoder: JSONDecoder(), encoder: JSONEncoder())
let settingsData = try store.read(key: SettingsKey.settings)
diff --git a/ios/MullvadSettings/MultihopSettings.swift b/ios/MullvadSettings/MultihopSettings.swift
index a9a17f2b43..1308f24535 100644
--- a/ios/MullvadSettings/MultihopSettings.swift
+++ b/ios/MullvadSettings/MultihopSettings.swift
@@ -9,8 +9,8 @@
import Foundation
import MullvadTypes
-/// Whether multihop is enabled.
-public enum MultihopState: Codable {
+/// Whether Multi-hop is enabled
+public enum MultihopState: Codable, Sendable {
case on
case off
diff --git a/ios/MullvadSettings/QuantumResistanceSettings.swift b/ios/MullvadSettings/QuantumResistanceSettings.swift
index 956b2fd0de..2ea3c2ee53 100644
--- a/ios/MullvadSettings/QuantumResistanceSettings.swift
+++ b/ios/MullvadSettings/QuantumResistanceSettings.swift
@@ -8,7 +8,7 @@
import Foundation
-public enum TunnelQuantumResistance: Codable {
+public enum TunnelQuantumResistance: Codable, Sendable {
case automatic
case on
case off
diff --git a/ios/MullvadSettings/SettingsManager.swift b/ios/MullvadSettings/SettingsManager.swift
index d414d7ed0a..7600b22ad3 100644
--- a/ios/MullvadSettings/SettingsManager.swift
+++ b/ios/MullvadSettings/SettingsManager.swift
@@ -15,16 +15,16 @@ private let accountTokenKey = "accountToken"
private let accountExpiryKey = "accountExpiry"
public enum SettingsManager {
- private static let logger = Logger(label: "SettingsManager")
+ nonisolated(unsafe) private static let logger = Logger(label: "SettingsManager")
#if DEBUG
- private static var _store = KeychainSettingsStore(
+ nonisolated(unsafe) private static var _store = KeychainSettingsStore(
serviceName: keychainServiceName,
accessGroup: ApplicationConfiguration.securityGroupIdentifier
)
/// Alternative store used for tests.
- internal static var unitTestStore: SettingsStore?
+ nonisolated(unsafe) internal static var unitTestStore: SettingsStore?
public static var store: SettingsStore {
if let unitTestStore { return unitTestStore }
diff --git a/ios/MullvadSettings/SettingsStore.swift b/ios/MullvadSettings/SettingsStore.swift
index 2901ae2bb3..8353d8711a 100644
--- a/ios/MullvadSettings/SettingsStore.swift
+++ b/ios/MullvadSettings/SettingsStore.swift
@@ -8,7 +8,7 @@
import Foundation
-public enum SettingsKey: String, CaseIterable {
+public enum SettingsKey: String, CaseIterable, Sendable {
case settings = "Settings"
case deviceState = "DeviceState"
case apiAccessMethods = "ApiAccessMethods"
@@ -18,7 +18,7 @@ public enum SettingsKey: String, CaseIterable {
case shouldWipeSettings = "ShouldWipeSettings"
}
-public protocol SettingsStore {
+public protocol SettingsStore: Sendable {
func read(key: SettingsKey) throws -> Data
func write(_ data: Data, for key: SettingsKey) throws
func delete(key: SettingsKey) throws
diff --git a/ios/MullvadSettings/ShadowsocksCipherOptions.swift b/ios/MullvadSettings/ShadowsocksCipherOptions.swift
index 6d1bcab7cb..c7703b5926 100644
--- a/ios/MullvadSettings/ShadowsocksCipherOptions.swift
+++ b/ios/MullvadSettings/ShadowsocksCipherOptions.swift
@@ -8,7 +8,7 @@
import Foundation
-public struct ShadowsocksCipherOptions: RawRepresentable, Codable, Hashable {
+public struct ShadowsocksCipherOptions: RawRepresentable, Codable, Hashable, Sendable {
public let rawValue: CipherIdentifiers
public init(rawValue: CipherIdentifiers) {
@@ -22,7 +22,7 @@ public struct ShadowsocksCipherOptions: RawRepresentable, Codable, Hashable {
public static let all = CipherIdentifiers.allCases.map { ShadowsocksCipherOptions(rawValue: $0) }
}
-public enum CipherIdentifiers: String, CaseIterable, CustomStringConvertible, Codable {
+public enum CipherIdentifiers: String, CaseIterable, CustomStringConvertible, Codable, Sendable {
// Stream ciphers.
case CFB_AES128 = "aes-128-cfb"
case CFB1_AES128 = "aes-128-cfb1"
diff --git a/ios/MullvadSettings/StoredAccountData.swift b/ios/MullvadSettings/StoredAccountData.swift
index 276982805c..e54eaeb1df 100644
--- a/ios/MullvadSettings/StoredAccountData.swift
+++ b/ios/MullvadSettings/StoredAccountData.swift
@@ -8,7 +8,7 @@
import Foundation
-public struct StoredAccountData: Codable, Equatable {
+public struct StoredAccountData: Codable, Equatable, Sendable {
/// Account identifier.
public var identifier: String
diff --git a/ios/MullvadSettings/StoredDeviceData.swift b/ios/MullvadSettings/StoredDeviceData.swift
index a0d784af61..dcb674da32 100644
--- a/ios/MullvadSettings/StoredDeviceData.swift
+++ b/ios/MullvadSettings/StoredDeviceData.swift
@@ -8,9 +8,9 @@
import Foundation
import MullvadTypes
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
-public struct StoredDeviceData: Codable, Equatable {
+public struct StoredDeviceData: Codable, Equatable, Sendable {
/// Device creation date.
public var creationDate: Date
diff --git a/ios/MullvadSettings/StoredWgKeyData.swift b/ios/MullvadSettings/StoredWgKeyData.swift
index df07e0e8c9..57915098d4 100644
--- a/ios/MullvadSettings/StoredWgKeyData.swift
+++ b/ios/MullvadSettings/StoredWgKeyData.swift
@@ -7,9 +7,9 @@
//
import Foundation
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
-public struct StoredWgKeyData: Codable, Equatable {
+public struct StoredWgKeyData: Codable, Equatable, Sendable {
/// Private key creation date.
public var creationDate: Date
diff --git a/ios/MullvadSettings/TunnelSettings.swift b/ios/MullvadSettings/TunnelSettings.swift
index 3298e84356..f530c43762 100644
--- a/ios/MullvadSettings/TunnelSettings.swift
+++ b/ios/MullvadSettings/TunnelSettings.swift
@@ -12,12 +12,12 @@ import Foundation
public typealias LatestTunnelSettings = TunnelSettingsV6
/// Protocol all TunnelSettings must adhere to, for upgrade purposes.
-public protocol TunnelSettings: Codable {
+public protocol TunnelSettings: Codable, Sendable {
func upgradeToNextVersion() -> any TunnelSettings
}
/// Settings and device state schema versions.
-public enum SchemaVersion: Int, Equatable {
+public enum SchemaVersion: Int, Equatable, Sendable {
/// Legacy settings format, stored as `TunnelSettingsV1`.
case v1 = 1
diff --git a/ios/MullvadSettings/TunnelSettingsPropagator.swift b/ios/MullvadSettings/TunnelSettingsPropagator.swift
index 3f0a3395ad..b38ffb48fa 100644
--- a/ios/MullvadSettings/TunnelSettingsPropagator.swift
+++ b/ios/MullvadSettings/TunnelSettingsPropagator.swift
@@ -8,7 +8,7 @@
import MullvadTypes
-public protocol SettingsPropagation {
+public protocol SettingsPropagation: Sendable {
typealias SettingsHandler = (LatestTunnelSettings) -> Void
var onNewSettings: SettingsHandler? { get set }
}
@@ -30,7 +30,7 @@ public class SettingsObserverBlock: SettingsObserver {
}
}
-public final class TunnelSettingsListener: SettingsPropagation {
+public final class TunnelSettingsListener: SettingsPropagation, @unchecked Sendable {
public var onNewSettings: SettingsHandler?
public init(onNewSettings: SettingsHandler? = nil) {
@@ -38,10 +38,10 @@ public final class TunnelSettingsListener: SettingsPropagation {
}
}
-public class SettingsUpdater {
+public final class SettingsUpdater: Sendable {
/// Observers.
private let observerList = ObserverList<SettingsObserver>()
- private var listener: SettingsPropagation
+ nonisolated(unsafe) private var listener: SettingsPropagation
public init(listener: SettingsPropagation) {
self.listener = listener
diff --git a/ios/MullvadSettings/TunnelSettingsStrategy.swift b/ios/MullvadSettings/TunnelSettingsStrategy.swift
index 22a3721cc4..a0d399be0d 100644
--- a/ios/MullvadSettings/TunnelSettingsStrategy.swift
+++ b/ios/MullvadSettings/TunnelSettingsStrategy.swift
@@ -7,11 +7,11 @@
//
import Foundation
-public protocol TunnelSettingsStrategyProtocol {
+public protocol TunnelSettingsStrategyProtocol: Sendable {
func shouldReconnectToNewRelay(oldSettings: LatestTunnelSettings, newSettings: LatestTunnelSettings) -> Bool
}
-public struct TunnelSettingsStrategy: TunnelSettingsStrategyProtocol {
+public struct TunnelSettingsStrategy: TunnelSettingsStrategyProtocol, Sendable {
public init() {}
public func shouldReconnectToNewRelay(
oldSettings: LatestTunnelSettings,
diff --git a/ios/MullvadSettings/TunnelSettingsUpdate.swift b/ios/MullvadSettings/TunnelSettingsUpdate.swift
index 6fd333d003..0dbf818baf 100644
--- a/ios/MullvadSettings/TunnelSettingsUpdate.swift
+++ b/ios/MullvadSettings/TunnelSettingsUpdate.swift
@@ -9,7 +9,7 @@
import Foundation
import MullvadTypes
-public enum TunnelSettingsUpdate {
+public enum TunnelSettingsUpdate: Sendable {
case dnsSettings(DNSSettings)
case obfuscation(WireGuardObfuscationSettings)
case relayConstraints(RelayConstraints)
diff --git a/ios/MullvadSettings/TunnelSettingsV1.swift b/ios/MullvadSettings/TunnelSettingsV1.swift
index aaa3c27845..e5a62217d5 100644
--- a/ios/MullvadSettings/TunnelSettingsV1.swift
+++ b/ios/MullvadSettings/TunnelSettingsV1.swift
@@ -22,7 +22,7 @@ public struct TunnelSettingsV1: Codable, Equatable, TunnelSettings {
}
/// A struct that holds a tun interface configuration.
-public struct InterfaceSettings: Codable, Equatable {
+public struct InterfaceSettings: Codable, Equatable, @unchecked Sendable {
public var privateKey: PrivateKeyWithMetadata
public var nextPrivateKey: PrivateKeyWithMetadata?
diff --git a/ios/MullvadSettings/TunnelSettingsV6.swift b/ios/MullvadSettings/TunnelSettingsV6.swift
index 4f81d9549d..7ce6ed1784 100644
--- a/ios/MullvadSettings/TunnelSettingsV6.swift
+++ b/ios/MullvadSettings/TunnelSettingsV6.swift
@@ -9,7 +9,7 @@
import Foundation
import MullvadTypes
-public struct TunnelSettingsV6: Codable, Equatable, TunnelSettings {
+public struct TunnelSettingsV6: Codable, Equatable, TunnelSettings, Sendable {
/// Relay constraints.
public var relayConstraints: RelayConstraints
diff --git a/ios/MullvadSettings/WireGuardObfuscationSettings.swift b/ios/MullvadSettings/WireGuardObfuscationSettings.swift
index c52a637626..01529d7deb 100644
--- a/ios/MullvadSettings/WireGuardObfuscationSettings.swift
+++ b/ios/MullvadSettings/WireGuardObfuscationSettings.swift
@@ -11,7 +11,7 @@ import Foundation
/// Whether obfuscation is enabled and which method is used.
///
/// `.automatic` means an algorithm will decide whether to use obfuscation or not.
-public enum WireGuardObfuscationState: Codable {
+public enum WireGuardObfuscationState: Codable, Sendable {
@available(*, deprecated, renamed: "udpOverTcp")
case on
@@ -52,7 +52,7 @@ public enum WireGuardObfuscationState: Codable {
}
}
-public enum WireGuardObfuscationUdpOverTcpPort: Codable, Equatable, CustomStringConvertible {
+public enum WireGuardObfuscationUdpOverTcpPort: Codable, Equatable, CustomStringConvertible, Sendable {
case automatic
case port80
case port5001
@@ -85,7 +85,7 @@ public enum WireGuardObfuscationUdpOverTcpPort: Codable, Equatable, CustomString
}
}
-public enum WireGuardObfuscationShadowsocksPort: Codable, Equatable, CustomStringConvertible {
+public enum WireGuardObfuscationShadowsocksPort: Codable, Equatable, CustomStringConvertible, Sendable {
case automatic
case custom(UInt16)
@@ -115,7 +115,7 @@ public enum WireGuardObfuscationShadowsocksPort: Codable, Equatable, CustomStrin
// Can't deprecate the whole type since it'll yield a lint warning when decoding
// port in `WireGuardObfuscationSettings`.
-private enum WireGuardObfuscationPort: UInt16, Codable {
+private enum WireGuardObfuscationPort: UInt16, Codable, Sendable {
@available(*, deprecated, message: "Use `udpOverTcpPort` instead")
case automatic = 0
@available(*, deprecated, message: "Use `udpOverTcpPort` instead")
@@ -124,7 +124,7 @@ private enum WireGuardObfuscationPort: UInt16, Codable {
case port5001 = 5001
}
-public struct WireGuardObfuscationSettings: Codable, Equatable {
+public struct WireGuardObfuscationSettings: Codable, Equatable, Sendable {
@available(*, deprecated, message: "Use `udpOverTcpPort` instead")
private var port: WireGuardObfuscationPort = .automatic
diff --git a/ios/MullvadTypes/AnyIPEndpoint.swift b/ios/MullvadTypes/AnyIPEndpoint.swift
index cf294c7028..27b995729d 100644
--- a/ios/MullvadTypes/AnyIPEndpoint.swift
+++ b/ios/MullvadTypes/AnyIPEndpoint.swift
@@ -9,7 +9,7 @@
import Foundation
import protocol Network.IPAddress
-public enum AnyIPEndpoint: Hashable, Equatable, Codable, CustomStringConvertible {
+public enum AnyIPEndpoint: Hashable, Equatable, Codable, CustomStringConvertible, @unchecked Sendable {
case ipv4(IPv4Endpoint)
case ipv6(IPv6Endpoint)
diff --git a/ios/MullvadTypes/Cancellable.swift b/ios/MullvadTypes/Cancellable.swift
index 8f658a6da1..3fa640f198 100644
--- a/ios/MullvadTypes/Cancellable.swift
+++ b/ios/MullvadTypes/Cancellable.swift
@@ -19,7 +19,7 @@ public final class AnyCancellable: Cancellable {
private let block: (() -> Void)?
/// Create cancellation token with block handler.
- public init(block: @escaping () -> Void) {
+ public init(block: @escaping @Sendable () -> Void) {
self.block = block
}
diff --git a/ios/MullvadTypes/IPv4Endpoint.swift b/ios/MullvadTypes/IPv4Endpoint.swift
index c0ed1e4771..2e771aaa33 100644
--- a/ios/MullvadTypes/IPv4Endpoint.swift
+++ b/ios/MullvadTypes/IPv4Endpoint.swift
@@ -9,7 +9,7 @@
import Foundation
import Network
-public struct IPv4Endpoint: Hashable, Equatable, Codable, CustomStringConvertible {
+public struct IPv4Endpoint: Hashable, Equatable, Codable, CustomStringConvertible, Sendable {
public let ip: IPv4Address
public let port: UInt16
diff --git a/ios/MullvadTypes/IPv6Endpoint.swift b/ios/MullvadTypes/IPv6Endpoint.swift
index 5dd56a4982..e65ce0f225 100644
--- a/ios/MullvadTypes/IPv6Endpoint.swift
+++ b/ios/MullvadTypes/IPv6Endpoint.swift
@@ -9,7 +9,7 @@
import Foundation
import Network
-public struct IPv6Endpoint: Hashable, Equatable, Codable, CustomStringConvertible {
+public struct IPv6Endpoint: Hashable, Equatable, Codable, CustomStringConvertible, Sendable {
public let ip: IPv6Address
public let port: UInt16
diff --git a/ios/MullvadTypes/Location.swift b/ios/MullvadTypes/Location.swift
index 13cf935425..5084541220 100644
--- a/ios/MullvadTypes/Location.swift
+++ b/ios/MullvadTypes/Location.swift
@@ -9,7 +9,7 @@
import CoreLocation
import Foundation
-public struct Location: Codable, Equatable {
+public struct Location: Codable, Equatable, Sendable {
public var country: String
public var countryCode: String
public var city: String
diff --git a/ios/MullvadTypes/MullvadEndpoint.swift b/ios/MullvadTypes/MullvadEndpoint.swift
index 1361df2e46..0702a5ee80 100644
--- a/ios/MullvadTypes/MullvadEndpoint.swift
+++ b/ios/MullvadTypes/MullvadEndpoint.swift
@@ -10,7 +10,7 @@ import Foundation
import Network
/// Contains server data needed to connect to a single mullvad endpoint.
-public struct MullvadEndpoint: Equatable, Codable {
+public struct MullvadEndpoint: Equatable, Codable, Sendable {
public let ipv4Relay: IPv4Endpoint
public let ipv6Relay: IPv6Endpoint?
public let ipv4Gateway: IPv4Address
diff --git a/ios/MullvadTypes/ObserverList.swift b/ios/MullvadTypes/ObserverList.swift
index 2f26085c44..fa659fb443 100644
--- a/ios/MullvadTypes/ObserverList.swift
+++ b/ios/MullvadTypes/ObserverList.swift
@@ -8,12 +8,12 @@
import Foundation
-public struct WeakBox<T> {
+public struct WeakBox<T>: Sendable {
public var value: T? {
valueProvider()
}
- private let valueProvider: () -> T?
+ nonisolated(unsafe) private let valueProvider: () -> T?
public init(_ value: T) {
let reference = value as AnyObject
@@ -28,9 +28,9 @@ public struct WeakBox<T> {
}
}
-final public class ObserverList<T> {
+final public class ObserverList<T>: Sendable {
private let lock = NSLock()
- private var observers = [WeakBox<T>]()
+ nonisolated(unsafe) private var observers = [WeakBox<T>]()
public init() {}
diff --git a/ios/MullvadTypes/Promise.swift b/ios/MullvadTypes/Promise.swift
index ab80c31695..7a4f248209 100644
--- a/ios/MullvadTypes/Promise.swift
+++ b/ios/MullvadTypes/Promise.swift
@@ -8,7 +8,7 @@
import Foundation
-public final class Promise<Success, Failure: Error> {
+public final class Promise<Success, Failure: Error>: @unchecked Sendable {
public typealias Result = Swift.Result<Success, Failure>
private let nslock = NSLock()
diff --git a/ios/MullvadTypes/Protocols/DaitaV2Parameters.swift b/ios/MullvadTypes/Protocols/DaitaV2Parameters.swift
index 53ab61ada4..adb3a14f28 100644
--- a/ios/MullvadTypes/Protocols/DaitaV2Parameters.swift
+++ b/ios/MullvadTypes/Protocols/DaitaV2Parameters.swift
@@ -8,7 +8,7 @@
import Foundation
-public struct DaitaV2Parameters: Equatable {
+public struct DaitaV2Parameters: Equatable, Sendable {
public let machines: String
public let maximumEvents: UInt32
public let maximumActions: UInt32
diff --git a/ios/MullvadTypes/Protocols/NetworkExtension+HiddenSymbols.swift b/ios/MullvadTypes/Protocols/NetworkExtension+HiddenSymbols.swift
new file mode 100644
index 0000000000..a43aa37b0e
--- /dev/null
+++ b/ios/MullvadTypes/Protocols/NetworkExtension+HiddenSymbols.swift
@@ -0,0 +1,17 @@
+//
+// NetworkExtension+HiddenSymbols.swift
+// MullvadTypes
+//
+// Created by Marco Nikic on 2024-12-04.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import NetworkExtension
+
+#if swift(>=6)
+#if compiler(>=6)
+public typealias NWTCPConnection = __NWTCPConnection
+public typealias NWHostEndpoint = __NWHostEndpoint
+#endif
+#endif
diff --git a/ios/MullvadTypes/RESTTypes.swift b/ios/MullvadTypes/RESTTypes.swift
index bb5c270022..f5fe9bdd6e 100644
--- a/ios/MullvadTypes/RESTTypes.swift
+++ b/ios/MullvadTypes/RESTTypes.swift
@@ -7,9 +7,9 @@
//
import Foundation
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
-public struct Account: Codable, Equatable {
+public struct Account: Codable, Equatable, Sendable {
public let id: String
public let expiry: Date
public let maxDevices: Int
@@ -23,7 +23,7 @@ public struct Account: Codable, Equatable {
}
}
-public struct Device: Codable, Equatable {
+public struct Device: Codable, Equatable, Sendable {
public let id: String
public let name: String
public let pubkey: PublicKey
diff --git a/ios/MullvadTypes/RelayConstraints.swift b/ios/MullvadTypes/RelayConstraints.swift
index b6396767d8..391b5e310b 100644
--- a/ios/MullvadTypes/RelayConstraints.swift
+++ b/ios/MullvadTypes/RelayConstraints.swift
@@ -8,7 +8,7 @@
import Foundation
-public struct RelayConstraints: Codable, Equatable, CustomDebugStringConvertible {
+public struct RelayConstraints: Codable, Equatable, CustomDebugStringConvertible, @unchecked Sendable {
@available(*, deprecated, renamed: "locations")
private var location: RelayConstraint<RelayLocation> = .only(.country("se"))
diff --git a/ios/MullvadTypes/RelayLocation.swift b/ios/MullvadTypes/RelayLocation.swift
index 279f3cb6bc..b2ef8fa954 100644
--- a/ios/MullvadTypes/RelayLocation.swift
+++ b/ios/MullvadTypes/RelayLocation.swift
@@ -8,7 +8,7 @@
import Foundation
-public enum RelayLocation: Codable, Hashable, CustomDebugStringConvertible {
+public enum RelayLocation: Codable, Hashable, CustomDebugStringConvertible, Sendable {
case country(String)
case city(String, String)
case hostname(String, String, String)
@@ -107,7 +107,7 @@ public enum RelayLocation: Codable, Hashable, CustomDebugStringConvertible {
}
}
-public struct UserSelectedRelays: Codable, Equatable {
+public struct UserSelectedRelays: Codable, Equatable, Sendable {
public let locations: [RelayLocation]
public let customListSelection: CustomListSelection?
@@ -118,7 +118,7 @@ public struct UserSelectedRelays: Codable, Equatable {
}
extension UserSelectedRelays {
- public struct CustomListSelection: Codable, Equatable {
+ public struct CustomListSelection: Codable, Equatable, Sendable {
/// The ID of the custom list that the selected relays belong to.
public let listId: UUID
/// Whether the selected relays are subnodes or the custom list itself.
diff --git a/ios/MullvadTypes/TransportLayer.swift b/ios/MullvadTypes/TransportLayer.swift
index b4a7e6c3cd..cb25c72490 100644
--- a/ios/MullvadTypes/TransportLayer.swift
+++ b/ios/MullvadTypes/TransportLayer.swift
@@ -8,7 +8,7 @@
import Foundation
-public enum TransportLayer: Codable {
+public enum TransportLayer: Codable, Sendable {
case udp
case tcp
}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index e15a6de7d5..0735c4df91 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -1321,6 +1321,13 @@
remoteGlobalIDString = 58D223D4294C8E5E0029F5F8;
remoteInfo = MullvadTypes;
};
+ A9609B6D2D004D1F0065A3D3 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 58CE5E58224146200008646E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 58FBDA9722A519BC00EB69A3;
+ remoteInfo = WireGuardGoBridge;
+ };
A992DA212C24709F00DE7CE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 58CE5E58224146200008646E /* Project object */;
@@ -4837,6 +4844,7 @@
buildRules = (
);
dependencies = (
+ A9609B6E2D004D1F0065A3D3 /* PBXTargetDependency */,
58D223E9294C8F120029F5F8 /* PBXTargetDependency */,
58D223F8294C8FF00029F5F8 /* PBXTargetDependency */,
06799AD028F98E1D00ACD94E /* PBXTargetDependency */,
@@ -6754,6 +6762,11 @@
target = 58D223D4294C8E5E0029F5F8 /* MullvadTypes */;
targetProxy = A9173C332C36CCFB00F6A08C /* PBXContainerItemProxy */;
};
+ A9609B6E2D004D1F0065A3D3 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 58FBDA9722A519BC00EB69A3 /* WireGuardGoBridge */;
+ targetProxy = A9609B6D2D004D1F0065A3D3 /* PBXContainerItemProxy */;
+ };
A992DA222C24709F00DE7CE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = A992DA1C2C24709F00DE7CE5 /* MullvadRustRuntime */;
@@ -6919,6 +6932,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -6938,6 +6952,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -7048,6 +7063,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -7087,6 +7103,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -7109,6 +7126,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -7129,6 +7147,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -7192,7 +7211,7 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -7250,7 +7269,7 @@
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@@ -7328,6 +7347,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = minimal;
+ SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -7347,6 +7367,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).PacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = minimal;
+ SWIFT_VERSION = 5.0;
};
name = Release;
};
@@ -7448,6 +7469,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSION_INFO_PREFIX = "";
};
@@ -7483,6 +7505,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSION_INFO_PREFIX = "";
};
@@ -7570,7 +7593,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
- PATH = "${PATH}:/opt/homebrew/opt/go@1.19/bin";
+ PATH = "${PATH}:/opt/homebrew/opt/go@1.21/bin";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
@@ -7583,7 +7606,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
- PATH = "${PATH}:/opt/homebrew/opt/go@1.19/bin";
+ PATH = "${PATH}:/opt/homebrew/opt/go@1.21/bin";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
@@ -7894,7 +7917,7 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Staging;
@@ -7938,7 +7961,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
- PATH = "${PATH}:/opt/homebrew/opt/go@1.19/bin";
+ PATH = "${PATH}:/opt/homebrew/opt/go@1.21/bin";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Staging;
@@ -7963,6 +7986,7 @@
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Packet Tunnel Development";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = minimal;
+ SWIFT_VERSION = 5.0;
};
name = Staging;
};
@@ -7981,6 +8005,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Staging;
@@ -8126,6 +8151,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSION_INFO_PREFIX = "";
};
@@ -8198,6 +8224,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -8220,6 +8247,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Staging;
@@ -8424,6 +8452,7 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -8474,6 +8503,7 @@
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -8524,6 +8554,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -8573,6 +8604,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -8600,6 +8632,7 @@
SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -8624,6 +8657,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Staging;
@@ -8648,6 +8682,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -8672,6 +8707,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = MockRelease;
@@ -8728,7 +8764,7 @@
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@@ -8769,7 +8805,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
- PATH = "${PATH}:/opt/homebrew/opt/go@1.19/bin";
+ PATH = "${PATH}:/opt/homebrew/opt/go@1.21/bin";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = MockRelease;
@@ -8793,6 +8829,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Packet Tunnel Development";
SWIFT_STRICT_CONCURRENCY = minimal;
+ SWIFT_VERSION = 5.0;
};
name = MockRelease;
};
@@ -8811,6 +8848,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = MockRelease;
@@ -8956,6 +8994,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSION_INFO_PREFIX = "";
};
@@ -9028,6 +9067,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -9050,6 +9090,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = MockRelease;
diff --git a/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift
index 03e108968a..699022a674 100644
--- a/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift
+++ b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift
@@ -22,7 +22,7 @@ class ProxyConfigurationTester: ProxyConfigurationTesterProtocol {
self.transportProvider = transportProvider
}
- func start(configuration: PersistentProxyConfiguration, completion: @escaping (Error?) -> Void) {
+ func start(configuration: PersistentProxyConfiguration, completion: @escaping @Sendable (Error?) -> Void) {
do {
let transport = try transportProvider.makeTransport(with: configuration)
let request = REST.APIAvailabilityTestRequest(transport: transport)
diff --git a/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift
index b01d817d61..95f6261c65 100644
--- a/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift
+++ b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift
@@ -15,7 +15,7 @@ protocol ProxyConfigurationTesterProtocol {
/// - Parameters:
/// - configuration: a proxy configuration.
/// - completion: a completion handler that receives `nil` upon success, otherwise the underlying error.
- func start(configuration: PersistentProxyConfiguration, completion: @escaping (Error?) -> Void)
+ func start(configuration: PersistentProxyConfiguration, completion: @escaping @Sendable (Error?) -> Void)
/// Cancel testing proxy configuration.
func cancel()
diff --git a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
index 00d8b2fe13..50d052a2b1 100644
--- a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
+++ b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
@@ -12,7 +12,7 @@ import MullvadTypes
import Operations
import UIKit
-final class AddressCacheTracker {
+final class AddressCacheTracker: @unchecked Sendable {
/// Update interval.
private static let updateInterval: Duration = .days(1)
@@ -84,7 +84,7 @@ final class AddressCacheTracker {
timer = nil
}
- func updateEndpoints(completionHandler: ((Result<Bool, Error>) -> Void)? = nil) -> Cancellable {
+ func updateEndpoints(completionHandler: (@Sendable (Result<Bool, Error>) -> Void)? = nil) -> Cancellable {
let operation = ResultBlockOperation<Bool> { finish -> Cancellable in
guard self.nextScheduleDate() <= Date() else {
finish(.success(false))
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index 2f78e90476..d6620c992c 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -18,9 +18,10 @@ import StoreKit
import UIKit
import UserNotifications
-@UIApplicationMain
-class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, StorePaymentManagerDelegate {
- private var logger: Logger!
+@main
+class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, StorePaymentManagerDelegate,
+ @unchecked Sendable {
+ nonisolated(unsafe) private var logger: Logger!
#if targetEnvironment(simulator)
private var simulatorTunnelProviderHost: SimulatorTunnelProviderHost?
@@ -29,22 +30,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
private let operationQueue = AsyncOperationQueue.makeSerial()
private(set) var tunnelStore: TunnelStore!
- private(set) var tunnelManager: TunnelManager!
+ nonisolated(unsafe) private(set) var tunnelManager: TunnelManager!
private(set) var addressCache: REST.AddressCache!
private var proxyFactory: ProxyFactoryProtocol!
private(set) var apiProxy: APIQuerying!
private(set) var accountsProxy: RESTAccountHandling!
- private(set) var devicesProxy: DeviceHandling!
+ nonisolated(unsafe) private(set) var devicesProxy: DeviceHandling!
private(set) var addressCacheTracker: AddressCacheTracker!
- private(set) var relayCacheTracker: RelayCacheTracker!
- private(set) var storePaymentManager: StorePaymentManager!
- private var transportMonitor: TransportMonitor!
+ nonisolated(unsafe) private(set) var relayCacheTracker: RelayCacheTracker!
+ nonisolated(unsafe) private(set) var storePaymentManager: StorePaymentManager!
+ nonisolated(unsafe) private var transportMonitor: TransportMonitor!
private var settingsObserver: TunnelBlockObserver!
private var migrationManager: MigrationManager!
- private(set) var accessMethodRepository = AccessMethodRepository()
+ nonisolated(unsafe) private(set) var accessMethodRepository = AccessMethodRepository()
private(set) var shadowsocksLoader: ShadowsocksLoaderProtocol!
private(set) var configuredTransportProvider: ProxyConfigurationTransportProvider!
private(set) var ipOverrideRepository = IPOverrideRepository()
@@ -277,11 +278,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
forTaskWithIdentifier: BackgroundTask.appRefresh.identifier,
using: nil
) { [self] task in
+ nonisolated(unsafe) let nonisolatedTask = task
+
let handle = relayCacheTracker.updateRelays { result in
- task.setTaskCompleted(success: result.isSuccess)
+ nonisolatedTask.setTaskCompleted(success: result.isSuccess)
}
- task.expirationHandler = {
+ nonisolatedTask.expirationHandler = {
handle.cancel()
}
@@ -300,13 +303,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
forTaskWithIdentifier: BackgroundTask.privateKeyRotation.identifier,
using: nil
) { [self] task in
+ nonisolated(unsafe) let nonisolatedTask = task
let handle = tunnelManager.rotatePrivateKey { [self] error in
- scheduleKeyRotationTask()
+ Task { @MainActor in
+ scheduleKeyRotationTask()
- task.setTaskCompleted(success: error == nil)
+ nonisolatedTask.setTaskCompleted(success: error == nil)
+ }
}
- task.expirationHandler = {
+ nonisolatedTask.expirationHandler = {
handle.cancel()
}
}
@@ -323,13 +329,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
forTaskWithIdentifier: BackgroundTask.addressCacheUpdate.identifier,
using: nil
) { [self] task in
- let handle = addressCacheTracker.updateEndpoints { [self] result in
- scheduleAddressCacheUpdateTask()
+ nonisolated(unsafe) let nonisolatedTask = task
- task.setTaskCompleted(success: result.isSuccess)
+ let handle = addressCacheTracker.updateEndpoints { [self] result in
+ Task { @MainActor in
+ scheduleAddressCacheUpdateTask()
+ nonisolatedTask.setTaskCompleted(success: result.isSuccess)
+ }
}
- task.expirationHandler = {
+ nonisolatedTask.expirationHandler = {
handle.cancel()
}
}
@@ -471,48 +480,56 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
private func getLoadTunnelStoreOperation() -> AsyncBlockOperation {
AsyncBlockOperation(dispatchQueue: .main) { [self] finish in
- tunnelStore.loadPersistentTunnels { [self] error in
- if let error {
- logger.error(
- error: error,
- message: "Failed to load persistent tunnels."
- )
+ MainActor.assumeIsolated {
+ tunnelStore.loadPersistentTunnels { [self] error in
+ if let error {
+ logger.error(
+ error: error,
+ message: "Failed to load persistent tunnels."
+ )
+ }
+ finish(nil)
}
- finish(nil)
}
}
}
private func getMigrateSettingsOperation(application: UIApplication) -> AsyncBlockOperation {
- AsyncBlockOperation(dispatchQueue: .main) { [self] finish in
- migrationManager
- .migrateSettings(store: SettingsManager.store) { [self] migrationResult in
- switch migrationResult {
- case .success:
- // Tell the tunnel to re-read tunnel configuration after migration.
- logger.debug("Successful migration from UI Process")
- tunnelManager.reconnectTunnel(selectNewRelay: true)
- fallthrough
+ AsyncBlockOperation(dispatchQueue: .main, block: { [self] (finish: @escaping @Sendable (Error?) -> Void) in
+ MainActor.assumeIsolated {
+ migrationManager
+ .migrateSettings(store: SettingsManager.store) { [self] migrationResult in
+ switch migrationResult {
+ case .success:
+ // Tell the tunnel to re-read tunnel configuration after migration.
+ logger.debug("Successful migration from UI Process")
+ tunnelManager.reconnectTunnel(selectNewRelay: true)
+ fallthrough
- case .nothing:
- logger.debug("Attempted migration from UI Process, but found nothing to do")
- finish(nil)
+ case .nothing:
+ logger.debug("Attempted migration from UI Process, but found nothing to do")
+ finish(nil)
- case let .failure(error):
- logger.error("Failed migration from UI Process: \(error)")
- let migrationUIHandler = application.connectedScenes
- .first { $0 is SettingsMigrationUIHandler } as? SettingsMigrationUIHandler
+ case let .failure(error):
+ logger.error("Failed migration from UI Process: \(error)")
+ MainActor.assumeIsolated {
+ let migrationUIHandler = application.connectedScenes
+ .first { $0 is SettingsMigrationUIHandler } as? SettingsMigrationUIHandler
- if let migrationUIHandler {
- migrationUIHandler.showMigrationError(error) {
- finish(error)
+ if let migrationUIHandler {
+ migrationUIHandler.showMigrationError(error) {
+ MainActor.assumeIsolated {
+ finish(error)
+ }
+ }
+ } else {
+ finish(error)
+ }
}
- } else {
- finish(error)
}
}
- }
- }
+ }
+ })
}
private func getInitTunnelManagerOperation() -> AsyncBlockOperation {
@@ -576,7 +593,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// MARK: - StorePaymentManagerDelegate
- func storePaymentManager(_ manager: StorePaymentManager, didRequestAccountTokenFor payment: SKPayment) -> String? {
+ nonisolated func storePaymentManager(
+ _ manager: StorePaymentManager,
+ didRequestAccountTokenFor payment: SKPayment
+ ) -> String? {
// Since we do not persist the relation between payment and account number between the
// app launches, we assume that all successful purchases belong to the active account
// number.
@@ -585,21 +605,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// MARK: - UNUserNotificationCenterDelegate
- func userNotificationCenter(
+ nonisolated func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
+ nonisolated(unsafe) let nonisolatedResponse = response
+ nonisolated(unsafe) let nonisolatedCompletionHandler = completionHandler
+
let blockOperation = AsyncBlockOperation(dispatchQueue: .main) {
- NotificationManager.shared.handleSystemNotificationResponse(response)
+ NotificationManager.shared.handleSystemNotificationResponse(nonisolatedResponse)
- completionHandler()
+ nonisolatedCompletionHandler()
}
operationQueue.addOperation(blockOperation)
}
- func userNotificationCenter(
+ nonisolated func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
index 73d9a2d140..1feeed7331 100644
--- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
+++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
@@ -228,6 +228,7 @@ extension AccessibilityIdentifier {
}
extension UIAccessibilityIdentification {
+ @MainActor
func setAccessibilityIdentifier(_ value: AccessibilityIdentifier?) {
accessibilityIdentifier = value.map(\.asString)
}
diff --git a/ios/MullvadVPN/Classes/AutomaticKeyboardResponder.swift b/ios/MullvadVPN/Classes/AutomaticKeyboardResponder.swift
index 00defec889..d4a3417050 100644
--- a/ios/MullvadVPN/Classes/AutomaticKeyboardResponder.swift
+++ b/ios/MullvadVPN/Classes/AutomaticKeyboardResponder.swift
@@ -9,6 +9,7 @@
import MullvadLogging
import UIKit
+@MainActor
class AutomaticKeyboardResponder {
weak var targetView: UIView?
private let handler: (UIView, CGFloat) -> Void
diff --git a/ios/MullvadVPN/Classes/ConsolidatedApplicationLog.swift b/ios/MullvadVPN/Classes/ConsolidatedApplicationLog.swift
index 6e96912c58..43b1a19dab 100644
--- a/ios/MullvadVPN/Classes/ConsolidatedApplicationLog.swift
+++ b/ios/MullvadVPN/Classes/ConsolidatedApplicationLog.swift
@@ -13,7 +13,7 @@ private let kRedactedPlaceholder = "[REDACTED]"
private let kRedactedAccountPlaceholder = "[REDACTED ACCOUNT NUMBER]"
private let kRedactedContainerPlaceholder = "[REDACTED CONTAINER PATH]"
-class ConsolidatedApplicationLog: TextOutputStreamable {
+class ConsolidatedApplicationLog: TextOutputStreamable, @unchecked Sendable {
typealias Metadata = KeyValuePairs<MetadataKey, String>
private let bufferSize: UInt64
@@ -50,7 +50,7 @@ class ConsolidatedApplicationLog: TextOutputStreamable {
}
}
- func addLogFiles(fileURLs: [URL], completion: (() -> Void)? = nil) {
+ func addLogFiles(fileURLs: [URL], completion: (@Sendable () -> Void)? = nil) {
logQueue.async(flags: .barrier) {
for fileURL in fileURLs {
self.addSingleLogFile(fileURL)
@@ -61,7 +61,7 @@ class ConsolidatedApplicationLog: TextOutputStreamable {
}
}
- func addError(message: String, error: String, completion: (() -> Void)? = nil) {
+ func addError(message: String, error: String, completion: (@Sendable () -> Void)? = nil) {
let redactedError = redact(string: error)
logQueue.async(flags: .barrier) {
self.logs.append(LogAttachment(label: message, content: redactedError))
diff --git a/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift b/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift
index 2c405395bc..8e3a4c8f5d 100644
--- a/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift
+++ b/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift
@@ -9,7 +9,7 @@
import Routing
import UIKit
-enum HeaderBarStyle {
+enum HeaderBarStyle: Sendable {
case transparent, `default`, unsecured, secured
fileprivate func backgroundColor() -> UIColor {
@@ -26,7 +26,7 @@ enum HeaderBarStyle {
}
}
-struct HeaderBarPresentation {
+struct HeaderBarPresentation: Sendable {
let style: HeaderBarStyle
let showsDivider: Bool
@@ -36,7 +36,8 @@ struct HeaderBarPresentation {
}
/// A protocol that defines the relationship between the root container and its child controllers
-protocol RootContainment {
+@MainActor
+protocol RootContainment: Sendable {
/// Return the preferred header bar style
var preferredHeaderBarPresentation: HeaderBarPresentation { get }
@@ -60,7 +61,7 @@ extension RootContainment {
}
}
-protocol RootContainerViewControllerDelegate: AnyObject {
+protocol RootContainerViewControllerDelegate: AnyObject, Sendable {
func rootContainerViewControllerShouldShowAccount(
_ controller: RootContainerViewController,
animated: Bool
diff --git a/ios/MullvadVPN/Coordinators/AccountCoordinator.swift b/ios/MullvadVPN/Coordinators/AccountCoordinator.swift
index 310ebbdc84..50f2a54d46 100644
--- a/ios/MullvadVPN/Coordinators/AccountCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/AccountCoordinator.swift
@@ -9,18 +9,18 @@
import Routing
import UIKit
-enum AccountDismissReason: Equatable {
+enum AccountDismissReason: Equatable, Sendable {
case none
case userLoggedOut
case accountDeletion
}
-enum AddedMoreCreditOption: Equatable {
+enum AddedMoreCreditOption: Equatable, Sendable {
case redeemingVoucher
case inAppPurchase
}
-final class AccountCoordinator: Coordinator, Presentable, Presenting {
+final class AccountCoordinator: Coordinator, Presentable, Presenting, @unchecked Sendable {
private let interactor: AccountInteractor
private var accountController: AccountViewController?
@@ -29,7 +29,7 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting {
navigationController
}
- var didFinish: ((AccountCoordinator, AccountDismissReason) -> Void)?
+ var didFinish: (@MainActor (AccountCoordinator, AccountDismissReason) -> Void)?
init(
navigationController: UINavigationController,
@@ -101,6 +101,7 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting {
)
}
+ @MainActor
private func navigateToDeleteAccount() {
let coordinator = AccountDeletionCoordinator(
navigationController: CustomNavigationController(),
@@ -109,10 +110,12 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting {
coordinator.start()
coordinator.didCancel = { accountDeletionCoordinator in
- accountDeletionCoordinator.dismiss(animated: true)
+ Task { @MainActor in
+ accountDeletionCoordinator.dismiss(animated: true)
+ }
}
- coordinator.didFinish = { accountDeletionCoordinator in
+ coordinator.didFinish = { @MainActor accountDeletionCoordinator in
accountDeletionCoordinator.dismiss(animated: true) {
self.didFinish?(self, .userLoggedOut)
}
diff --git a/ios/MullvadVPN/Coordinators/AccountDeletionCoordinator.swift b/ios/MullvadVPN/Coordinators/AccountDeletionCoordinator.swift
index e1e2058ca2..445812f601 100644
--- a/ios/MullvadVPN/Coordinators/AccountDeletionCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/AccountDeletionCoordinator.swift
@@ -14,8 +14,8 @@ final class AccountDeletionCoordinator: Coordinator, Presentable {
private let navigationController: UINavigationController
private let interactor: AccountDeletionInteractor
- var didCancel: ((AccountDeletionCoordinator) -> Void)?
- var didFinish: ((AccountDeletionCoordinator) -> Void)?
+ var didCancel: (@MainActor (AccountDeletionCoordinator) -> Void)?
+ var didFinish: (@MainActor (AccountDeletionCoordinator) -> Void)?
var presentedViewController: UIViewController {
navigationController
@@ -37,7 +37,7 @@ final class AccountDeletionCoordinator: Coordinator, Presentable {
}
}
-extension AccountDeletionCoordinator: AccountDeletionViewControllerDelegate {
+extension AccountDeletionCoordinator: @preconcurrency AccountDeletionViewControllerDelegate {
func deleteAccountDidSucceed(controller: AccountDeletionViewController) {
didFinish?(self)
}
diff --git a/ios/MullvadVPN/Coordinators/AddCreditSucceededCoordinator.swift b/ios/MullvadVPN/Coordinators/AddCreditSucceededCoordinator.swift
index 7d7922adfb..3a90d4cb82 100644
--- a/ios/MullvadVPN/Coordinators/AddCreditSucceededCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/AddCreditSucceededCoordinator.swift
@@ -35,7 +35,7 @@ final class AddCreditSucceededCoordinator: Coordinator {
}
}
-extension AddCreditSucceededCoordinator: AddCreditSucceededViewControllerDelegate {
+extension AddCreditSucceededCoordinator: @preconcurrency AddCreditSucceededViewControllerDelegate {
func header(in controller: AddCreditSucceededViewController) -> String {
switch paymentType {
case .inAppPurchase:
diff --git a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
index 6fbb0690cb..706637086f 100644
--- a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
@@ -16,14 +16,15 @@ import UIKit
/**
Application coordinator managing split view and two navigation contexts.
*/
-final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewControllerDelegate,
- UISplitViewControllerDelegate, ApplicationRouterDelegate, NotificationManagerDelegate {
+final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency RootContainerViewControllerDelegate,
+ UISplitViewControllerDelegate, @preconcurrency ApplicationRouterDelegate,
+ @preconcurrency NotificationManagerDelegate {
typealias RouteType = AppRoute
/**
Application router.
*/
- private(set) var router: ApplicationRouter<AppRoute>!
+ nonisolated(unsafe) private(set) var router: ApplicationRouter<AppRoute>!
/**
Navigation container.
@@ -109,7 +110,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
_ router: ApplicationRouter<RouteType>,
presentWithContext context: RoutePresentationContext<RouteType>,
animated: Bool,
- completion: @escaping (Coordinator) -> Void
+ completion: @escaping @Sendable (Coordinator) -> Void
) {
switch context.route {
case .account:
@@ -150,7 +151,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
func applicationRouter(
_ router: ApplicationRouter<RouteType>,
dismissWithContext context: RouteDismissalContext<RouteType>,
- completion: @escaping () -> Void
+ completion: @escaping @Sendable () -> Void
) {
let dismissedRoute = context.dismissedRoutes.first!
@@ -230,7 +231,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
func applicationRouter(
_ router: ApplicationRouter<RouteType>,
handleSubNavigationWithContext context: RouteSubnavigationContext<RouteType>,
- completion: @escaping () -> Void
+ completion: @escaping @Sendable @MainActor () -> Void
) {
switch context.route {
case let .settings(subRoute):
@@ -385,9 +386,11 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
coordinator.didFinishPayment = { [weak self] _ in
guard let self = self else { return }
- if shouldDismissOutOfTime() {
- router.dismiss(.outOfTime, animated: true)
- continueFlow(animated: true)
+ Task { @MainActor in
+ if shouldDismissOutOfTime() {
+ router.dismiss(.outOfTime, animated: true)
+ continueFlow(animated: true)
+ }
}
}
@@ -448,10 +451,14 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
coordinator.preferredAccountNumberPublisher = preferredAccountNumberSubject.eraseToAnyPublisher()
coordinator.didFinish = { [weak self] _ in
- self?.continueFlow(animated: true)
+ MainActor.assumeIsolated {
+ self?.continueFlow(animated: true)
+ }
}
coordinator.didCreateAccount = { [weak self] in
- self?.appPreferences.isShownOnboarding = false
+ MainActor.assumeIsolated {
+ self?.appPreferences.isShownOnboarding = false
+ }
}
addChild(coordinator)
@@ -532,7 +539,9 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
)
coordinator.didFinish = { [weak self] _, reason in
- self?.didDismissAccount(reason)
+ MainActor.assumeIsolated {
+ self?.didDismissAccount(reason)
+ }
}
coordinator.start(animated: animated)
@@ -550,7 +559,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
private func presentSettings(
route: SettingsNavigationRoute?,
animated: Bool,
- completion: @escaping (Coordinator) -> Void
+ completion: @escaping @Sendable (Coordinator) -> Void
) {
let interactorFactory = SettingsInteractorFactory(
storePaymentManager: storePaymentManager,
@@ -574,7 +583,9 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
)
coordinator.didFinish = { [weak self] _ in
- self?.router.dismissAll(.settings, animated: true)
+ Task { @MainActor in
+ self?.router.dismissAll(.settings, animated: true)
+ }
}
coordinator.willNavigate = { [weak self] _, _, to in
@@ -663,7 +674,9 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
guard !accountData.isExpired else { return }
let timer = Timer(fire: accountData.expiry, interval: 0, repeats: false, block: { [weak self] _ in
- self?.router.present(.outOfTime, animated: true)
+ Task { @MainActor in
+ self?.router.present(.outOfTime, animated: true)
+ }
})
RunLoop.main.add(timer, forMode: .common)
diff --git a/ios/MullvadVPN/Coordinators/CreateAccountVoucherCoordinator.swift b/ios/MullvadVPN/Coordinators/CreateAccountVoucherCoordinator.swift
index 50beb1ac55..7fc203dac3 100644
--- a/ios/MullvadVPN/Coordinators/CreateAccountVoucherCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/CreateAccountVoucherCoordinator.swift
@@ -50,7 +50,7 @@ public class CreateAccountVoucherCoordinator: Coordinator {
}
}
-extension CreateAccountVoucherCoordinator: RedeemVoucherViewControllerDelegate {
+extension CreateAccountVoucherCoordinator: @preconcurrency RedeemVoucherViewControllerDelegate {
func redeemVoucherDidSucceed(_ controller: RedeemVoucherViewController, with response: REST.SubmitVoucherResponse) {
let coordinator = AddCreditSucceededCoordinator(
purchaseType: .redeemingVoucher,
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/AddCustomListCoordinator.swift b/ios/MullvadVPN/Coordinators/CustomLists/AddCustomListCoordinator.swift
index 6ef8d044fd..d69a997494 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/AddCustomListCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/AddCustomListCoordinator.swift
@@ -71,7 +71,7 @@ class AddCustomListCoordinator: Coordinator, Presentable, Presenting {
}
}
-extension AddCustomListCoordinator: CustomListViewControllerDelegate {
+extension AddCustomListCoordinator: @preconcurrency CustomListViewControllerDelegate {
func customListDidSave(_ list: CustomList) {
didFinish?(self)
}
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsCoordinator.swift b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsCoordinator.swift
index 1634a24e80..20bdc02500 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsCoordinator.swift
@@ -51,7 +51,7 @@ class AddLocationsCoordinator: Coordinator, Presentable, Presenting {
}
}
-extension AddLocationsCoordinator: AddLocationsViewControllerDelegate {
+extension AddLocationsCoordinator: @preconcurrency AddLocationsViewControllerDelegate {
func didBack() {
didFinish?(self)
}
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift
index 4db1587c6b..64a8230156 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift
@@ -13,7 +13,7 @@ import UIKit
class AddLocationsDataSource:
UITableViewDiffableDataSource<LocationSection, LocationCellViewModel>,
- LocationDiffableDataSourceProtocol {
+ LocationDiffableDataSourceProtocol, @unchecked Sendable {
private var customListLocationNode: CustomListLocationNode
private let nodes: [LocationNode]
private let subject: CurrentValueSubject<CustomListViewModel, Never>
@@ -108,7 +108,7 @@ extension AddLocationsDataSource: UITableViewDelegate {
}
}
-extension AddLocationsDataSource: LocationCellDelegate {
+extension AddLocationsDataSource: @preconcurrency LocationCellDelegate {
func toggleExpanding(cell: LocationCell) {
toggleItems(for: cell) {
if let indexPath = self.tableView.indexPath(for: cell),
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsViewController.swift b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsViewController.swift
index 10d8a308b6..053ba36221 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsViewController.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsViewController.swift
@@ -11,7 +11,7 @@ import MullvadSettings
import MullvadTypes
import UIKit
-protocol AddLocationsViewControllerDelegate: AnyObject {
+protocol AddLocationsViewControllerDelegate: AnyObject, Sendable {
func didBack()
}
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/CustomListCellConfiguration.swift b/ios/MullvadVPN/Coordinators/CustomLists/CustomListCellConfiguration.swift
index ddf7b87c11..ef3cc36cc3 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/CustomListCellConfiguration.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/CustomListCellConfiguration.swift
@@ -10,6 +10,7 @@ import Combine
import MullvadTypes
import UIKit
+@MainActor
struct CustomListCellConfiguration {
let tableView: UITableView
let subject: CurrentValueSubject<CustomListViewModel, Never>
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/CustomListDataSourceConfiguration.swift b/ios/MullvadVPN/Coordinators/CustomLists/CustomListDataSourceConfiguration.swift
index b5598abf3e..aed829ce39 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/CustomListDataSourceConfiguration.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/CustomListDataSourceConfiguration.swift
@@ -8,6 +8,7 @@
import UIKit
+@MainActor
class CustomListDataSourceConfiguration: NSObject {
let dataSource: UITableViewDiffableDataSource<CustomListSectionIdentifier, CustomListItemIdentifier>
var validationErrors: Set<CustomListFieldValidationError> = []
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/EditCustomListCoordinator.swift b/ios/MullvadVPN/Coordinators/CustomLists/EditCustomListCoordinator.swift
index 2e3c8c9a0c..bc2892c58f 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/EditCustomListCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/EditCustomListCoordinator.swift
@@ -139,7 +139,7 @@ class EditCustomListCoordinator: Coordinator, Presentable, Presenting {
}
}
-extension EditCustomListCoordinator: CustomListViewControllerDelegate {
+extension EditCustomListCoordinator: @preconcurrency CustomListViewControllerDelegate {
func customListDidSave(_ list: CustomList) {
didFinish?(self, .save, list)
}
@@ -156,7 +156,9 @@ extension EditCustomListCoordinator: CustomListViewControllerDelegate {
)
coordinator.didFinish = { locationsCoordinator in
- locationsCoordinator.removeFromParent()
+ Task { @MainActor in
+ locationsCoordinator.removeFromParent()
+ }
}
coordinator.start()
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/EditLocationsCoordinator.swift b/ios/MullvadVPN/Coordinators/CustomLists/EditLocationsCoordinator.swift
index 9ca615ea69..a5561c1682 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/EditLocationsCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/EditLocationsCoordinator.swift
@@ -12,12 +12,13 @@ import MullvadTypes
import Routing
import UIKit
-class EditLocationsCoordinator: Coordinator, Presentable, Presenting {
+@MainActor
+class EditLocationsCoordinator: Coordinator, Presentable, Presenting, Sendable {
private let navigationController: UINavigationController
private let nodes: [LocationNode]
private var subject: CurrentValueSubject<CustomListViewModel, Never>
- var didFinish: ((EditLocationsCoordinator) -> Void)?
+ nonisolated(unsafe) var didFinish: (@Sendable (EditLocationsCoordinator) -> Void)?
var presentedViewController: UIViewController {
navigationController
@@ -51,7 +52,7 @@ class EditLocationsCoordinator: Coordinator, Presentable, Presenting {
}
extension EditLocationsCoordinator: AddLocationsViewControllerDelegate {
- func didBack() {
+ nonisolated func didBack() {
didFinish?(self)
}
}
diff --git a/ios/MullvadVPN/Coordinators/LocationCoordinator.swift b/ios/MullvadVPN/Coordinators/LocationCoordinator.swift
index acdb359560..8703c46e5d 100644
--- a/ios/MullvadVPN/Coordinators/LocationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/LocationCoordinator.swift
@@ -204,7 +204,7 @@ extension LocationCoordinator: UIAdaptivePresentationControllerDelegate {
}
}
-extension LocationCoordinator: RelayCacheTrackerObserver {
+extension LocationCoordinator: @preconcurrency RelayCacheTrackerObserver {
func relayCacheTracker(
_ tracker: RelayCacheTracker,
didUpdateCachedRelays cachedRelays: CachedRelays
@@ -219,7 +219,7 @@ extension LocationCoordinator: RelayCacheTrackerObserver {
}
}
-extension LocationCoordinator: LocationViewControllerWrapperDelegate {
+extension LocationCoordinator: @preconcurrency LocationViewControllerWrapperDelegate {
func didSelectEntryRelays(_ relays: UserSelectedRelays) {
var relayConstraints = tunnelManager.settings.relayConstraints
relayConstraints.entryLocations = .only(relays)
diff --git a/ios/MullvadVPN/Coordinators/LoginCoordinator.swift b/ios/MullvadVPN/Coordinators/LoginCoordinator.swift
index a99d693b9d..4ac34bcbbe 100644
--- a/ios/MullvadVPN/Coordinators/LoginCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/LoginCoordinator.swift
@@ -13,16 +13,16 @@ import Operations
import Routing
import UIKit
-final class LoginCoordinator: Coordinator, Presenting, DeviceManagementViewControllerDelegate {
+final class LoginCoordinator: Coordinator, Presenting, @preconcurrency DeviceManagementViewControllerDelegate {
private let tunnelManager: TunnelManager
private let devicesProxy: DeviceHandling
private var loginController: LoginViewController?
- private var lastLoginAction: LoginAction?
+ nonisolated(unsafe) private var lastLoginAction: LoginAction?
private var subscriptions = Set<Combine.AnyCancellable>()
- var didFinish: ((LoginCoordinator) -> Void)?
- var didCreateAccount: (() -> Void)?
+ var didFinish: (@Sendable (LoginCoordinator) -> Void)?
+ var didCreateAccount: (@Sendable () -> Void)?
var preferredAccountNumberPublisher: AnyPublisher<String, Never>?
var presentationContext: UIViewController {
@@ -84,10 +84,11 @@ final class LoginCoordinator: Coordinator, Presenting, DeviceManagementViewContr
if case let .useExistingAccount(accountNumber) = action {
if let error = error as? REST.Error, error.compareErrorCode(.maxDevicesReached) {
return .wait(Promise { resolve in
+ nonisolated(unsafe) let sendableResolve = resolve
self.showDeviceList(for: accountNumber) { error in
self.lastLoginAction = action
- resolve(error.map { .failure($0) } ?? .success(()))
+ sendableResolve(error.map { .failure($0) } ?? .success(()))
}
})
} else {
@@ -115,7 +116,7 @@ final class LoginCoordinator: Coordinator, Presenting, DeviceManagementViewContr
}
}
- private func showDeviceList(for accountNumber: String, completion: @escaping (Error?) -> Void) {
+ private func showDeviceList(for accountNumber: String, completion: @escaping @Sendable (Error?) -> Void) {
let interactor = DeviceManagementInteractor(
accountNumber: accountNumber,
devicesProxy: devicesProxy
@@ -131,8 +132,10 @@ final class LoginCoordinator: Coordinator, Presenting, DeviceManagementViewContr
switch result {
case .success:
- navigationController.pushViewController(controller, animated: true) {
- completion(nil)
+ Task { @MainActor in
+ navigationController.pushViewController(controller, animated: true) {
+ completion(nil)
+ }
}
case let .failure(error):
diff --git a/ios/MullvadVPN/Coordinators/OutOfTimeCoordinator.swift b/ios/MullvadVPN/Coordinators/OutOfTimeCoordinator.swift
index 5c333c4564..54e928e726 100644
--- a/ios/MullvadVPN/Coordinators/OutOfTimeCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/OutOfTimeCoordinator.swift
@@ -9,12 +9,12 @@
import Routing
import UIKit
-class OutOfTimeCoordinator: Coordinator, Presenting, OutOfTimeViewControllerDelegate, Poppable {
+class OutOfTimeCoordinator: Coordinator, Presenting, @preconcurrency OutOfTimeViewControllerDelegate, Poppable {
let navigationController: RootContainerViewController
let storePaymentManager: StorePaymentManager
let tunnelManager: TunnelManager
- var didFinishPayment: ((OutOfTimeCoordinator) -> Void)?
+ nonisolated(unsafe) var didFinishPayment: (@Sendable (OutOfTimeCoordinator) -> Void)?
var presentedViewController: UIViewController {
navigationController
diff --git a/ios/MullvadVPN/Coordinators/ProfileVoucherCoordinator.swift b/ios/MullvadVPN/Coordinators/ProfileVoucherCoordinator.swift
index 61fbcd7e94..6e0e06a08e 100644
--- a/ios/MullvadVPN/Coordinators/ProfileVoucherCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/ProfileVoucherCoordinator.swift
@@ -44,7 +44,7 @@ final class ProfileVoucherCoordinator: Coordinator, Presentable {
}
}
-extension ProfileVoucherCoordinator: RedeemVoucherViewControllerDelegate {
+extension ProfileVoucherCoordinator: @preconcurrency RedeemVoucherViewControllerDelegate {
func redeemVoucherDidSucceed(
_ controller: RedeemVoucherViewController,
with response: REST.SubmitVoucherResponse
@@ -59,7 +59,7 @@ extension ProfileVoucherCoordinator: RedeemVoucherViewControllerDelegate {
}
}
-extension ProfileVoucherCoordinator: AddCreditSucceededViewControllerDelegate {
+extension ProfileVoucherCoordinator: @preconcurrency AddCreditSucceededViewControllerDelegate {
func addCreditSucceededViewControllerDidFinish(in controller: AddCreditSucceededViewController) {
didFinish?(self)
}
diff --git a/ios/MullvadVPN/Coordinators/RelayFilterCoordinator.swift b/ios/MullvadVPN/Coordinators/RelayFilterCoordinator.swift
index 39287b3ec2..02d65b023a 100644
--- a/ios/MullvadVPN/Coordinators/RelayFilterCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/RelayFilterCoordinator.swift
@@ -11,7 +11,7 @@ import MullvadTypes
import Routing
import UIKit
-class RelayFilterCoordinator: Coordinator, Presentable, RelayCacheTrackerObserver {
+class RelayFilterCoordinator: Coordinator, Presentable, @preconcurrency RelayCacheTrackerObserver {
private let tunnelManager: TunnelManager
private let relayCacheTracker: RelayCacheTracker
private var cachedRelays: CachedRelays?
diff --git a/ios/MullvadVPN/Coordinators/SafariCoordinator.swift b/ios/MullvadVPN/Coordinators/SafariCoordinator.swift
index eca261faea..cca28e075c 100644
--- a/ios/MullvadVPN/Coordinators/SafariCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/SafariCoordinator.swift
@@ -10,8 +10,9 @@ import Foundation
import Routing
import SafariServices
-class SafariCoordinator: Coordinator, Presentable, SFSafariViewControllerDelegate {
- var didFinish: (() -> Void)?
+@MainActor
+class SafariCoordinator: Coordinator, Presentable, @preconcurrency SFSafariViewControllerDelegate {
+ nonisolated(unsafe) var didFinish: (@Sendable () -> Void)?
var presentedViewController: UIViewController {
safariController
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Add/AddAccessMethodCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Add/AddAccessMethodCoordinator.swift
index d6ea38a426..33d0870307 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Add/AddAccessMethodCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Add/AddAccessMethodCoordinator.swift
@@ -81,7 +81,7 @@ class AddAccessMethodCoordinator: Coordinator, Presentable, Presenting {
}
}
-extension AddAccessMethodCoordinator: MethodSettingsViewControllerDelegate {
+extension AddAccessMethodCoordinator: @preconcurrency MethodSettingsViewControllerDelegate {
func accessMethodDidSave(_ accessMethod: PersistentAccessMethod) {
dismiss(animated: true)
}
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/TextCellContentView.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/TextCellContentView.swift
index c41123906d..a3b0009b46 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/TextCellContentView.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/TextCellContentView.swift
@@ -9,7 +9,7 @@
import UIKit
/// Content view presenting a label and text field.
-class TextCellContentView: UIView, UIContentView, UIGestureRecognizerDelegate {
+class TextCellContentView: UIView, UIContentView, UIGestureRecognizerDelegate, Sendable {
private var textLabel = UILabel()
private var textField = CustomTextField()
@@ -175,6 +175,7 @@ extension TextCellContentView: UITextFieldDelegate {
}
extension TextCellContentConfiguration.TextFieldProperties {
+ @MainActor
func apply(to textField: CustomTextField) {
textField.font = font
textField.backgroundColor = .clear
@@ -197,6 +198,7 @@ extension TextCellContentConfiguration.TextFieldProperties {
}
extension TextCellContentConfiguration.EditingEvents {
+ @MainActor
func register(in textField: UITextField) {
onChange.map { textField.addAction($0, for: .editingChanged) }
onBegin.map { textField.addAction($0, for: .editingDidBegin) }
@@ -204,6 +206,7 @@ extension TextCellContentConfiguration.EditingEvents {
onEndOnExit.map { textField.addAction($0, for: .editingDidEndOnExit) }
}
+ @MainActor
func unregister(from textField: UITextField) {
onChange.map { textField.removeAction($0, for: .editingChanged) }
onBegin.map { textField.removeAction($0, for: .editingDidBegin) }
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/ShadowsocksSectionHandler.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/ShadowsocksSectionHandler.swift
index 81d9e9fc02..7a32cd4aa1 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/ShadowsocksSectionHandler.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/ShadowsocksSectionHandler.swift
@@ -10,6 +10,7 @@ import Combine
import UIKit
/// Type responsible for handling cells in shadowsocks table view section.
+@MainActor
struct ShadowsocksSectionHandler {
private let authenticationInputMaxLength = 2048
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/SocksSectionHandler.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/SocksSectionHandler.swift
index b5c3acfb69..e341aa60cd 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/SocksSectionHandler.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/SocksSectionHandler.swift
@@ -10,6 +10,7 @@ import Combine
import UIKit
/// Type responsible for handling cells in socks table view section.
+@MainActor
struct SocksSectionHandler {
private let authenticationInputMaxLength = 255
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/CurrentValueSubject+UIActionBindings.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/CurrentValueSubject+UIActionBindings.swift
index 9adb159935..1ad22507e4 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/CurrentValueSubject+UIActionBindings.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/CurrentValueSubject+UIActionBindings.swift
@@ -14,7 +14,7 @@ extension CurrentValueSubject {
///
/// - Parameter keyPath: the key path to the field that should be updated.
/// - Returns: an instance of `UIAction`.
- func bindTextAction(to keyPath: WritableKeyPath<Output, String>) -> UIAction {
+ @MainActor func bindTextAction(to keyPath: WritableKeyPath<Output, String>) -> UIAction {
UIAction { action in
guard let textField = action.sender as? UITextField else { return }
@@ -26,7 +26,7 @@ extension CurrentValueSubject {
///
/// - Parameter keyPath: the key path to the field that should be updated.
/// - Returns: an instance of `UIAction`.
- func bindSwitchAction(to keyPath: WritableKeyPath<Output, Bool>) -> UIAction {
+ @MainActor func bindSwitchAction(to keyPath: WritableKeyPath<Output, Bool>) -> UIAction {
UIAction { action in
guard let toggle = action.sender as? UISwitch else { return }
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodCoordinator.swift
index d219a7bf93..3f69ed090a 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodCoordinator.swift
@@ -57,7 +57,7 @@ class EditAccessMethodCoordinator: Coordinator, Presenting {
}
}
-extension EditAccessMethodCoordinator: EditAccessMethodViewControllerDelegate {
+extension EditAccessMethodCoordinator: @preconcurrency EditAccessMethodViewControllerDelegate {
func controllerShouldShowMethodSettings(_ controller: EditAccessMethodViewController) {
methodSettingsSubject = getViewModelSubjectFromStore()
@@ -127,7 +127,7 @@ extension EditAccessMethodCoordinator: EditAccessMethodViewControllerDelegate {
}
}
-extension EditAccessMethodCoordinator: MethodSettingsViewControllerDelegate {
+extension EditAccessMethodCoordinator: @preconcurrency MethodSettingsViewControllerDelegate {
func accessMethodDidSave(_ accessMethod: PersistentAccessMethod) {
editAccessMethodSubject.value = accessMethod.toViewModel()
navigationController.popViewController(animated: true)
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodInteractor.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodInteractor.swift
index 420fd71eab..c369a96212 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodInteractor.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodInteractor.swift
@@ -6,7 +6,7 @@
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//
-import Combine
+@preconcurrency import Combine
import Foundation
import MullvadSettings
@@ -25,7 +25,7 @@ struct EditAccessMethodInteractor: EditAccessMethodInteractorProtocol {
repository.delete(id: subject.value.id)
}
- func startProxyConfigurationTest(_ completion: ((Bool) -> Void)?) {
+ func startProxyConfigurationTest(_ completion: (@Sendable (Bool) -> Void)?) {
guard let config = try? subject.value.intoPersistentProxyConfiguration() else { return }
let subject = subject
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsCellConfiguration.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsCellConfiguration.swift
index 5b2b48a593..4e2024a67c 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsCellConfiguration.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsCellConfiguration.swift
@@ -10,6 +10,7 @@ import Combine
import MullvadTypes
import UIKit
+@MainActor
class MethodSettingsCellConfiguration {
private let subject: CurrentValueSubject<AccessMethodViewModel, Never>
private let tableView: UITableView
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsDataSourceConfiguration.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsDataSourceConfiguration.swift
index 7e88cfc1c5..3b360ec13f 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsDataSourceConfiguration.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsDataSourceConfiguration.swift
@@ -8,6 +8,7 @@
import UIKit
+@MainActor
class MethodSettingsDataSourceConfiguration {
private let dataSource: UITableViewDiffableDataSource<
MethodSettingsSectionIdentifier,
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift
index 7828a37ba8..58763a2551 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift
@@ -298,7 +298,9 @@ class MethodSettingsViewController: UITableViewController {
saveBarButton.isEnabled = false
interactor.startProxyConfigurationTest { [weak self] _ in
- self?.onTestCompleted()
+ Task { @MainActor in
+ self?.onTestCompleted()
+ }
}
}
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/ProxyConfigurationInteractorProtocol.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/ProxyConfigurationInteractorProtocol.swift
index cd7d24d2ae..80a1076fc7 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/ProxyConfigurationInteractorProtocol.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/ProxyConfigurationInteractorProtocol.swift
@@ -18,7 +18,7 @@ protocol ProxyConfigurationInteractorProtocol {
/// the UI accordingly.
///
/// - Parameter completion: completion handler receiving `true` if the test succeeded, otherwise `false`.
- func startProxyConfigurationTest(_ completion: ((Bool) -> Void)?)
+ func startProxyConfigurationTest(_ completion: (@Sendable (Bool) -> Void)?)
/// Cancel currently running configuration test.
/// The interactor is expected to reset the testing status to the initial.
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodCoordinator.swift
index f5383c6d19..346c14158a 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodCoordinator.swift
@@ -130,7 +130,7 @@ class ListAccessMethodCoordinator: Coordinator, Presenting, SettingsChildCoordin
}
}
-extension ListAccessMethodCoordinator: ListAccessMethodViewControllerDelegate {
+extension ListAccessMethodCoordinator: @preconcurrency ListAccessMethodViewControllerDelegate {
func controllerShouldShowAbout(_ controller: ListAccessMethodViewController) {
about()
}
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/AccessMethodProtocolPicker.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/AccessMethodProtocolPicker.swift
index e11246635c..38d595bd29 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/AccessMethodProtocolPicker.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/AccessMethodProtocolPicker.swift
@@ -10,6 +10,7 @@ import MullvadSettings
import UIKit
/// Type implementing the access method protocol picker.
+@MainActor
struct AccessMethodProtocolPicker {
/// The navigation controller used for presenting the picker.
let navigationController: UINavigationController
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/ShadowsocksCipherPicker.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/ShadowsocksCipherPicker.swift
index c858aa4726..97721651c8 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/ShadowsocksCipherPicker.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/ShadowsocksCipherPicker.swift
@@ -10,6 +10,7 @@ import MullvadSettings
import UIKit
/// Type implementing the shadowsocks cipher picker.
+@MainActor
struct ShadowsocksCipherPicker {
/// The navigation controller used for presenting the picker.
let navigationController: UINavigationController
diff --git a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift
index b2f9fe5164..a5cd3d484c 100644
--- a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift
@@ -40,7 +40,7 @@ class IPOverrideCoordinator: Coordinator, Presenting, SettingsChildCoordinator {
}
}
-extension IPOverrideCoordinator: IPOverrideViewControllerDelegate {
+extension IPOverrideCoordinator: @preconcurrency IPOverrideViewControllerDelegate {
func presentImportTextController() {
let viewController = IPOverrideTextViewController(interactor: interactor)
let customNavigationController = CustomNavigationController(rootViewController: viewController)
diff --git a/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
index f8c501ff11..2bf076115d 100644
--- a/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
@@ -38,12 +38,13 @@ enum SettingsNavigationRoute: Equatable {
}
/// Top-level settings coordinator.
+@MainActor
final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsViewControllerDelegate,
- UINavigationControllerDelegate {
+ UINavigationControllerDelegate, Sendable {
private let logger = Logger(label: "SettingsNavigationCoordinator")
private var currentRoute: SettingsNavigationRoute?
- private var modalRoute: SettingsNavigationRoute?
+ nonisolated(unsafe) private var modalRoute: SettingsNavigationRoute?
private let interactorFactory: SettingsInteractorFactory
private var viewControllerFactory: SettingsViewControllerFactory?
@@ -61,7 +62,7 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
) -> Void)?
/// Event handler invoked when coordinator and its view hierarchy should be dismissed.
- var didFinish: ((SettingsCoordinator) -> Void)?
+ nonisolated(unsafe) var didFinish: (@Sendable (SettingsCoordinator) -> Void)?
/// Designated initializer.
/// - Parameters:
@@ -109,7 +110,11 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
/// - route: the route to present.
/// - animated: whether transition should be animated.
/// - completion: a completion handler, typically called immediately for horizontal navigation and
- func navigate(to route: SettingsNavigationRoute, animated: Bool, completion: (() -> Void)? = nil) {
+ func navigate(
+ to route: SettingsNavigationRoute,
+ animated: Bool,
+ completion: (@Sendable @MainActor () -> Void)? = nil
+ ) {
switch route {
case .root:
popToRoot(animated: animated)
@@ -182,15 +187,17 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
// MARK: - SettingsViewControllerDelegate
- func settingsViewControllerDidFinish(_ controller: SettingsViewController) {
+ nonisolated func settingsViewControllerDidFinish(_ controller: SettingsViewController) {
didFinish?(self)
}
- func settingsViewController(
+ nonisolated func settingsViewController(
_ controller: SettingsViewController,
didRequestRoutePresentation route: SettingsNavigationRoute
) {
- navigate(to: route, animated: true)
+ Task {
+ await navigate(to: route, animated: true)
+ }
}
// MARK: - Route handling
diff --git a/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift b/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift
index 69a7127d28..829bb05b58 100644
--- a/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift
@@ -11,6 +11,7 @@ import Routing
import SwiftUI
import UIKit
+@MainActor
struct SettingsViewControllerFactory {
/// The result of creating a child representing a route.
enum MakeChildResult {
diff --git a/ios/MullvadVPN/Coordinators/SetupAccountCompletedCoordinator.swift b/ios/MullvadVPN/Coordinators/SetupAccountCompletedCoordinator.swift
index 93c5532ede..6101e4be40 100644
--- a/ios/MullvadVPN/Coordinators/SetupAccountCompletedCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/SetupAccountCompletedCoordinator.swift
@@ -34,7 +34,7 @@ class SetupAccountCompletedCoordinator: Coordinator, Presenting {
}
}
-extension SetupAccountCompletedCoordinator: SetupAccountCompletedControllerDelegate {
+extension SetupAccountCompletedCoordinator: @preconcurrency SetupAccountCompletedControllerDelegate {
func didRequestToSeePrivacy(controller: SetupAccountCompletedController) {
presentChild(SafariCoordinator(url: ApplicationConfiguration.privacyGuidesURL), animated: true)
}
diff --git a/ios/MullvadVPN/Coordinators/VPNSettingsCoordinator.swift b/ios/MullvadVPN/Coordinators/VPNSettingsCoordinator.swift
index 8d2db23e57..ba9c258422 100644
--- a/ios/MullvadVPN/Coordinators/VPNSettingsCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/VPNSettingsCoordinator.swift
@@ -42,7 +42,7 @@ class VPNSettingsCoordinator: Coordinator, Presenting, SettingsChildCoordinator
}
}
-extension VPNSettingsCoordinator: VPNSettingsViewControllerDelegate {
+extension VPNSettingsCoordinator: @preconcurrency VPNSettingsViewControllerDelegate {
func showIPOverrides() {
let coordinator = IPOverrideCoordinator(
navigationController: navigationController,
diff --git a/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift b/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift
index 7ae0f7876e..0f21f39f1c 100644
--- a/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift
@@ -80,7 +80,7 @@ final class WelcomeCoordinator: Coordinator, Poppable, Presenting {
}
}
-extension WelcomeCoordinator: WelcomeViewControllerDelegate {
+extension WelcomeCoordinator: @preconcurrency WelcomeViewControllerDelegate {
func didRequestToShowInfo(controller: WelcomeViewController) {
let message = NSLocalizedString(
"WELCOME_DEVICE_CONCEPT_TEXT_DIALOG",
diff --git a/ios/MullvadVPN/Extensions/UITextField+Appearance.swift b/ios/MullvadVPN/Extensions/UITextField+Appearance.swift
index 16bfb0cf5e..a033974e94 100644
--- a/ios/MullvadVPN/Extensions/UITextField+Appearance.swift
+++ b/ios/MullvadVPN/Extensions/UITextField+Appearance.swift
@@ -9,6 +9,7 @@
import UIKit
extension UITextField {
+ @MainActor
struct SearchTextFieldAppearance {
let placeholderTextColor: UIColor
let textColor: UIColor
diff --git a/ios/MullvadVPN/Extensions/UIView+AutoLayoutBuilder.swift b/ios/MullvadVPN/Extensions/UIView+AutoLayoutBuilder.swift
index d1b05242bd..b44e35793b 100644
--- a/ios/MullvadVPN/Extensions/UIView+AutoLayoutBuilder.swift
+++ b/ios/MullvadVPN/Extensions/UIView+AutoLayoutBuilder.swift
@@ -19,8 +19,8 @@ protocol AutoLayoutAnchorsProtocol {
var trailingAnchor: NSLayoutXAxisAnchor { get }
}
-extension UIView: AutoLayoutAnchorsProtocol {}
-extension UILayoutGuide: AutoLayoutAnchorsProtocol {}
+extension UIView: @preconcurrency AutoLayoutAnchorsProtocol {}
+extension UILayoutGuide: @preconcurrency AutoLayoutAnchorsProtocol {}
extension UIView {
/**
@@ -139,6 +139,7 @@ extension UIView {
/**
Add subviews using AutoLayout and configure constraints.
*/
+ @MainActor
func addConstrainedSubviews(
_ subviews: [UIView],
@AutoLayoutBuilder builder: () -> [NSLayoutConstraint]
@@ -201,6 +202,7 @@ struct PinnableEdges {
lhs.rectEdge == rhs.rectEdge
}
+ @MainActor
func makeConstraint(
firstView: AutoLayoutAnchorsProtocol,
secondView: AutoLayoutAnchorsProtocol
@@ -256,6 +258,7 @@ struct PinnableEdges {
/**
Returns new constraints pinning edges of the corresponding views.
*/
+ @MainActor
func makeConstraints(
firstView: AutoLayoutAnchorsProtocol,
secondView: AutoLayoutAnchorsProtocol
diff --git a/ios/MullvadVPN/Extensions/View+Size.swift b/ios/MullvadVPN/Extensions/View+Size.swift
index 34be86795b..3710923753 100644
--- a/ios/MullvadVPN/Extensions/View+Size.swift
+++ b/ios/MullvadVPN/Extensions/View+Size.swift
@@ -24,8 +24,8 @@ extension View {
}
}
-private struct ViewSizeKey: PreferenceKey {
- static var defaultValue: CGSize = .zero
+private struct ViewSizeKey: PreferenceKey, Sendable {
+ nonisolated(unsafe) static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift
index 6c43b9d7c1..cbe95c1ce6 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift
@@ -10,7 +10,8 @@ import Foundation
import MullvadSettings
import MullvadTypes
-final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppNotificationProvider {
+final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppNotificationProvider,
+ @unchecked Sendable {
private var accountExpiry = AccountExpiry()
private var tunnelObserver: TunnelBlockObserver?
private var timer: DispatchSourceTimer?
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift
index 16987abc28..e0e69f747d 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift
@@ -10,7 +10,8 @@ import Foundation
import MullvadSettings
import UserNotifications
-final class AccountExpirySystemNotificationProvider: NotificationProvider, SystemNotificationProvider {
+final class AccountExpirySystemNotificationProvider: NotificationProvider, SystemNotificationProvider,
+ @unchecked Sendable {
private var accountExpiry = AccountExpiry()
private var tunnelObserver: TunnelBlockObserver?
private var accountHasExpired = false
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift
index b9b1a9c8e6..ade1b0eb20 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift
@@ -12,7 +12,7 @@ import UIKit.UIColor
import UIKit.UIFont
final class RegisteredDeviceInAppNotificationProvider: NotificationProvider,
- InAppNotificationProvider {
+ InAppNotificationProvider, @unchecked Sendable {
// MARK: - private properties
private let tunnelManager: TunnelManager
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/TunnelStatusNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/TunnelStatusNotificationProvider.swift
index 3e98da16c7..8a0a3b88ce 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/TunnelStatusNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/TunnelStatusNotificationProvider.swift
@@ -9,7 +9,7 @@
import Foundation
import PacketTunnelCore
-final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotificationProvider {
+final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotificationProvider, @unchecked Sendable {
private var isWaitingForConnectivity = false
private var noNetwork = false
private var packetTunnelError: BlockedStateReason?
diff --git a/ios/MullvadVPN/Notifications/NotificationManager.swift b/ios/MullvadVPN/Notifications/NotificationManager.swift
index 3f722a6280..378a9bdc1b 100644
--- a/ios/MullvadVPN/Notifications/NotificationManager.swift
+++ b/ios/MullvadVPN/Notifications/NotificationManager.swift
@@ -48,7 +48,7 @@ final class NotificationManager: NotificationProviderDelegate {
}
}
- static let shared = NotificationManager()
+ nonisolated(unsafe) static let shared = NotificationManager()
private init() {}
diff --git a/ios/MullvadVPN/Notifications/NotificationProvider.swift b/ios/MullvadVPN/Notifications/NotificationProvider.swift
index ded3aa6b60..8e82313010 100644
--- a/ios/MullvadVPN/Notifications/NotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/NotificationProvider.swift
@@ -16,7 +16,7 @@ protocol NotificationProviderDelegate: AnyObject {
}
/// Base class for all notification providers.
-class NotificationProvider: NotificationProviderProtocol {
+class NotificationProvider: NotificationProviderProtocol, @unchecked Sendable {
weak var delegate: NotificationProviderDelegate?
/**
@@ -51,7 +51,7 @@ class NotificationProvider: NotificationProviderProtocol {
}
}
- private func dispatchOnMain(_ block: @escaping () -> Void) {
+ private func dispatchOnMain(_ block: @escaping @Sendable () -> Void) {
if Thread.isMainThread {
block()
} else {
diff --git a/ios/MullvadVPN/Operations/ProductsRequestOperation.swift b/ios/MullvadVPN/Operations/ProductsRequestOperation.swift
index fbe02adb2c..f2985c7739 100644
--- a/ios/MullvadVPN/Operations/ProductsRequestOperation.swift
+++ b/ios/MullvadVPN/Operations/ProductsRequestOperation.swift
@@ -11,7 +11,7 @@ import Operations
import StoreKit
final class ProductsRequestOperation: ResultOperation<SKProductsResponse>,
- SKProductsRequestDelegate {
+ SKProductsRequestDelegate, @unchecked Sendable {
private let productIdentifiers: Set<String>
private let maxRetryCount = 10
diff --git a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
index 8b00f9f26a..fa1b6cf6e6 100644
--- a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
+++ b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
@@ -13,10 +13,10 @@ import MullvadTypes
import Operations
import UIKit
-protocol RelayCacheTrackerProtocol {
+protocol RelayCacheTrackerProtocol: Sendable {
func startPeriodicUpdates()
func stopPeriodicUpdates()
- func updateRelays(completionHandler: ((Result<RelaysFetchResult, Error>) -> Void)?) -> Cancellable
+ func updateRelays(completionHandler: (@Sendable (Result<RelaysFetchResult, Error>) -> Void)?) -> Cancellable
func getCachedRelays() throws -> CachedRelays
func getNextUpdateDate() -> Date
func addObserver(_ observer: RelayCacheTrackerObserver)
@@ -24,12 +24,12 @@ protocol RelayCacheTrackerProtocol {
func refreshCachedRelays() throws
}
-final class RelayCacheTracker: RelayCacheTrackerProtocol {
+final class RelayCacheTracker: RelayCacheTrackerProtocol, @unchecked Sendable {
/// Relay update interval.
static let relayUpdateInterval: Duration = .hours(1)
/// Tracker log.
- private let logger = Logger(label: "RelayCacheTracker")
+ nonisolated(unsafe) private let logger = Logger(label: "RelayCacheTracker")
/// Relay cache.
private let cache: RelayCacheProtocol
@@ -164,7 +164,7 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol {
timerSource = nil
}
- func updateRelays(completionHandler: ((Result<RelaysFetchResult, Error>) -> Void)? = nil)
+ func updateRelays(completionHandler: (@Sendable (Result<RelaysFetchResult, Error>) -> Void)? = nil)
-> Cancellable {
let operation = ResultBlockOperation<RelaysFetchResult> { finish in
let cachedRelays = try? self.getCachedRelays()
diff --git a/ios/MullvadVPN/SceneDelegate.swift b/ios/MullvadVPN/SceneDelegate.swift
index a79e02745a..ff5334d853 100644
--- a/ios/MullvadVPN/SceneDelegate.swift
+++ b/ios/MullvadVPN/SceneDelegate.swift
@@ -13,7 +13,7 @@ import MullvadTypes
import Operations
import UIKit
-class SceneDelegate: UIResponder, UIWindowSceneDelegate, SettingsMigrationUIHandler {
+class SceneDelegate: UIResponder, UIWindowSceneDelegate, @preconcurrency SettingsMigrationUIHandler {
private let logger = Logger(label: "SceneDelegate")
var window: UIWindow?
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelInfo.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelInfo.swift
index a1324df09f..a2eee7be0d 100644
--- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelInfo.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelInfo.swift
@@ -11,7 +11,8 @@
import Foundation
import NetworkExtension
-final class SimulatorTunnelProviderSession: SimulatorVPNConnection, VPNTunnelProviderSessionProtocol {
+final class SimulatorTunnelProviderSession: SimulatorVPNConnection, VPNTunnelProviderSessionProtocol,
+ @unchecked Sendable {
func sendProviderMessage(_ messageData: Data, responseHandler: ((Data?) -> Void)?) throws {
SimulatorTunnelProvider.shared.handleAppMessage(
messageData,
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProvider.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProvider.swift
index 64b8b55b17..1d8766bbda 100644
--- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProvider.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProvider.swift
@@ -27,11 +27,11 @@ class SimulatorTunnelProviderDelegate {
}
}
- func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
+ func startTunnel(options: [String: NSObject]?, completionHandler: @escaping @Sendable (Error?) -> Void) {
completionHandler(nil)
}
- func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
+ func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping @Sendable () -> Void) {
completionHandler()
}
@@ -40,11 +40,11 @@ class SimulatorTunnelProviderDelegate {
}
}
-final class SimulatorTunnelProvider {
+final class SimulatorTunnelProvider: Sendable {
static let shared = SimulatorTunnelProvider()
private let lock = NSLock()
- private var _delegate: SimulatorTunnelProviderDelegate?
+ nonisolated(unsafe) private var _delegate: SimulatorTunnelProviderDelegate?
var delegate: SimulatorTunnelProviderDelegate! {
get {
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
index 3dd21b1351..29f9d3c29e 100644
--- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
@@ -8,7 +8,7 @@
#if targetEnvironment(simulator)
-import Foundation
+@preconcurrency import Foundation
import MullvadLogging
import MullvadREST
import MullvadSettings
@@ -16,7 +16,7 @@ import MullvadTypes
import NetworkExtension
import PacketTunnelCore
-final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
+final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate, @unchecked Sendable {
private var observedState: ObservedState = .disconnected
private var selectedRelays: SelectedRelays?
private let urlRequestProxy: URLRequestProxy
@@ -35,7 +35,7 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
override func startTunnel(
options: [String: NSObject]?,
- completionHandler: @escaping (Error?) -> Void
+ completionHandler: @escaping @Sendable (Error?) -> Void
) {
dispatchQueue.async { [weak self] in
guard let self else {
@@ -75,7 +75,7 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
}
}
- override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
+ override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping @Sendable () -> Void) {
dispatchQueue.async { [weak self] in
self?.selectedRelays = nil
self?.observedState = .disconnected
@@ -85,7 +85,7 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
- dispatchQueue.async {
+ dispatchQueue.sync {
do {
let message = try TunnelProviderMessage(messageData: messageData)
@@ -101,7 +101,11 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
}
}
- private func handleProviderMessage(_ message: TunnelProviderMessage, completionHandler: ((Data?) -> Void)?) {
+ private func handleProviderMessage(
+ _ message: TunnelProviderMessage,
+ completionHandler: ((Data?) -> Void)?
+ ) {
+ nonisolated(unsafe) let handler = completionHandler
switch message {
case .getTunnelStatus:
var reply: Data?
@@ -114,7 +118,7 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
)
}
- completionHandler?(reply)
+ handler?(reply)
case let .reconnectTunnel(nextRelay):
reasserting = true
@@ -146,7 +150,7 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
message: "Failed to encode ProxyURLResponse."
)
}
- completionHandler?(reply)
+ handler?(reply)
}
case let .cancelURLRequest(listId):
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderManager.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderManager.swift
index c8bc1fe7fe..cc83c239d0 100644
--- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderManager.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderManager.swift
@@ -13,7 +13,7 @@ import NetworkExtension
final class SimulatorTunnelProviderManager: NSObject, VPNTunnelProviderManagerProtocol {
static let tunnelsLock = NSRecursiveLock()
- fileprivate static var tunnels = [SimulatorTunnelInfo]()
+ nonisolated(unsafe) fileprivate static var tunnels = [SimulatorTunnelInfo]()
private let lock = NSLock()
private var tunnelInfo: SimulatorTunnelInfo
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift
index 5ca0b16f1e..28ceadfaf0 100644
--- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift
@@ -12,7 +12,7 @@ import Foundation
import MullvadREST
import NetworkExtension
-class SimulatorVPNConnection: NSObject, VPNConnectionProtocol {
+class SimulatorVPNConnection: NSObject, VPNConnectionProtocol, @unchecked Sendable {
// Protocol configuration is automatically synced by `SimulatorTunnelInfo`
var protocolConfiguration = NEVPNProtocol()
diff --git a/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift b/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift
index acb32083cd..5da2e911a4 100644
--- a/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift
+++ b/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift
@@ -13,7 +13,8 @@ import MullvadTypes
import Operations
import StoreKit
-class SendStoreReceiptOperation: ResultOperation<REST.CreateApplePaymentResponse>, SKRequestDelegate {
+class SendStoreReceiptOperation: ResultOperation<REST.CreateApplePaymentResponse>, SKRequestDelegate,
+ @unchecked Sendable {
private let apiProxy: APIQuerying
private let accountNumber: String
diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentBlockObserver.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentBlockObserver.swift
index ce3fd61915..cde43da250 100644
--- a/ios/MullvadVPN/StorePaymentManager/StorePaymentBlockObserver.swift
+++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentBlockObserver.swift
@@ -9,7 +9,7 @@
import Foundation
final class StorePaymentBlockObserver: StorePaymentObserver {
- typealias BlockHandler = (StorePaymentManager, StorePaymentEvent) -> Void
+ typealias BlockHandler = @Sendable (StorePaymentManager, StorePaymentEvent) -> Void
private let blockHandler: BlockHandler
diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentEvent.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentEvent.swift
index 7d017e9dd7..ca7a9226ea 100644
--- a/ios/MullvadVPN/StorePaymentManager/StorePaymentEvent.swift
+++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentEvent.swift
@@ -8,10 +8,10 @@
import Foundation
import MullvadREST
-import StoreKit
+@preconcurrency import StoreKit
/// The payment event received by observers implementing ``StorePaymentObserver``.
-enum StorePaymentEvent {
+enum StorePaymentEvent: @unchecked Sendable {
/// The payment is successfully completed.
case finished(StorePaymentCompletion)
@@ -42,7 +42,7 @@ struct StorePaymentCompletion {
}
/// Failed payment metadata.
-struct StorePaymentFailure {
+struct StorePaymentFailure: @unchecked Sendable {
/// Transaction object, if available.
/// May not be available due to account validation failure.
let transaction: SKPaymentTransaction?
diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift
index 8086e42161..b4c06adad9 100644
--- a/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift
+++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift
@@ -10,13 +10,13 @@ import MullvadLogging
import MullvadREST
import MullvadTypes
import Operations
-import StoreKit
+@preconcurrency import StoreKit
import UIKit
/// Manager responsible for handling AppStore payments and passing StoreKit receipts to the backend.
///
/// - Warning: only interact with this object on the main queue.
-final class StorePaymentManager: NSObject, SKPaymentTransactionObserver {
+final class StorePaymentManager: NSObject, SKPaymentTransactionObserver, @unchecked Sendable {
private enum OperationCategory {
static let sendStoreReceipt = "StorePaymentManager.sendStoreReceipt"
static let productsRequest = "StorePaymentManager.productsRequest"
@@ -114,7 +114,7 @@ final class StorePaymentManager: NSObject, SKPaymentTransactionObserver {
/// - Returns: the request cancellation token
func requestProducts(
with productIdentifiers: Set<StoreSubscription>,
- completionHandler: @escaping (Result<SKProductsResponse, Error>) -> Void
+ completionHandler: @escaping @Sendable (Result<SKProductsResponse, Error>) -> Void
) -> Cancellable {
let productIdentifiers = productIdentifiers.productIdentifiersSet
let operation = ProductsRequestOperation(
@@ -172,7 +172,7 @@ final class StorePaymentManager: NSObject, SKPaymentTransactionObserver {
/// - Returns: the request cancellation token.
func restorePurchases(
for accountNumber: String,
- completionHandler: @escaping (Result<REST.CreateApplePaymentResponse, Error>) -> Void
+ completionHandler: @escaping @Sendable (Result<REST.CreateApplePaymentResponse, Error>) -> Void
) -> Cancellable {
logger.debug("Restore purchases.")
@@ -222,7 +222,7 @@ final class StorePaymentManager: NSObject, SKPaymentTransactionObserver {
/// - completionHandler: completion handler invoked on main queue. The completion block Receives `nil` upon success, otherwise an error.
private func validateAccount(
accountNumber: String,
- completionHandler: @escaping (StorePaymentManagerError?) -> Void
+ completionHandler: @escaping @Sendable (StorePaymentManagerError?) -> Void
) {
let accountOperation = ResultBlockOperation<Account>(dispatchQueue: .main) { finish in
self.accountsProxy.getAccountData(accountNumber: accountNumber).execute(
@@ -255,7 +255,7 @@ final class StorePaymentManager: NSObject, SKPaymentTransactionObserver {
private func sendStoreReceipt(
accountNumber: String,
forceRefresh: Bool,
- completionHandler: @escaping (Result<REST.CreateApplePaymentResponse, Error>) -> Void
+ completionHandler: @escaping @Sendable (Result<REST.CreateApplePaymentResponse, Error>) -> Void
) -> Cancellable {
let operation = SendStoreReceiptOperation(
apiProxy: apiProxy,
diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerDelegate.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerDelegate.swift
index 8d24814749..fd6ee850e4 100644
--- a/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerDelegate.swift
+++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerDelegate.swift
@@ -9,7 +9,7 @@
import Foundation
import StoreKit
-protocol StorePaymentManagerDelegate: AnyObject {
+protocol StorePaymentManagerDelegate: AnyObject, Sendable {
/// Return the account number associated with the payment.
/// Usually called for unfinished transactions coming back after the app was restarted.
func storePaymentManager(_ manager: StorePaymentManager, didRequestAccountTokenFor payment: SKPayment) -> String?
diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentObserver.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentObserver.swift
index 0d6e3584fc..f504f5062a 100644
--- a/ios/MullvadVPN/StorePaymentManager/StorePaymentObserver.swift
+++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentObserver.swift
@@ -8,7 +8,7 @@
import Foundation
-protocol StorePaymentObserver: AnyObject {
+protocol StorePaymentObserver: AnyObject, Sendable {
func storePaymentManager(
_ manager: StorePaymentManager,
didReceiveEvent event: StorePaymentEvent
diff --git a/ios/MullvadVPN/StorePaymentManager/StoreTransactionLog.swift b/ios/MullvadVPN/StorePaymentManager/StoreTransactionLog.swift
index 8d380bdc44..57153e0616 100644
--- a/ios/MullvadVPN/StorePaymentManager/StoreTransactionLog.swift
+++ b/ios/MullvadVPN/StorePaymentManager/StoreTransactionLog.swift
@@ -12,7 +12,7 @@ import MullvadLogging
/// Transaction log responsible for storing and querying processed transactions.
///
/// This class is thread safe.
-final class StoreTransactionLog {
+final class StoreTransactionLog: @unchecked Sendable {
private let logger = Logger(label: "StoreTransactionLog")
private var transactionIdentifiers: Set<String> = []
private let stateLock = NSLock()
diff --git a/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift b/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift
index 713fc4f54d..375a052bc0 100644
--- a/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift
+++ b/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift
@@ -25,7 +25,7 @@ struct PacketTunnelTransport: RESTTransport {
func sendRequest(
_ request: URLRequest,
- completion: @escaping (Data?, URLResponse?, Error?) -> Void
+ completion: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void
) -> Cancellable {
let proxyRequest = ProxyURLRequest(
id: UUID(),
diff --git a/ios/MullvadVPN/TunnelManager/BackgroundTaskProvider.swift b/ios/MullvadVPN/TunnelManager/BackgroundTaskProvider.swift
index de826634e8..86529dbf76 100644
--- a/ios/MullvadVPN/TunnelManager/BackgroundTaskProvider.swift
+++ b/ios/MullvadVPN/TunnelManager/BackgroundTaskProvider.swift
@@ -12,7 +12,7 @@ import Foundation
import UIKit
@available(iOSApplicationExtension, unavailable)
-public protocol BackgroundTaskProviding {
+public protocol BackgroundTaskProviding: Sendable {
var backgroundTimeRemaining: TimeInterval { get }
#if compiler(>=6)
nonisolated
@@ -31,9 +31,9 @@ public protocol BackgroundTaskProviding {
}
@available(iOSApplicationExtension, unavailable)
-public class BackgroundTaskProvider: BackgroundTaskProviding {
- public var backgroundTimeRemaining: TimeInterval
- weak var application: UIApplication!
+public final class BackgroundTaskProvider: BackgroundTaskProviding {
+ nonisolated(unsafe) public var backgroundTimeRemaining: TimeInterval
+ nonisolated(unsafe) weak var application: UIApplication!
public init(backgroundTimeRemaining: TimeInterval, application: UIApplication) {
self.backgroundTimeRemaining = backgroundTimeRemaining
diff --git a/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift b/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift
index 54f14355a7..d43a92fcc6 100644
--- a/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift
@@ -12,7 +12,7 @@ import MullvadSettings
import MullvadTypes
import Operations
-class LoadTunnelConfigurationOperation: ResultOperation<Void> {
+class LoadTunnelConfigurationOperation: ResultOperation<Void>, @unchecked Sendable {
private let logger = Logger(label: "LoadTunnelConfigurationOperation")
private let interactor: TunnelInteractor
diff --git a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
index 674c59d186..fc55fafa9b 100644
--- a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
@@ -14,7 +14,7 @@ import NetworkExtension
import Operations
import PacketTunnelCore
-class MapConnectionStatusOperation: AsyncOperation {
+class MapConnectionStatusOperation: AsyncOperation, @unchecked Sendable {
private let interactor: TunnelInteractor
private let connectionStatus: NEVPNStatus
private var request: Cancellable?
@@ -159,7 +159,7 @@ class MapConnectionStatusOperation: AsyncOperation {
private func fetchTunnelStatus(
tunnel: any TunnelProtocol,
- mapToState: @escaping (ObservedState) -> TunnelState?
+ mapToState: @escaping @Sendable (ObservedState) -> TunnelState?
) {
request = tunnel.getTunnelStatus { [weak self] result in
guard let self else { return }
diff --git a/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift b/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift
index 99d77a1204..2b9c19ce60 100644
--- a/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift
@@ -13,7 +13,7 @@ import MullvadSettings
import MullvadTypes
import Operations
-class RedeemVoucherOperation: ResultOperation<REST.SubmitVoucherResponse> {
+class RedeemVoucherOperation: ResultOperation<REST.SubmitVoucherResponse>, @unchecked Sendable {
private let logger = Logger(label: "RedeemVoucherOperation")
private let interactor: TunnelInteractor
diff --git a/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift b/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift
index 0176f02b39..7369b937d5 100644
--- a/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift
@@ -14,7 +14,7 @@ import MullvadTypes
import Operations
import WireGuardKitTypes
-class RotateKeyOperation: ResultOperation<Void> {
+class RotateKeyOperation: ResultOperation<Void>, @unchecked Sendable {
private let logger = Logger(label: "RotateKeyOperation")
private let interactor: TunnelInteractor
private let devicesProxy: DeviceHandling
@@ -35,7 +35,7 @@ class RotateKeyOperation: ResultOperation<Void> {
}
// Create key rotation.
- var keyRotation = WgKeyRotation(data: deviceData)
+ nonisolated(unsafe) var keyRotation = WgKeyRotation(data: deviceData)
// Check if key rotation can take place.
guard keyRotation.shouldRotate else {
diff --git a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift
index 47b8562f7f..21485ae2ec 100644
--- a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift
@@ -20,7 +20,7 @@ private let connectingStateWaitDelay: Duration = .seconds(5)
/// Default timeout in seconds.
private let defaultTimeout: Duration = .seconds(5)
-final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output> {
+final class SendTunnelProviderMessageOperation<Output: Sendable>: ResultOperation<Output>, @unchecked Sendable {
typealias DecoderHandler = (Data?) throws -> Output
private let backgroundTaskProvider: BackgroundTaskProviding
diff --git a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
index db6e1cc814..72fa471683 100644
--- a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
@@ -12,7 +12,7 @@ import MullvadREST
import MullvadSettings
import MullvadTypes
import Operations
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
enum SetAccountAction {
/// Set new account.
@@ -37,7 +37,7 @@ enum SetAccountAction {
}
}
-class SetAccountOperation: ResultOperation<StoredAccountData?> {
+class SetAccountOperation: ResultOperation<StoredAccountData?>, @unchecked Sendable {
private let interactor: TunnelInteractor
private let accountsProxy: RESTAccountHandling
private let devicesProxy: DeviceHandling
@@ -108,7 +108,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
Does nothing if device is already logged out.
*/
- private func startLogoutFlow(completion: @escaping () -> Void) {
+ private func startLogoutFlow(completion: @escaping @Sendable () -> Void) {
switch interactor.deviceState {
case let .loggedIn(accountData, deviceData):
deleteDevice(accountNumber: accountData.number, deviceIdentifier: deviceData.identifier) { [self] _ in
@@ -129,7 +129,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
1. Create new account via API.
2. Call `continueLoginFlow()` passing the result of account creation request.
*/
- private func startNewAccountFlow(completion: @escaping (Result<StoredAccountData, Error>) -> Void) {
+ private func startNewAccountFlow(completion: @escaping @Sendable (Result<StoredAccountData, Error>) -> Void) {
createAccount { [self] result in
continueLoginFlow(result, completion: completion)
}
@@ -143,7 +143,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
*/
private func startExistingAccountFlow(
accountNumber: String,
- completion: @escaping (Result<StoredAccountData, Error>) -> Void
+ completion: @escaping @Sendable (Result<StoredAccountData, Error>) -> Void
) {
getAccount(accountNumber: accountNumber) { [self] result in
continueLoginFlow(result, completion: completion)
@@ -158,7 +158,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
*/
private func startDeleteAccountFlow(
accountNumber: String,
- completion: @escaping (Result<Void, Error>) -> Void
+ completion: @escaping @Sendable (Result<Void, Error>) -> Void
) {
deleteAccount(accountNumber: accountNumber) { [self] result in
if result.isSuccess {
@@ -179,7 +179,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
*/
private func continueLoginFlow(
_ result: Result<StoredAccountData, Error>,
- completion: @escaping (Result<StoredAccountData, Error>) -> Void
+ completion: @escaping @Sendable (Result<StoredAccountData, Error>) -> Void
) {
do {
let accountData = try result.get()
@@ -234,7 +234,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
}
/// Create new account and produce `StoredAccountData` upon success.
- private func createAccount(completion: @escaping (Result<StoredAccountData, Error>) -> Void) {
+ private func createAccount(completion: @escaping @Sendable (Result<StoredAccountData, Error>) -> Void) {
logger.debug("Create new account...")
let task = accountsProxy.createAccount(retryStrategy: .default) { [self] result in
@@ -261,7 +261,10 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
}
/// Get account data from the API and produce `StoredAccountData` upon success.
- private func getAccount(accountNumber: String, completion: @escaping (Result<StoredAccountData, Error>) -> Void) {
+ private func getAccount(
+ accountNumber: String,
+ completion: @escaping @Sendable (Result<StoredAccountData, Error>) -> Void
+ ) {
logger.debug("Request account data...")
let task = accountsProxy.getAccountData(accountNumber: accountNumber).execute(
@@ -290,7 +293,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
}
/// Delete account.
- private func deleteAccount(accountNumber: String, completion: @escaping (Result<Void, Error>) -> Void) {
+ private func deleteAccount(accountNumber: String, completion: @escaping @Sendable (Result<Void, Error>) -> Void) {
logger.debug("Delete account...")
let task = accountsProxy.deleteAccount(
@@ -312,7 +315,11 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
}
/// Delete device from API.
- private func deleteDevice(accountNumber: String, deviceIdentifier: String, completion: @escaping (Error?) -> Void) {
+ private func deleteDevice(
+ accountNumber: String,
+ deviceIdentifier: String,
+ completion: @escaping @Sendable (Error?) -> Void
+ ) {
logger.debug("Delete current device...")
let task = devicesProxy.deleteDevice(
@@ -346,7 +353,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
2. Reset device staate to logged out and persist it.
3. Remove VPN configuration and release an instance of `Tunnel` object.
*/
- private func unsetDeviceState(completion: @escaping () -> Void) {
+ private func unsetDeviceState(completion: @escaping @Sendable () -> Void) {
// Tell the caller to unsubscribe from VPN status notifications.
interactor.prepareForVPNConfigurationDeletion()
@@ -379,7 +386,10 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
}
/// Create new private key and create new device via API.
- private func createDevice(accountNumber: String, completion: @escaping (Result<NewDevice, Error>) -> Void) {
+ private func createDevice(
+ accountNumber: String,
+ completion: @escaping @Sendable (Result<NewDevice, Error>) -> Void
+ ) {
let privateKey = PrivateKey()
let request = REST.CreateDeviceRequest(publicKey: privateKey.publicKey, hijackDNS: false)
@@ -417,7 +427,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
private func findDevice(
accountNumber: String,
publicKey: PublicKey,
- completion: @escaping (Result<Device?, Error>) -> Void
+ completion: @escaping @Sendable (Result<Device?, Error>) -> Void
) {
let task = devicesProxy.getDevices(accountNumber: accountNumber, retryStrategy: .default) { [self] result in
dispatchQueue.async { [self] in
diff --git a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
index c6b712061c..923b33ad36 100644
--- a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
@@ -13,7 +13,7 @@ import NetworkExtension
import Operations
import PacketTunnelCore
-class StartTunnelOperation: ResultOperation<Void> {
+class StartTunnelOperation: ResultOperation<Void>, @unchecked Sendable {
typealias EncodeErrorHandler = (Error) -> Void
private let interactor: TunnelInteractor
@@ -58,7 +58,7 @@ class StartTunnelOperation: ResultOperation<Void> {
}
}
- private func makeTunnelProviderAndStartTunnel(completionHandler: @escaping (Error?) -> Void) {
+ private func makeTunnelProviderAndStartTunnel(completionHandler: @escaping @Sendable (Error?) -> Void) {
makeTunnelProvider { result in
self.dispatchQueue.async {
do {
@@ -100,7 +100,10 @@ class StartTunnelOperation: ResultOperation<Void> {
try tunnel.start(options: tunnelOptions.rawOptions())
}
- private func makeTunnelProvider(completionHandler: @escaping (Result<any TunnelProtocol, Error>) -> Void) {
+ private func makeTunnelProvider(
+ completionHandler: @escaping @Sendable (Result<any TunnelProtocol, Error>)
+ -> Void
+ ) {
let persistentTunnels = interactor.getPersistentTunnels()
let tunnel = persistentTunnels.first ?? interactor.createNewTunnel()
let configuration = Self.makeTunnelConfiguration()
diff --git a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift
index 1e8362c2ca..ba41debe30 100644
--- a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift
@@ -9,7 +9,7 @@
import Foundation
import Operations
-class StopTunnelOperation: ResultOperation<Void> {
+class StopTunnelOperation: ResultOperation<Void>, @unchecked Sendable {
private let interactor: TunnelInteractor
init(
diff --git a/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift b/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift
index 280420d086..40f52b1afe 100644
--- a/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift
+++ b/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift
@@ -25,7 +25,7 @@ extension TunnelProtocol {
/// Request packet tunnel process to reconnect the tunnel with the given relays.
func reconnectTunnel(
to nextRelays: NextRelays,
- completionHandler: @escaping (Result<Void, Error>) -> Void
+ completionHandler: @escaping @Sendable (Result<Void, Error>) -> Void
) -> Cancellable {
let operation = SendTunnelProviderMessageOperation(
dispatchQueue: dispatchQueue,
@@ -43,7 +43,7 @@ extension TunnelProtocol {
/// Request status from packet tunnel process.
func getTunnelStatus(
- completionHandler: @escaping (Result<ObservedState, Error>) -> Void
+ completionHandler: @escaping @Sendable (Result<ObservedState, Error>) -> Void
) -> Cancellable {
let decoderHandler: (Data?) throws -> ObservedState = { data in
if let data {
@@ -69,7 +69,7 @@ extension TunnelProtocol {
/// Send HTTP request via packet tunnel process bypassing VPN.
func sendRequest(
_ proxyRequest: ProxyURLRequest,
- completionHandler: @escaping (Result<ProxyURLResponse, Error>) -> Void
+ completionHandler: @escaping @Sendable (Result<ProxyURLResponse, Error>) -> Void
) -> Cancellable {
let decoderHandler: (Data?) throws -> ProxyURLResponse = { data in
if let data {
@@ -111,7 +111,7 @@ extension TunnelProtocol {
/// Notify tunnel about private key rotation.
func notifyKeyRotation(
- completionHandler: @escaping (Result<Void, Error>) -> Void
+ completionHandler: @escaping @Sendable (Result<Void, Error>) -> Void
) -> Cancellable {
let operation = SendTunnelProviderMessageOperation(
dispatchQueue: dispatchQueue,
diff --git a/ios/MullvadVPN/TunnelManager/Tunnel.swift b/ios/MullvadVPN/TunnelManager/Tunnel.swift
index 9489998ef6..6324cbc01b 100644
--- a/ios/MullvadVPN/TunnelManager/Tunnel.swift
+++ b/ios/MullvadVPN/TunnelManager/Tunnel.swift
@@ -21,7 +21,7 @@ protocol TunnelStatusObserver {
func tunnel(_ tunnel: any TunnelProtocol, didReceiveStatus status: NEVPNStatus)
}
-protocol TunnelProtocol: AnyObject {
+protocol TunnelProtocol: AnyObject, Sendable {
associatedtype TunnelManagerProtocol: VPNTunnelProviderManagerProtocol
var status: NEVPNStatus { get }
var isOnDemandEnabled: Bool { get set }
@@ -49,7 +49,7 @@ protocol TunnelProtocol: AnyObject {
}
/// Tunnel wrapper class.
-final class Tunnel: TunnelProtocol, Equatable {
+final class Tunnel: TunnelProtocol, Equatable, @unchecked Sendable {
/// Unique identifier assigned to instance at the time of creation.
let identifier = UUID()
diff --git a/ios/MullvadVPN/TunnelManager/TunnelBlockObserver.swift b/ios/MullvadVPN/TunnelManager/TunnelBlockObserver.swift
index 050fb1fdf2..abe9462db7 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelBlockObserver.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelBlockObserver.swift
@@ -9,7 +9,7 @@
import Foundation
import MullvadSettings
-final class TunnelBlockObserver: TunnelObserver {
+final class TunnelBlockObserver: TunnelObserver, @unchecked Sendable {
typealias DidLoadConfigurationHandler = (TunnelManager) -> Void
typealias DidUpdateTunnelStatusHandler = (TunnelManager, TunnelStatus) -> Void
typealias DidUpdateDeviceStateHandler = (
diff --git a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
index cb268ef330..e1f0740dca 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
@@ -25,7 +25,7 @@ protocol TunnelInteractor {
// MARK: - Tunnel status
var tunnelStatus: TunnelStatus { get }
- @discardableResult func updateTunnelStatus(_ block: (inout TunnelStatus) -> Void) -> TunnelStatus
+ @discardableResult func updateTunnelStatus(_ block: @Sendable (inout TunnelStatus) -> Void) -> TunnelStatus
// MARK: - Configuration
diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
index 4703fe3cda..428dcc4fcc 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
@@ -28,8 +28,8 @@ private let establishedTunnelStatusPollInterval: Duration = .seconds(5)
/// A class that provides a convenient interface for VPN tunnels configuration, manipulation and
/// monitoring.
-final class TunnelManager: StorePaymentObserver {
- private enum OperationCategory: String {
+final class TunnelManager: StorePaymentObserver, @unchecked Sendable {
+ private enum OperationCategory: String, Sendable {
case manageTunnel
case deviceStateUpdate
case settingsUpdate
@@ -180,7 +180,7 @@ final class TunnelManager: StorePaymentObserver {
// MARK: - Public methods
- func loadConfiguration(completionHandler: @escaping () -> Void) {
+ func loadConfiguration(completionHandler: @escaping @Sendable () -> Void) {
let loadTunnelOperation = LoadTunnelConfigurationOperation(
dispatchQueue: internalQueue,
interactor: TunnelInteractorProxy(self)
@@ -218,12 +218,13 @@ final class TunnelManager: StorePaymentObserver {
}
func startTunnel(completionHandler: ((Error?) -> Void)? = nil) {
+ nonisolated(unsafe) let nonisolatedCompletionHandler = completionHandler
+
let operation = StartTunnelOperation(
dispatchQueue: internalQueue,
interactor: TunnelInteractorProxy(self),
completionHandler: { [weak self] result in
guard let self else { return }
-
DispatchQueue.main.async {
if let error = result.error {
self.logger.error(
@@ -238,7 +239,7 @@ final class TunnelManager: StorePaymentObserver {
}
}
- completionHandler?(result.error)
+ nonisolatedCompletionHandler?(result.error)
}
}
)
@@ -254,6 +255,7 @@ final class TunnelManager: StorePaymentObserver {
}
func stopTunnel(completionHandler: ((Error?) -> Void)? = nil) {
+ nonisolated(unsafe) let nonisolatedCompletionHandler = completionHandler
let operation = StopTunnelOperation(
dispatchQueue: internalQueue,
interactor: TunnelInteractorProxy(self)
@@ -274,7 +276,7 @@ final class TunnelManager: StorePaymentObserver {
}
}
- completionHandler?(result.error)
+ nonisolatedCompletionHandler?(result.error)
}
}
@@ -288,7 +290,7 @@ final class TunnelManager: StorePaymentObserver {
operationQueue.addOperation(operation)
}
- func reconnectTunnel(selectNewRelay: Bool, completionHandler: ((Error?) -> Void)? = nil) {
+ func reconnectTunnel(selectNewRelay: Bool, completionHandler: (@Sendable (Error?) -> Void)? = nil) {
let operation = AsyncBlockOperation(dispatchQueue: internalQueue) { finish -> Cancellable in
do {
guard let tunnel = self.tunnel else {
@@ -335,7 +337,7 @@ final class TunnelManager: StorePaymentObserver {
private func setAccount(
action: SetAccountAction,
- completionHandler: @escaping (Result<StoredAccountData?, Error>) -> Void
+ completionHandler: @escaping @Sendable (Result<StoredAccountData?, Error>) -> Void
) {
let operation = SetAccountOperation(
dispatchQueue: internalQueue,
@@ -394,7 +396,7 @@ final class TunnelManager: StorePaymentObserver {
_ = try? await setAccount(action: .unset)
}
- func updateAccountData(_ completionHandler: ((Error?) -> Void)? = nil) {
+ func updateAccountData(_ completionHandler: (@Sendable (Error?) -> Void)? = nil) {
let operation = UpdateAccountDataOperation(
dispatchQueue: internalQueue,
interactor: TunnelInteractorProxy(self),
@@ -423,7 +425,7 @@ final class TunnelManager: StorePaymentObserver {
func redeemVoucher(
_ voucherCode: String,
- completion: ((Result<REST.SubmitVoucherResponse, Error>) -> Void)? = nil
+ completion: (@Sendable (Result<REST.SubmitVoucherResponse, Error>) -> Void)? = nil
) -> Cancellable {
let operation = RedeemVoucherOperation(
dispatchQueue: internalQueue,
@@ -453,7 +455,7 @@ final class TunnelManager: StorePaymentObserver {
_ = try await setAccount(action: .delete(accountNumber))
}
- func updateDeviceData(_ completionHandler: ((Error?) -> Void)? = nil) {
+ func updateDeviceData(_ completionHandler: (@Sendable (Error?) -> Void)? = nil) {
let operation = UpdateDeviceDataOperation(
dispatchQueue: internalQueue,
interactor: TunnelInteractorProxy(self),
@@ -480,7 +482,7 @@ final class TunnelManager: StorePaymentObserver {
operationQueue.addOperation(operation)
}
- func rotatePrivateKey(completionHandler: @escaping (Error?) -> Void) -> Cancellable {
+ func rotatePrivateKey(completionHandler: @escaping @Sendable (Error?) -> Void) -> Cancellable {
let operation = RotateKeyOperation(
dispatchQueue: internalQueue,
interactor: TunnelInteractorProxy(self),
@@ -518,7 +520,7 @@ final class TunnelManager: StorePaymentObserver {
return operation
}
- func updateSettings(_ updates: [TunnelSettingsUpdate], completionHandler: (() -> Void)? = nil) {
+ func updateSettings(_ updates: [TunnelSettingsUpdate], completionHandler: (@Sendable () -> Void)? = nil) {
let taskName = "Set " + updates.map(\.subjectName).joined(separator: ", ")
scheduleSettingsUpdate(
taskName: taskName,
@@ -659,7 +661,7 @@ final class TunnelManager: StorePaymentObserver {
}
}
- fileprivate func setTunnelStatus(_ block: (inout TunnelStatus) -> Void) -> TunnelStatus {
+ fileprivate func setTunnelStatus(_ block: @Sendable (inout TunnelStatus) -> Void) -> TunnelStatus {
nslock.lock()
defer { nslock.unlock() }
@@ -676,12 +678,12 @@ final class TunnelManager: StorePaymentObserver {
// Packet tunnel may have attempted or rotated the key.
// In that case we have to reload device state from Keychain as it's likely was modified by packet tunnel.
- let newPacketTunnelKeyRotation = newTunnelStatus.observedState.connectionState?.lastKeyRotation
+ let newPacketTunnelKeyRotation = _tunnelStatus.observedState.connectionState?.lastKeyRotation
if lastPacketTunnelKeyRotation != newPacketTunnelKeyRotation {
lastPacketTunnelKeyRotation = newPacketTunnelKeyRotation
refreshDeviceState()
}
- switch newTunnelStatus.state {
+ switch _tunnelStatus.state {
case .connecting, .reconnecting, .negotiatingEphemeralPeer:
// Start polling tunnel status to keep the relay information up to date
// while the tunnel process is trying to connect.
@@ -709,7 +711,7 @@ final class TunnelManager: StorePaymentObserver {
DispatchQueue.main.async {
self.observerList.notify { observer in
- observer.tunnelManager(self, didUpdateTunnelStatus: newTunnelStatus)
+ observer.tunnelManager(self, didUpdateTunnelStatus: self._tunnelStatus)
}
}
@@ -933,8 +935,8 @@ final class TunnelManager: StorePaymentObserver {
private func scheduleSettingsUpdate(
taskName: String,
- modificationBlock: @escaping (inout LatestTunnelSettings) -> Void,
- completionHandler: (() -> Void)?
+ modificationBlock: @escaping @Sendable (inout LatestTunnelSettings) -> Void,
+ completionHandler: (@Sendable () -> Void)?
) {
let operation = AsyncBlockOperation(dispatchQueue: internalQueue) {
let currentSettings = self._tunnelSettings
@@ -972,8 +974,8 @@ final class TunnelManager: StorePaymentObserver {
private func scheduleDeviceStateUpdate(
taskName: String,
reconnectTunnel: Bool = true,
- modificationBlock: @escaping (inout DeviceState) -> Void,
- completionHandler: (() -> Void)? = nil
+ modificationBlock: @escaping @Sendable (inout DeviceState) -> Void,
+ completionHandler: (@Sendable () -> Void)? = nil
) {
let operation = AsyncBlockOperation(dispatchQueue: internalQueue) {
var deviceState = self.deviceState
@@ -1071,7 +1073,7 @@ final class TunnelManager: StorePaymentObserver {
}
}
- private func unsetTunnelConfiguration(completion: @escaping () -> Void) {
+ private func unsetTunnelConfiguration(completion: @escaping @Sendable () -> Void) {
// Tell the caller to unsubscribe from VPN status notifications.
prepareForVPNConfigurationDeletion()
@@ -1224,7 +1226,7 @@ private struct TunnelInteractorProxy: TunnelInteractor {
tunnelManager.tunnelStatus
}
- func updateTunnelStatus(_ block: (inout TunnelStatus) -> Void) -> TunnelStatus {
+ func updateTunnelStatus(_ block: @Sendable (inout TunnelStatus) -> Void) -> TunnelStatus {
tunnelManager.setTunnelStatus(block)
}
diff --git a/ios/MullvadVPN/TunnelManager/TunnelObserver.swift b/ios/MullvadVPN/TunnelManager/TunnelObserver.swift
index 0ae9fd5f63..5b41a809cc 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelObserver.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelObserver.swift
@@ -9,7 +9,7 @@
import Foundation
import MullvadSettings
-protocol TunnelObserver: AnyObject {
+protocol TunnelObserver: AnyObject, Sendable {
func tunnelManagerDidLoadConfiguration(_ manager: TunnelManager)
func tunnelManager(_ manager: TunnelManager, didUpdateTunnelStatus tunnelStatus: TunnelStatus)
func tunnelManager(
diff --git a/ios/MullvadVPN/TunnelManager/TunnelState.swift b/ios/MullvadVPN/TunnelManager/TunnelState.swift
index b235f8255c..1bc6bc5381 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelState.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelState.swift
@@ -10,10 +10,10 @@ import Foundation
import MullvadREST
import MullvadTypes
import PacketTunnelCore
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
/// A struct describing the tunnel status.
-struct TunnelStatus: Equatable, CustomStringConvertible {
+struct TunnelStatus: Equatable, CustomStringConvertible, Sendable {
/// Tunnel status returned by tunnel process.
var observedState: ObservedState = .disconnected
@@ -38,7 +38,7 @@ struct TunnelStatus: Equatable, CustomStringConvertible {
}
/// An enum that describes the tunnel state.
-enum TunnelState: Equatable, CustomStringConvertible {
+enum TunnelState: Equatable, CustomStringConvertible, Sendable {
enum WaitingForConnectionReason {
/// Tunnel connection is down.
case noConnection
diff --git a/ios/MullvadVPN/TunnelManager/TunnelStatusBlockObserver.swift b/ios/MullvadVPN/TunnelManager/TunnelStatusBlockObserver.swift
index 135e79220c..37ce71a65e 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelStatusBlockObserver.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelStatusBlockObserver.swift
@@ -9,7 +9,7 @@
import Foundation
import NetworkExtension
-final class TunnelStatusBlockObserver: TunnelStatusObserver {
+final class TunnelStatusBlockObserver: TunnelStatusObserver, @unchecked Sendable {
typealias Handler = (any TunnelProtocol, NEVPNStatus) -> Void
private weak var tunnel: (any TunnelProtocol)?
@@ -27,7 +27,7 @@ final class TunnelStatusBlockObserver: TunnelStatusObserver {
}
func tunnel(_ tunnel: any TunnelProtocol, didReceiveStatus status: NEVPNStatus) {
- let block = {
+ let block: @Sendable () -> Void = {
self.handler(tunnel, status)
}
diff --git a/ios/MullvadVPN/TunnelManager/TunnelStore.swift b/ios/MullvadVPN/TunnelManager/TunnelStore.swift
index fa1fb0f3d7..0fcb75dcbe 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelStore.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelStore.swift
@@ -12,14 +12,14 @@ import MullvadTypes
import NetworkExtension
import UIKit
-protocol TunnelStoreProtocol {
+protocol TunnelStoreProtocol: Sendable {
associatedtype TunnelType: TunnelProtocol, Equatable
func getPersistentTunnels() -> [TunnelType]
func createNewTunnel() -> TunnelType
}
/// Wrapper around system VPN tunnels.
-final class TunnelStore: TunnelStoreProtocol, TunnelStatusObserver {
+final class TunnelStore: TunnelStoreProtocol, TunnelStatusObserver, @unchecked Sendable {
typealias TunnelType = Tunnel
private let logger = Logger(label: "TunnelStore")
private let lock = NSLock()
diff --git a/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift b/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift
index 3992f17c90..9c94773239 100644
--- a/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift
@@ -13,7 +13,7 @@ import MullvadSettings
import MullvadTypes
import Operations
-class UpdateAccountDataOperation: ResultOperation<Void> {
+class UpdateAccountDataOperation: ResultOperation<Void>, @unchecked Sendable {
private let logger = Logger(label: "UpdateAccountDataOperation")
private let interactor: TunnelInteractor
private let accountsProxy: RESTAccountHandling
diff --git a/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift b/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift
index 341af90806..6d720987ac 100644
--- a/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift
@@ -14,7 +14,7 @@ import MullvadTypes
import Operations
import WireGuardKitTypes
-class UpdateDeviceDataOperation: ResultOperation<StoredDeviceData> {
+class UpdateDeviceDataOperation: ResultOperation<StoredDeviceData>, @unchecked Sendable {
private let interactor: TunnelInteractor
private let devicesProxy: DeviceHandling
@@ -42,7 +42,7 @@ class UpdateDeviceDataOperation: ResultOperation<StoredDeviceData> {
identifier: deviceData.identifier,
retryStrategy: .default,
completion: { [weak self] result in
- self?.dispatchQueue.async {
+ self?.dispatchQueue.async { [weak self] in
self?.didReceiveDeviceResponse(result: result)
}
}
diff --git a/ios/MullvadVPN/TunnelManager/WgKeyRotation.swift b/ios/MullvadVPN/TunnelManager/WgKeyRotation.swift
index 89474e5afd..f21dc7dedb 100644
--- a/ios/MullvadVPN/TunnelManager/WgKeyRotation.swift
+++ b/ios/MullvadVPN/TunnelManager/WgKeyRotation.swift
@@ -9,13 +9,13 @@
import Foundation
import MullvadSettings
import MullvadTypes
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
/**
Implements manipulations related to marking the beginning and the completion of key rotation, private key creation and other tasks relevant to handling the state of
key rotation.
*/
-struct WgKeyRotation {
+struct WgKeyRotation: Sendable {
/// Private key rotation interval counted from the time when the key was successfully pushed
/// to the backend.
public static let rotationInterval: Duration = .days(30)
diff --git a/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift b/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift
index b282c48cac..fc7cc9a2fa 100644
--- a/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift
+++ b/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift
@@ -13,17 +13,17 @@ import MullvadTypes
import Operations
import StoreKit
-final class AccountInteractor {
+final class AccountInteractor: Sendable {
private let storePaymentManager: StorePaymentManager
let tunnelManager: TunnelManager
let accountsProxy: RESTAccountHandling
let apiProxy: APIQuerying
- var didReceivePaymentEvent: ((StorePaymentEvent) -> Void)?
- var didReceiveDeviceState: ((DeviceState) -> Void)?
+ nonisolated(unsafe) var didReceivePaymentEvent: (@Sendable (StorePaymentEvent) -> Void)?
+ nonisolated(unsafe) var didReceiveDeviceState: (@Sendable (DeviceState) -> Void)?
- private var tunnelObserver: TunnelObserver?
- private var paymentObserver: StorePaymentObserver?
+ nonisolated(unsafe) private var tunnelObserver: TunnelObserver?
+ nonisolated(unsafe) private var paymentObserver: StorePaymentObserver?
init(
storePaymentManager: StorePaymentManager,
@@ -73,7 +73,7 @@ final class AccountInteractor {
func restorePurchases(
for accountNumber: String,
- completionHandler: @escaping (Result<REST.CreateApplePaymentResponse, Error>) -> Void
+ completionHandler: @escaping @Sendable (Result<REST.CreateApplePaymentResponse, Error>) -> Void
) -> Cancellable {
storePaymentManager.restorePurchases(
for: accountNumber,
@@ -83,7 +83,7 @@ final class AccountInteractor {
func requestProducts(
with productIdentifiers: Set<StoreSubscription>,
- completionHandler: @escaping (Result<SKProductsResponse, Error>) -> Void
+ completionHandler: @escaping @Sendable (Result<SKProductsResponse, Error>) -> Void
) -> Cancellable {
storePaymentManager.requestProducts(
with: productIdentifiers,
diff --git a/ios/MullvadVPN/View controllers/Account/AccountViewController.swift b/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
index f814378c2f..0c7ca21532 100644
--- a/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
+++ b/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
@@ -14,7 +14,7 @@ import Operations
import StoreKit
import UIKit
-enum AccountViewControllerAction {
+enum AccountViewControllerAction: Sendable {
case deviceInfo
case finish
case logOut
@@ -23,7 +23,7 @@ enum AccountViewControllerAction {
case restorePurchasesInfo
}
-class AccountViewController: UIViewController {
+class AccountViewController: UIViewController, @unchecked Sendable {
typealias ActionHandler = (AccountViewControllerAction) -> Void
private let interactor: AccountInteractor
@@ -91,11 +91,15 @@ class AccountViewController: UIViewController {
}
interactor.didReceiveDeviceState = { [weak self] deviceState in
- self?.updateView(from: deviceState)
+ Task { @MainActor in
+ self?.updateView(from: deviceState)
+ }
}
interactor.didReceivePaymentEvent = { [weak self] event in
- self?.didReceivePaymentEvent(event)
+ Task { @MainActor in
+ self?.didReceivePaymentEvent(event)
+ }
}
configUI()
addActions()
@@ -157,10 +161,13 @@ class AccountViewController: UIViewController {
let productState: ProductState = completion.value?.products.first
.map { .received($0) } ?? .failed
- self?.setProductState(productState, animated: true)
+ MainActor.assumeIsolated {
+ self?.setProductState(productState, animated: true)
+ }
}
}
+ @MainActor
private func setPaymentState(_ newState: PaymentState, animated: Bool) {
paymentState = newState
@@ -282,18 +289,20 @@ class AccountViewController: UIViewController {
_ = interactor.restorePurchases(for: accountData.number) { [weak self] completion in
guard let self else { return }
- switch completion {
- case let .success(response):
- errorPresenter.showAlertForResponse(response, context: .restoration)
+ Task { @MainActor in
+ switch completion {
+ case let .success(response):
+ errorPresenter.showAlertForResponse(response, context: .restoration)
- case let .failure(error as StorePaymentManagerError):
- errorPresenter.showAlertForError(error, context: .restoration)
+ case let .failure(error as StorePaymentManagerError):
+ errorPresenter.showAlertForError(error, context: .restoration)
- default:
- break
- }
+ default:
+ break
+ }
- setPaymentState(.none, animated: true)
+ setPaymentState(.none, animated: true)
+ }
}
}
diff --git a/ios/MullvadVPN/View controllers/Account/PaymentAlertPresenter.swift b/ios/MullvadVPN/View controllers/Account/PaymentAlertPresenter.swift
index 82820ba3fb..4e62d1a808 100644
--- a/ios/MullvadVPN/View controllers/Account/PaymentAlertPresenter.swift
+++ b/ios/MullvadVPN/View controllers/Account/PaymentAlertPresenter.swift
@@ -9,13 +9,14 @@
import MullvadREST
import Routing
+@MainActor
struct PaymentAlertPresenter {
let alertContext: any Presenting
func showAlertForError(
_ error: StorePaymentManagerError,
context: REST.CreateApplePaymentResponse.Context,
- completion: (() -> Void)? = nil
+ completion: (@Sendable () -> Void)? = nil
) {
let presentation = AlertPresentation(
id: "payment-error-alert",
@@ -63,7 +64,7 @@ struct PaymentAlertPresenter {
func showAlertForResponse(
_ response: REST.CreateApplePaymentResponse,
context: REST.CreateApplePaymentResponse.Context,
- completion: (() -> Void)? = nil
+ completion: (@Sendable () -> Void)? = nil
) {
guard case .noTimeAdded = response else {
completion?()
diff --git a/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionInteractor.swift b/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionInteractor.swift
index f0cf80ab87..2b4ef5fa29 100644
--- a/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionInteractor.swift
+++ b/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionInteractor.swift
@@ -26,7 +26,7 @@ enum AccountDeletionError: LocalizedError {
}
}
-class AccountDeletionInteractor {
+final class AccountDeletionInteractor: Sendable {
private let tunnelManager: TunnelManager
var viewModel: AccountDeletionViewModel {
AccountDeletionViewModel(
diff --git a/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionViewController.swift b/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionViewController.swift
index 6479134b14..0ca4f7cbcd 100644
--- a/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionViewController.swift
+++ b/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionViewController.swift
@@ -14,6 +14,7 @@ protocol AccountDeletionViewControllerDelegate: AnyObject {
func deleteAccountDidCancel(controller: AccountDeletionViewController)
}
+@MainActor
class AccountDeletionViewController: UIViewController {
private lazy var contentView: AccountDeletionContentView = {
let view = AccountDeletionContentView()
@@ -22,7 +23,7 @@ class AccountDeletionViewController: UIViewController {
}()
weak var delegate: AccountDeletionViewControllerDelegate?
- var interactor: AccountDeletionInteractor
+ let interactor: AccountDeletionInteractor
init(interactor: AccountDeletionInteractor) {
self.interactor = interactor
@@ -75,7 +76,7 @@ class AccountDeletionViewController: UIViewController {
}
}
-extension AccountDeletionViewController: AccountDeletionContentViewDelegate {
+extension AccountDeletionViewController: @preconcurrency AccountDeletionContentViewDelegate {
func didTapCancelButton(contentView: AccountDeletionContentView, button: AppButton) {
contentView.isEditing = false
delegate?.deleteAccountDidCancel(controller: self)
diff --git a/ios/MullvadVPN/View controllers/Alert/AlertPresenter.swift b/ios/MullvadVPN/View controllers/Alert/AlertPresenter.swift
index 854076784f..8cea541cd1 100644
--- a/ios/MullvadVPN/View controllers/Alert/AlertPresenter.swift
+++ b/ios/MullvadVPN/View controllers/Alert/AlertPresenter.swift
@@ -8,6 +8,7 @@
import Routing
+@MainActor
struct AlertPresenter {
weak var context: (any Presenting)?
diff --git a/ios/MullvadVPN/View controllers/CreationAccount/Completed/SetupAccountCompletedController.swift b/ios/MullvadVPN/View controllers/CreationAccount/Completed/SetupAccountCompletedController.swift
index a7f4cb91ea..116814028d 100644
--- a/ios/MullvadVPN/View controllers/CreationAccount/Completed/SetupAccountCompletedController.swift
+++ b/ios/MullvadVPN/View controllers/CreationAccount/Completed/SetupAccountCompletedController.swift
@@ -8,7 +8,7 @@
import UIKit
-protocol SetupAccountCompletedControllerDelegate: AnyObject {
+protocol SetupAccountCompletedControllerDelegate: AnyObject, Sendable {
func didRequestToSeePrivacy(controller: SetupAccountCompletedController)
func didRequestToStartTheApp(controller: SetupAccountCompletedController)
}
@@ -51,7 +51,7 @@ class SetupAccountCompletedController: UIViewController, RootContainment {
}
}
-extension SetupAccountCompletedController: SetupAccountCompletedContentViewDelegate {
+extension SetupAccountCompletedController: @preconcurrency SetupAccountCompletedContentViewDelegate {
func didTapPrivacyButton(view: SetupAccountCompletedContentView, button: AppButton) {
delegate?.didRequestToSeePrivacy(controller: self)
}
diff --git a/ios/MullvadVPN/View controllers/CreationAccount/InAppPurchaseInteractor.swift b/ios/MullvadVPN/View controllers/CreationAccount/InAppPurchaseInteractor.swift
index 567df8fc89..e4a0c209e6 100644
--- a/ios/MullvadVPN/View controllers/CreationAccount/InAppPurchaseInteractor.swift
+++ b/ios/MullvadVPN/View controllers/CreationAccount/InAppPurchaseInteractor.swift
@@ -14,7 +14,7 @@ protocol InAppPurchaseViewControllerDelegate: AnyObject {
func didEndPayment()
}
-class InAppPurchaseInteractor {
+class InAppPurchaseInteractor: @unchecked Sendable {
let storePaymentManager: StorePaymentManager
var didFinishPayment: ((InAppPurchaseInteractor, StorePaymentEvent) -> Void)?
weak var viewControllerDelegate: InAppPurchaseViewControllerDelegate?
diff --git a/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeContentView.swift b/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeContentView.swift
index db3145ac68..17c9de2d2b 100644
--- a/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeContentView.swift
+++ b/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeContentView.swift
@@ -8,18 +8,18 @@
import UIKit
-protocol WelcomeContentViewDelegate: AnyObject {
+protocol WelcomeContentViewDelegate: AnyObject, Sendable {
func didTapPurchaseButton(welcomeContentView: WelcomeContentView, button: AppButton)
func didTapRedeemVoucherButton(welcomeContentView: WelcomeContentView, button: AppButton)
func didTapInfoButton(welcomeContentView: WelcomeContentView, button: UIButton)
}
-struct WelcomeViewModel {
+struct WelcomeViewModel: Sendable {
let deviceName: String
let accountNumber: String
}
-final class WelcomeContentView: UIView {
+final class WelcomeContentView: UIView, Sendable {
private let titleLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
diff --git a/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeInteractor.swift b/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeInteractor.swift
index 80be852f65..11bddcf5aa 100644
--- a/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeInteractor.swift
+++ b/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeInteractor.swift
@@ -11,7 +11,7 @@ import MullvadLogging
import MullvadTypes
import StoreKit
-final class WelcomeInteractor {
+final class WelcomeInteractor: @unchecked Sendable {
private let storePaymentManager: StorePaymentManager
private let tunnelManager: TunnelManager
diff --git a/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeViewController.swift b/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeViewController.swift
index 9de0ce8334..909c22b824 100644
--- a/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeViewController.swift
+++ b/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeViewController.swift
@@ -83,7 +83,7 @@ class WelcomeViewController: UIViewController, RootContainment {
}
}
-extension WelcomeViewController: WelcomeContentViewDelegate {
+extension WelcomeViewController: @preconcurrency WelcomeContentViewDelegate {
func didTapInfoButton(welcomeContentView: WelcomeContentView, button: UIButton) {
delegate?.didRequestToShowInfo(controller: self)
}
@@ -103,7 +103,7 @@ extension WelcomeViewController: WelcomeContentViewDelegate {
}
}
-extension WelcomeViewController: InAppPurchaseViewControllerDelegate {
+extension WelcomeViewController: @preconcurrency InAppPurchaseViewControllerDelegate {
func didBeginPayment() {
contentView.isPurchasing = true
}
diff --git a/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementContentView.swift b/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementContentView.swift
index 46386165c1..14bd8cd0fb 100644
--- a/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementContentView.swift
+++ b/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementContentView.swift
@@ -97,7 +97,7 @@ class DeviceManagementContentView: UIView {
return stackView
}()
- var handleDeviceDeletion: ((DeviceViewModel, @escaping () -> Void) -> Void)?
+ var handleDeviceDeletion: (@Sendable (DeviceViewModel, @escaping @Sendable () -> Void) -> Void)?
private var currentDeviceModels = [DeviceViewModel]()
@@ -118,11 +118,11 @@ class DeviceManagementContentView: UIView {
}
private func addViews() {
- [scrollView, buttonStackView].forEach(addSubview)
+ try? [scrollView, buttonStackView].forEach(addSubview)
scrollView.addSubview(scrollContentView)
- [statusImageView, titleLabel, messageLabel, deviceStackView]
+ try? [statusImageView, titleLabel, messageLabel, deviceStackView]
.forEach(scrollContentView.addSubview)
}
@@ -262,7 +262,9 @@ class DeviceManagementContentView: UIView {
view.showsActivityIndicator = true
self?.handleDeviceDeletion?(view.viewModel) {
- view.showsActivityIndicator = false
+ Task { @MainActor in
+ view.showsActivityIndicator = false
+ }
}
}
diff --git a/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementInteractor.swift b/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementInteractor.swift
index 581b5e0d0a..a42590c342 100644
--- a/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementInteractor.swift
+++ b/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementInteractor.swift
@@ -11,7 +11,7 @@ import MullvadREST
import MullvadTypes
import Operations
-class DeviceManagementInteractor {
+class DeviceManagementInteractor: @unchecked Sendable {
private let devicesProxy: DeviceHandling
private let accountNumber: String
@@ -21,7 +21,7 @@ class DeviceManagementInteractor {
}
@discardableResult
- func getDevices(_ completionHandler: @escaping (Result<[Device], Error>) -> Void) -> Cancellable {
+ func getDevices(_ completionHandler: @escaping @Sendable (Result<[Device], Error>) -> Void) -> Cancellable {
devicesProxy.getDevices(
accountNumber: accountNumber,
retryStrategy: .default,
@@ -30,7 +30,10 @@ class DeviceManagementInteractor {
}
@discardableResult
- func deleteDevice(_ identifier: String, completionHandler: @escaping (Result<Bool, Error>) -> Void) -> Cancellable {
+ func deleteDevice(
+ _ identifier: String,
+ completionHandler: @escaping @Sendable (Result<Bool, Error>) -> Void
+ ) -> Cancellable {
devicesProxy.deleteDevice(
accountNumber: accountNumber,
identifier: identifier,
diff --git a/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementViewController.swift b/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementViewController.swift
index b36c48b2e7..546d6bd044 100644
--- a/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementViewController.swift
+++ b/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementViewController.swift
@@ -6,13 +6,13 @@
// Copyright © 2022 Mullvad VPN AB. All rights reserved.
//
-import MullvadLogging
+@preconcurrency import MullvadLogging
import MullvadREST
import MullvadTypes
import Operations
import UIKit
-protocol DeviceManagementViewControllerDelegate: AnyObject {
+protocol DeviceManagementViewControllerDelegate: AnyObject, Sendable {
func deviceManagementViewControllerDidFinish(_ controller: DeviceManagementViewController)
func deviceManagementViewControllerDidCancel(_ controller: DeviceManagementViewController)
}
@@ -38,8 +38,8 @@ class DeviceManagementViewController: UIViewController, RootContainment {
return contentView
}()
- private let logger = Logger(label: "DeviceManagementViewController")
- private let interactor: DeviceManagementInteractor
+ nonisolated(unsafe) private let logger = Logger(label: "DeviceManagementViewController")
+ let interactor: DeviceManagementInteractor
private let alertPresenter: AlertPresenter
init(interactor: DeviceManagementInteractor, alertPresenter: AlertPresenter) {
@@ -73,7 +73,9 @@ class DeviceManagementViewController: UIViewController, RootContainment {
)
contentView.handleDeviceDeletion = { [weak self] viewModel, finish in
- self?.handleDeviceDeletion(viewModel, completionHandler: finish)
+ Task { @MainActor in
+ self?.handleDeviceDeletion(viewModel, completionHandler: finish)
+ }
}
NSLayoutConstraint.activate([
@@ -86,7 +88,7 @@ class DeviceManagementViewController: UIViewController, RootContainment {
func fetchDevices(
animateUpdates: Bool,
- completionHandler: ((Result<Void, Error>) -> Void)? = nil
+ completionHandler: (@Sendable (Result<Void, Error>) -> Void)? = nil
) {
interactor.getDevices { [weak self] result in
guard let self = self else { return }
@@ -101,7 +103,7 @@ class DeviceManagementViewController: UIViewController, RootContainment {
// MARK: - Private
- private func setDevices(_ devices: [Device], animated: Bool) {
+ nonisolated private func setDevices(_ devices: [Device], animated: Bool) {
let viewModels = devices.map { restDevice -> DeviceViewModel in
DeviceViewModel(
id: restDevice.id,
@@ -114,13 +116,15 @@ class DeviceManagementViewController: UIViewController, RootContainment {
)
}
- contentView.canContinue = viewModels.count < ApplicationConfiguration.maxAllowedDevices
- contentView.setDeviceViewModels(viewModels, animated: animated)
+ Task { @MainActor in
+ contentView.canContinue = viewModels.count < ApplicationConfiguration.maxAllowedDevices
+ contentView.setDeviceViewModels(viewModels, animated: animated)
+ }
}
private func handleDeviceDeletion(
_ device: DeviceViewModel,
- completionHandler: @escaping () -> Void
+ completionHandler: @escaping @Sendable () -> Void
) {
showLogoutConfirmation(deviceName: device.name) { [weak self] shouldDelete in
guard let self else { return }
@@ -134,15 +138,17 @@ class DeviceManagementViewController: UIViewController, RootContainment {
guard let self = self else { return }
if let error {
- self.showErrorAlert(
- title: NSLocalizedString(
- "LOGOUT_DEVICE_ERROR_ALERT_TITLE",
- tableName: "DeviceManagement",
- value: "Failed to log out device",
- comment: ""
- ),
- error: error
- )
+ Task { @MainActor in
+ self.showErrorAlert(
+ title: NSLocalizedString(
+ "LOGOUT_DEVICE_ERROR_ALERT_TITLE",
+ tableName: "DeviceManagement",
+ value: "Failed to log out device",
+ comment: ""
+ ),
+ error: error
+ )
+ }
}
completionHandler()
@@ -236,14 +242,16 @@ class DeviceManagementViewController: UIViewController, RootContainment {
alertPresenter.showAlert(presentation: presentation, animated: true)
}
- private func deleteDevice(identifier: String, completionHandler: @escaping (Error?) -> Void) {
+ private func deleteDevice(identifier: String, completionHandler: @escaping @Sendable (Error?) -> Void) {
interactor.deleteDevice(identifier) { [weak self] completion in
guard let self = self else { return }
switch completion {
case .success:
- fetchDevices(animateUpdates: true) { completion in
- completionHandler(completion.error)
+ Task { @MainActor in
+ fetchDevices(animateUpdates: true) { completion in
+ completionHandler(completion.error)
+ }
}
case let .failure(error):
@@ -271,7 +279,7 @@ class DeviceManagementViewController: UIViewController, RootContainment {
}
}
-struct DeviceViewModel {
+struct DeviceViewModel: Sendable {
let id: String
let name: String
let creationDate: String
diff --git a/ios/MullvadVPN/View controllers/Login/LoginInteractor.swift b/ios/MullvadVPN/View controllers/Login/LoginInteractor.swift
index a6d2300077..cd7564f356 100644
--- a/ios/MullvadVPN/View controllers/Login/LoginInteractor.swift
+++ b/ios/MullvadVPN/View controllers/Login/LoginInteractor.swift
@@ -7,15 +7,15 @@
//
import Foundation
-import MullvadLogging
+@preconcurrency import MullvadLogging
import MullvadSettings
-final class LoginInteractor {
+final class LoginInteractor: @unchecked Sendable {
private let tunnelManager: TunnelManager
private let logger = Logger(label: "LoginInteractor")
private var tunnelObserver: TunnelObserver?
- var didCreateAccount: (() -> Void)?
- var suggestPreferredAccountNumber: ((String) -> Void)?
+ var didCreateAccount: (@Sendable () -> Void)?
+ var suggestPreferredAccountNumber: (@Sendable (String) -> Void)?
init(tunnelManager: TunnelManager) {
self.tunnelManager = tunnelManager
diff --git a/ios/MullvadVPN/View controllers/Login/LoginViewController.swift b/ios/MullvadVPN/View controllers/Login/LoginViewController.swift
index 81d00f8248..fb162fedd6 100644
--- a/ios/MullvadVPN/View controllers/Login/LoginViewController.swift
+++ b/ios/MullvadVPN/View controllers/Login/LoginViewController.swift
@@ -141,7 +141,9 @@ class LoginViewController: UIViewController, RootContainment {
}
interactor.suggestPreferredAccountNumber = { [weak self] value in
- self?.contentView.accountInputGroup.setAccount(value)
+ Task { @MainActor in
+ self?.contentView.accountInputGroup.setAccount(value)
+ }
}
contentView.accountInputGroup.setOnReturnKey { [weak self] _ in
diff --git a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeInteractor.swift b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeInteractor.swift
index 8524ff13b0..6beb0bd9c1 100644
--- a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeInteractor.swift
+++ b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeInteractor.swift
@@ -12,23 +12,23 @@ import MullvadREST
import MullvadSettings
import MullvadTypes
import Operations
-import StoreKit
+@preconcurrency import StoreKit
-final class OutOfTimeInteractor {
+final class OutOfTimeInteractor: Sendable {
private let storePaymentManager: StorePaymentManager
private let tunnelManager: TunnelManager
- private var tunnelObserver: TunnelObserver?
- private var paymentObserver: StorePaymentObserver?
+ nonisolated(unsafe) private var tunnelObserver: TunnelObserver?
+ nonisolated(unsafe) private var paymentObserver: StorePaymentObserver?
- private let logger = Logger(label: "OutOfTimeInteractor")
+ nonisolated(unsafe) private let logger = Logger(label: "OutOfTimeInteractor")
private let accountUpdateTimerInterval: Duration = .minutes(1)
- private var accountUpdateTimer: DispatchSourceTimer?
+ nonisolated(unsafe) private var accountUpdateTimer: DispatchSourceTimer?
- var didReceivePaymentEvent: ((StorePaymentEvent) -> Void)?
- var didReceiveTunnelStatus: ((TunnelStatus) -> Void)?
- var didAddMoreCredit: (() -> Void)?
+ nonisolated(unsafe) var didReceivePaymentEvent: (@Sendable (StorePaymentEvent) -> Void)?
+ nonisolated(unsafe) var didReceiveTunnelStatus: (@Sendable (TunnelStatus) -> Void)?
+ nonisolated(unsafe) var didAddMoreCredit: (@Sendable () -> Void)?
init(storePaymentManager: StorePaymentManager, tunnelManager: TunnelManager) {
self.storePaymentManager = storePaymentManager
@@ -76,7 +76,7 @@ final class OutOfTimeInteractor {
func restorePurchases(
for accountNumber: String,
- completionHandler: @escaping (Result<
+ completionHandler: @escaping @Sendable (Result<
REST.CreateApplePaymentResponse,
Error
>) -> Void
@@ -89,7 +89,7 @@ final class OutOfTimeInteractor {
func requestProducts(
with productIdentifiers: Set<StoreSubscription>,
- completionHandler: @escaping (Result<SKProductsResponse, Error>) -> Void
+ completionHandler: @escaping @Sendable (Result<SKProductsResponse, Error>) -> Void
) -> Cancellable {
storePaymentManager.requestProducts(
with: productIdentifiers,
diff --git a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift
index a85b0de35c..a735fb4eca 100644
--- a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift
+++ b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift
@@ -9,14 +9,15 @@
import Foundation
import MullvadREST
import Operations
-import StoreKit
+@preconcurrency import StoreKit
import UIKit
-protocol OutOfTimeViewControllerDelegate: AnyObject {
+protocol OutOfTimeViewControllerDelegate: AnyObject, Sendable {
func outOfTimeViewControllerDidBeginPayment(_ controller: OutOfTimeViewController)
func outOfTimeViewControllerDidEndPayment(_ controller: OutOfTimeViewController)
}
+@MainActor
class OutOfTimeViewController: UIViewController, RootContainment {
weak var delegate: OutOfTimeViewControllerDelegate?
@@ -42,7 +43,7 @@ class OutOfTimeViewController: UIViewController, RootContainment {
.lightContent
}
- var preferredHeaderBarPresentation: HeaderBarPresentation {
+ nonisolated(unsafe) var preferredHeaderBarPresentation: HeaderBarPresentation {
let tunnelState = interactor.tunnelStatus.state
return HeaderBarPresentation(
@@ -95,12 +96,16 @@ class OutOfTimeViewController: UIViewController, RootContainment {
)
interactor.didReceivePaymentEvent = { [weak self] event in
- self?.didReceivePaymentEvent(event)
+ Task { @MainActor in
+ self?.didReceivePaymentEvent(event)
+ }
}
interactor.didReceiveTunnelStatus = { [weak self] _ in
- self?.setNeedsHeaderBarStyleAppearanceUpdate()
- self?.applyViewState()
+ Task { @MainActor in
+ self?.setNeedsHeaderBarStyleAppearanceUpdate()
+ self?.applyViewState()
+ }
}
if StorePaymentManager.canMakePayments {
@@ -131,7 +136,9 @@ class OutOfTimeViewController: UIViewController, RootContainment {
let productState: ProductState = completion.value?.products.first
.map { .received($0) } ?? .failed
- self?.productState = productState
+ Task { @MainActor in
+ self?.productState = productState
+ }
}
}
@@ -214,7 +221,9 @@ class OutOfTimeViewController: UIViewController, RootContainment {
default:
errorPresenter.showAlertForError(paymentFailure.error, context: .purchase) {
- self.paymentState = .none
+ MainActor.assumeIsolated {
+ self.paymentState = .none
+ }
}
}
}
@@ -249,17 +258,27 @@ class OutOfTimeViewController: UIViewController, RootContainment {
switch result {
case let .success(response):
- errorPresenter.showAlertForResponse(response, context: .restoration) {
- self.paymentState = .none
+ Task { @MainActor in
+ errorPresenter.showAlertForResponse(response, context: .restoration) {
+ MainActor.assumeIsolated {
+ self.paymentState = .none
+ }
+ }
}
case let .failure(error as StorePaymentManagerError):
- errorPresenter.showAlertForError(error, context: .restoration) {
- self.paymentState = .none
+ Task { @MainActor in
+ errorPresenter.showAlertForError(error, context: .restoration) {
+ MainActor.assumeIsolated {
+ self.paymentState = .none
+ }
+ }
}
default:
- paymentState = .none
+ Task { @MainActor in
+ paymentState = .none
+ }
}
}
}
diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift
index 616ea7d081..a63d97e2ed 100644
--- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift
+++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift
@@ -11,7 +11,7 @@ import MullvadREST
import MullvadTypes
import Operations
-final class ProblemReportInteractor {
+final class ProblemReportInteractor: @unchecked Sendable {
private let apiProxy: APIQuerying
private let tunnelManager: TunnelManager
private let consolidatedLog: ConsolidatedApplicationLog
@@ -28,7 +28,7 @@ final class ProblemReportInteractor {
)
}
- func fetchReportString(completion: @escaping (String) -> Void) {
+ func fetchReportString(completion: @escaping @Sendable (String) -> Void) {
consolidatedLog.addLogFiles(fileURLs: ApplicationTarget.allCases.flatMap {
ApplicationConfiguration.logFileURLs(for: $0, in: ApplicationConfiguration.containerURL)
}) { [weak self] in
@@ -40,7 +40,7 @@ final class ProblemReportInteractor {
func sendReport(
email: String,
message: String,
- completion: @escaping (Result<Void, Error>) -> Void
+ completion: @escaping @Sendable (Result<Void, Error>) -> Void
) {
let logString = self.consolidatedLog.string
@@ -67,7 +67,7 @@ final class ProblemReportInteractor {
email: String,
message: String,
logString: String,
- completion: @escaping (Result<Void, Error>) -> Void
+ completion: @escaping @Sendable (Result<Void, Error>) -> Void
) {
let metadataDict = self.consolidatedLog.metadata.reduce(into: [:]) { output, entry in
output[entry.key.rawValue] = entry.value
diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportReviewViewController.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportReviewViewController.swift
index 56befb66fd..1103a76e71 100644
--- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportReviewViewController.swift
+++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportReviewViewController.swift
@@ -97,9 +97,11 @@ class ProblemReportReviewViewController: UIViewController {
spinnerView.startAnimating()
interactor.fetchReportString { [weak self] reportString in
guard let self else { return }
- textView.text = reportString
- spinnerView.stopAnimating()
- spinnerContainerView.isHidden = true
+ Task { @MainActor in
+ textView.text = reportString
+ spinnerView.stopAnimating()
+ spinnerContainerView.isHidden = true
+ }
}
}
@@ -107,14 +109,16 @@ class ProblemReportReviewViewController: UIViewController {
private func share() {
interactor.fetchReportString { [weak self] reportString in
guard let self,!reportString.isEmpty else { return }
- let activityController = UIActivityViewController(
- activityItems: [reportString],
- applicationActivities: nil
- )
+ Task { @MainActor in
+ let activityController = UIActivityViewController(
+ activityItems: [reportString],
+ applicationActivities: nil
+ )
- activityController.popoverPresentationController?.barButtonItem = navigationItem.leftBarButtonItem
+ activityController.popoverPresentationController?.barButtonItem = navigationItem.leftBarButtonItem
- present(activityController, animated: true)
+ present(activityController, animated: true)
+ }
}
}
#endif
diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift
index 1cbba022e4..a7ad2d73a7 100644
--- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift
+++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift
@@ -262,7 +262,9 @@ final class ProblemReportViewController: UIViewController, UITextFieldDelegate {
email: viewModel.email,
message: viewModel.message
) { completion in
- self.didSendProblemReport(viewModel: viewModel, completion: completion)
+ Task { @MainActor in
+ self.didSendProblemReport(viewModel: viewModel, completion: completion)
+ }
}
}
diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift
index 8801137005..12fd0271d1 100644
--- a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift
+++ b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadREST
import MullvadTypes
-final class RedeemVoucherInteractor {
+final class RedeemVoucherInteractor: @unchecked Sendable {
private let tunnelManager: TunnelManager
private let accountsProxy: RESTAccountHandling
private let shouldVerifyVoucherAsAccount: Bool
@@ -33,7 +33,7 @@ final class RedeemVoucherInteractor {
func redeemVoucher(
code: String,
- completion: @escaping ((Result<REST.SubmitVoucherResponse, Error>) -> Void)
+ completion: @escaping (@Sendable (Result<REST.SubmitVoucherResponse, Error>) -> Void)
) {
tasks.append(tunnelManager.redeemVoucher(code) { [weak self] result in
guard let self else { return }
diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewController.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewController.swift
index e1fdc52f16..870426c004 100644
--- a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewController.swift
+++ b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewController.swift
@@ -10,7 +10,7 @@ import MullvadREST
import MullvadTypes
import UIKit
-protocol RedeemVoucherViewControllerDelegate: AnyObject {
+protocol RedeemVoucherViewControllerDelegate: AnyObject, Sendable {
func redeemVoucherDidSucceed(
_ controller: RedeemVoucherViewController,
with response: REST.SubmitVoucherResponse
@@ -18,9 +18,10 @@ protocol RedeemVoucherViewControllerDelegate: AnyObject {
func redeemVoucherDidCancel(_ controller: RedeemVoucherViewController)
}
+@MainActor
class RedeemVoucherViewController: UIViewController, UINavigationControllerDelegate, RootContainment {
private let contentView: RedeemVoucherContentView
- private var interactor: RedeemVoucherInteractor
+ nonisolated(unsafe) private var interactor: RedeemVoucherInteractor
weak var delegate: RedeemVoucherViewControllerDelegate?
@@ -115,12 +116,14 @@ class RedeemVoucherViewController: UIViewController, UINavigationControllerDeleg
contentView.isEditing = false
interactor.redeemVoucher(code: code, completion: { [weak self] result in
guard let self else { return }
- switch result {
- case let .success(value):
- contentView.state = .success
- delegate?.redeemVoucherDidSucceed(self, with: value)
- case let .failure(error):
- contentView.state = .failure(error)
+ MainActor.assumeIsolated {
+ switch result {
+ case let .success(value):
+ contentView.state = .success
+ delegate?.redeemVoucherDidSucceed(self, with: value)
+ case let .failure(error):
+ contentView.state = .failure(error)
+ }
}
})
}
diff --git a/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterCellFactory.swift b/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterCellFactory.swift
index ca3e26cc75..a82fd2cfec 100644
--- a/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterCellFactory.swift
@@ -8,7 +8,8 @@
import UIKit
-struct RelayFilterCellFactory: CellFactoryProtocol {
+@MainActor
+struct RelayFilterCellFactory: @preconcurrency CellFactoryProtocol {
let tableView: UITableView
func makeCell(for item: RelayFilterDataSource.Item, indexPath: IndexPath) -> UITableViewCell {
diff --git a/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift b/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift
index 53634790bf..5ff3a3aed6 100644
--- a/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift
+++ b/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift
@@ -164,9 +164,11 @@ class RelayFilterView: UIView {
.old,
]) { [weak self] _, change in
guard let self, let newSize = change.newValue else { return }
- let height = newSize.height == .zero ? 8 : newSize.height
- collectionViewHeightConstraint.constant = height > 80 ? 80 : height
- layoutIfNeeded() // Update the layout
+ Task { @MainActor in
+ let height = newSize.height == .zero ? 8 : newSize.height
+ collectionViewHeightConstraint.constant = height > 80 ? 80 : height
+ layoutIfNeeded() // Update the layout
+ }
}
}
}
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift
index ec7f5b75e2..76b537bf6c 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift
@@ -8,7 +8,7 @@
import MullvadTypes
-struct LocationCellViewModel: Hashable {
+struct LocationCellViewModel: Hashable, Sendable {
let section: LocationSection
let node: LocationNode
var indentationLevel = 0
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift
index c841d1ffda..674e174cfa 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift
@@ -15,19 +15,18 @@ import UIKit
final class LocationDataSource:
UITableViewDiffableDataSource<LocationSection, LocationCellViewModel>,
LocationDiffableDataSourceProtocol {
- private var currentSearchString = ""
- private var dataSources: [LocationDataSourceProtocol] = []
+ nonisolated(unsafe) private var currentSearchString = ""
+ nonisolated(unsafe) private var dataSources: [LocationDataSourceProtocol] = []
// The selected location.
- private var selectedLocation: LocationCellViewModel?
+ nonisolated(unsafe) private var selectedLocation: LocationCellViewModel?
// When multihop is enabled, this is the "inverted" selected location, ie. entry
// if in exit mode and exit if in entry mode.
- private var excludedLocation: LocationCellViewModel?
+ nonisolated(unsafe) private var excludedLocation: LocationCellViewModel?
let tableView: UITableView
let sections: [LocationSection]
- let serialUpdateQueue = DispatchQueue(label: "LocationDataSource.UpdateQueue")
- var didSelectRelayLocations: ((UserSelectedRelays) -> Void)?
- var didTapEditCustomLists: (() -> Void)?
+ var didSelectRelayLocations: (@Sendable (UserSelectedRelays) -> Void)?
+ var didTapEditCustomLists: (@Sendable () -> Void)?
init(
tableView: UITableView,
@@ -56,22 +55,22 @@ final class LocationDataSource:
}
func setRelays(_ relaysWithLocation: LocationRelays, selectedRelays: RelaySelection) {
- serialUpdateQueue.async { [weak self] in
- guard let self = self,
- let allLocationsDataSource = dataSources
- .first(where: { $0 is AllLocationDataSource }) as? AllLocationDataSource,
- let customListsDataSource = dataSources
- .first(where: { $0 is CustomListsDataSource }) as? CustomListsDataSource else { return }
+ Task { @MainActor in
+ guard let allLocationsDataSource = dataSources
+ .first(where: { $0 is AllLocationDataSource }) as? AllLocationDataSource,
+ let customListsDataSource = dataSources
+ .first(where: { $0 is CustomListsDataSource }) as? CustomListsDataSource else { return }
allLocationsDataSource.reload(relaysWithLocation)
customListsDataSource.reload(allLocationNodes: allLocationsDataSource.nodes)
- setSelectedRelays(selectedRelays)
- filterRelays(by: currentSearchString)
+ Task { @MainActor in
+ setSelectedRelays(selectedRelays)
+ filterRelays(by: currentSearchString)
+ }
}
}
func filterRelays(by searchString: String) {
- serialUpdateQueue.async(flags: .barrier) { [weak self] in
- guard let self = self else { return }
+ Task { @MainActor in
currentSearchString = searchString
let list = sections.enumerated().map { index, section in
@@ -103,12 +102,11 @@ final class LocationDataSource:
/// Refreshes the custom list section and keeps all modifications intact (selection and expanded states).
func refreshCustomLists() {
- serialUpdateQueue.async { [weak self] in
- guard let self = self,
- let allLocationsDataSource =
- dataSources.first(where: { $0 is AllLocationDataSource }) as? AllLocationDataSource,
- let customListsDataSource =
- dataSources.first(where: { $0 is CustomListsDataSource }) as? CustomListsDataSource
+ Task { @MainActor in
+ guard let allLocationsDataSource =
+ dataSources.first(where: { $0 is AllLocationDataSource }) as? AllLocationDataSource,
+ let customListsDataSource =
+ dataSources.first(where: { $0 is CustomListsDataSource }) as? CustomListsDataSource
else {
return
}
@@ -120,8 +118,8 @@ final class LocationDataSource:
}
func setSelectedRelays(_ selectedRelays: RelaySelection) {
- serialUpdateQueue.async(flags: .barrier) { [weak self] in
- guard let self, let _selectedLocation = mapSelection(from: selectedRelays.selected) else { return }
+ Task { @MainActor in
+ guard let _selectedLocation = mapSelection(from: selectedRelays.selected) else { return }
selectedLocation = _selectedLocation
excludedLocation = mapSelection(from: selectedRelays.excluded)
excludedLocation?.excludedRelayTitle = selectedRelays.excludedTitle
@@ -141,9 +139,7 @@ final class LocationDataSource:
}
private func indexPathForSelectedRelay() -> IndexPath? {
- serialUpdateQueue.sync {
- selectedLocation.flatMap { indexPath(for: $0) }
- }
+ selectedLocation.flatMap { indexPath(for: $0) }
}
private func mapSelection(from selectedRelays: UserSelectedRelays?) -> LocationCellViewModel? {
@@ -330,7 +326,7 @@ extension LocationDataSource: UITableViewDelegate {
}
}
-extension LocationDataSource: LocationCellDelegate {
+extension LocationDataSource: @preconcurrency LocationCellDelegate {
func toggleExpanding(cell: LocationCell) {
guard let indexPath = tableView.indexPath(for: cell),
let item = itemIdentifier(for: indexPath) else { return }
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationNode.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationNode.swift
index 8a43493f0b..6c81c941b3 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationNode.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationNode.swift
@@ -9,7 +9,7 @@
import MullvadSettings
import MullvadTypes
-class LocationNode {
+class LocationNode: @unchecked Sendable {
let name: String
var code: String
var locations: [RelayLocation]
@@ -134,13 +134,13 @@ extension LocationNode: Comparable {
}
/// Proxy class for building and/or searching node trees.
-class RootLocationNode: LocationNode {
+class RootLocationNode: LocationNode, @unchecked Sendable {
init(name: String = "", code: String = "", children: [LocationNode] = []) {
super.init(name: name, code: code, children: children)
}
}
-class CustomListLocationNode: LocationNode {
+class CustomListLocationNode: LocationNode, @unchecked Sendable {
let customList: CustomList
init(
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationRelays.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationRelays.swift
index a3bc127593..baa8f60ec6 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationRelays.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationRelays.swift
@@ -8,7 +8,7 @@
import MullvadREST
-struct LocationRelays {
+struct LocationRelays: Sendable {
var relays: [REST.ServerRelay]
var locations: [String: REST.ServerLocation]
}
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationSection.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationSection.swift
index 87dc2dbb53..074675de48 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationSection.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationSection.swift
@@ -7,7 +7,7 @@
//
import Foundation
-enum LocationSection: String, Hashable, CustomStringConvertible, CaseIterable, CellIdentifierProtocol {
+enum LocationSection: String, Hashable, CustomStringConvertible, CaseIterable, CellIdentifierProtocol, Sendable {
case customLists
case allLocations
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
index 70e11012fe..3bd2671f12 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
@@ -136,16 +136,20 @@ final class LocationViewController: UIViewController {
)
dataSource?.didSelectRelayLocations = { [weak self] relays in
- self?.delegate?.didSelectRelays(relays: relays)
+ Task { @MainActor in
+ self?.delegate?.didSelectRelays(relays: relays)
+ }
}
dataSource?.didTapEditCustomLists = { [weak self] in
guard let self else { return }
- if let relaysWithLocation {
- let allLocationDataSource = AllLocationDataSource()
- allLocationDataSource.reload(relaysWithLocation)
- delegate?.navigateToCustomLists(nodes: allLocationDataSource.nodes)
+ Task { @MainActor in
+ if let relaysWithLocation {
+ let allLocationDataSource = AllLocationDataSource()
+ allLocationDataSource.reload(relaysWithLocation)
+ delegate?.navigateToCustomLists(nodes: allLocationDataSource.nodes)
+ }
}
}
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift
index 3aba8a46e5..1bcd44713c 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift
@@ -280,7 +280,7 @@ final class LocationViewControllerWrapper: UIViewController {
}
}
-extension LocationViewControllerWrapper: LocationViewControllerDelegate {
+extension LocationViewControllerWrapper: @preconcurrency LocationViewControllerDelegate {
func navigateToCustomLists(nodes: [LocationNode]) {
delegate?.navigateToCustomLists(nodes: nodes)
}
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/RelaySelection.swift b/ios/MullvadVPN/View controllers/SelectLocation/RelaySelection.swift
index 85b185a574..8cffa380b7 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/RelaySelection.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/RelaySelection.swift
@@ -8,7 +8,7 @@
import MullvadTypes
-struct RelaySelection {
+struct RelaySelection: Sendable {
var selected: UserSelectedRelays?
var excluded: UserSelectedRelays?
var excludedTitle: String?
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift b/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
index d9b5b42bb1..dc365734e8 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
@@ -13,7 +13,8 @@ protocol SettingsCellEventHandler {
func showInfo(for button: SettingsInfoButtonItem)
}
-final class SettingsCellFactory: CellFactoryProtocol {
+@MainActor
+final class SettingsCellFactory: @preconcurrency CellFactoryProtocol, Sendable {
let tableView: UITableView
var delegate: SettingsCellEventHandler?
var viewModel: SettingsViewModel
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift b/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
index 7d46b67750..b769690d26 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
@@ -175,7 +175,7 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
}
}
-extension SettingsDataSource: SettingsCellEventHandler {
+extension SettingsDataSource: @preconcurrency SettingsCellEventHandler {
func showInfo(for button: SettingsInfoButtonItem) {
delegate?.showInfo(for: button)
}
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift b/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
index a8b62ebdbc..607d4d6488 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
@@ -11,7 +11,7 @@ import MullvadSettings
import Routing
import UIKit
-protocol SettingsViewControllerDelegate: AnyObject {
+protocol SettingsViewControllerDelegate: AnyObject, Sendable {
func settingsViewControllerDidFinish(_ controller: SettingsViewController)
func settingsViewController(
_ controller: SettingsViewController,
@@ -76,7 +76,7 @@ class SettingsViewController: UITableViewController {
}
}
-extension SettingsViewController: SettingsDataSourceDelegate {
+extension SettingsViewController: @preconcurrency SettingsDataSourceDelegate {
func didSelectItem(item: SettingsDataSource.Item) {
guard let route = item.navigationRoute else { return }
delegate?.settingsViewController(self, didRequestRoutePresentation: route)
diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipContainerView.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipContainerView.swift
index 0d417542f3..addc6f92b5 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipContainerView.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipContainerView.swift
@@ -47,8 +47,8 @@ struct ChipContainerView<ViewModel>: View where ViewModel: ChipViewModelProtocol
}
private func createChipViews(chips: [ChipModel], containerWidth: CGFloat) -> some View {
- var width = CGFloat.zero
- var height = CGFloat.zero
+ nonisolated(unsafe) var width = CGFloat.zero
+ nonisolated(unsafe) var height = CGFloat.zero
return ForEach(chips) { data in
ChipView(item: data)
diff --git a/ios/MullvadVPN/View controllers/Tunnel/MapViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/MapViewController.swift
index 1b5f2be784..b77a307a23 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/MapViewController.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/MapViewController.swift
@@ -233,8 +233,9 @@ final class MapViewController: UIViewController, MKMapViewDelegate {
cancelOtherAnimations: Bool,
block: @escaping (_ finish: @escaping () -> Void) -> Void
) {
+ nonisolated(unsafe) let nonisolatedBlock = block
let operation = AsyncBlockOperation(dispatchQueue: .main) { finish in
- block {
+ nonisolatedBlock {
finish(nil)
}
}
diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewControllerInteractor.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewControllerInteractor.swift
index e072ec283e..12153b375a 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewControllerInteractor.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewControllerInteractor.swift
@@ -10,7 +10,7 @@ import Combine
import MullvadSettings
import MullvadTypes
-final class TunnelViewControllerInteractor {
+final class TunnelViewControllerInteractor: @unchecked Sendable {
private let tunnelManager: TunnelManager
private let outgoingConnectionService: OutgoingConnectionServiceHandling
private var tunnelObserver: TunnelObserver?
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift
index 6566211edd..7b8342029e 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift
@@ -16,7 +16,8 @@ protocol CustomDNSCellEventHandler {
func showInfo(for button: VPNSettingsInfoButtonItem)
}
-final class CustomDNSCellFactory: CellFactoryProtocol {
+@MainActor
+final class CustomDNSCellFactory: @preconcurrency CellFactoryProtocol {
let tableView: UITableView
var viewModel: VPNSettingsViewModel
var delegate: CustomDNSCellEventHandler?
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSDataSource.swift b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSDataSource.swift
index 42d3ac8f45..133de6af24 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSDataSource.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSDataSource.swift
@@ -630,7 +630,7 @@ final class CustomDNSDataSource: UITableViewDiffableDataSource<
}
}
-extension CustomDNSDataSource: CustomDNSCellEventHandler {
+extension CustomDNSDataSource: @preconcurrency CustomDNSCellEventHandler {
func didChangeState(for preference: Item, isOn: Bool) {
switch preference {
case .blockAll:
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift
index efae49ad8c..a66fa474cc 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift
@@ -96,7 +96,7 @@ class CustomDNSViewController: UITableViewController {
}
}
-extension CustomDNSViewController: DNSSettingsDataSourceDelegate {
+extension CustomDNSViewController: @preconcurrency DNSSettingsDataSourceDelegate {
func didChangeViewModel(_ viewModel: VPNSettingsViewModel) {
interactor.updateSettings([.dnsSettings(viewModel.asDNSSettings())])
}
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
index b1f1c9fd59..d97ffeecb7 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
@@ -18,7 +18,8 @@ protocol VPNSettingsCellEventHandler {
func switchMultihop(_ state: MultihopState)
}
-final class VPNSettingsCellFactory: CellFactoryProtocol {
+@MainActor
+final class VPNSettingsCellFactory: @preconcurrency CellFactoryProtocol {
let tableView: UITableView
var viewModel: VPNSettingsViewModel
var delegate: VPNSettingsCellEventHandler?
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
index 22d8a4a8da..6d57966ca2 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
@@ -600,7 +600,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
}
}
-extension VPNSettingsDataSource: VPNSettingsCellEventHandler {
+extension VPNSettingsDataSource: @preconcurrency VPNSettingsCellEventHandler {
func showInfo(for button: VPNSettingsInfoButtonItem) {
delegate?.showInfo(for: button)
}
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInteractor.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInteractor.swift
index 88fddf83b2..ccae325641 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInteractor.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInteractor.swift
@@ -38,11 +38,11 @@ final class VPNSettingsInteractor {
tunnelManager.addObserver(tunnelObserver)
}
- func updateSettings(_ changes: [TunnelSettingsUpdate], completion: (() -> Void)? = nil) {
+ func updateSettings(_ changes: [TunnelSettingsUpdate], completion: (@Sendable () -> Void)? = nil) {
tunnelManager.updateSettings(changes, completionHandler: completion)
}
- func setPort(_ port: UInt16?, completion: (() -> Void)? = nil) {
+ func setPort(_ port: UInt16?, completion: (@Sendable () -> Void)? = nil) {
var relayConstraints = tunnelManager.settings.relayConstraints
if let port {
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift
index a9756aecf7..a21e31236b 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift
@@ -73,7 +73,7 @@ class VPNSettingsViewController: UITableViewController {
}
}
-extension VPNSettingsViewController: VPNSettingsDataSourceDelegate {
+extension VPNSettingsViewController: @preconcurrency VPNSettingsDataSourceDelegate {
func humanReadablePortRepresentation() -> String {
let ranges = interactor.cachedRelays?.relays.wireguard.portRanges ?? []
return ranges
diff --git a/ios/MullvadVPN/Views/CustomButton.swift b/ios/MullvadVPN/Views/CustomButton.swift
index 8c99ddb2cc..6f6deb0e0a 100644
--- a/ios/MullvadVPN/Views/CustomButton.swift
+++ b/ios/MullvadVPN/Views/CustomButton.swift
@@ -24,7 +24,7 @@ extension UIControl.State {
}
/// A custom `UIButton` subclass that implements additional layouts for the image
-class CustomButton: UIButton {
+class CustomButton: UIButton, Sendable {
var imageAlignment: NSDirectionalRectEdge = .leading {
didSet {
self.configuration?.imagePlacement = imageAlignment
diff --git a/ios/MullvadVPN/Views/CustomTextView.swift b/ios/MullvadVPN/Views/CustomTextView.swift
index c9f43ed6d7..9f3106d3ab 100644
--- a/ios/MullvadVPN/Views/CustomTextView.swift
+++ b/ios/MullvadVPN/Views/CustomTextView.swift
@@ -84,7 +84,7 @@ class CustomTextView: UITextView {
}
}
- private var notificationObserver: Any?
+ nonisolated(unsafe) private var notificationObserver: Any?
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
@@ -125,7 +125,9 @@ class CustomTextView: UITextView {
object: textStorage,
queue: OperationQueue.main
) { [weak self] _ in
- self?.updatePlaceholderVisibility()
+ MainActor.assumeIsolated {
+ self?.updatePlaceholderVisibility()
+ }
}
updatePlaceholderVisibility()
diff --git a/ios/MullvadVPN/Views/SpinnerActivityIndicatorView.swift b/ios/MullvadVPN/Views/SpinnerActivityIndicatorView.swift
index 2b0a5e07ac..24daafbc6d 100644
--- a/ios/MullvadVPN/Views/SpinnerActivityIndicatorView.swift
+++ b/ios/MullvadVPN/Views/SpinnerActivityIndicatorView.swift
@@ -8,11 +8,13 @@
import UIKit
+@MainActor
class SpinnerActivityIndicatorView: UIView {
private static let rotationAnimationKey = "rotation"
private static let animationDuration = 0.6
- enum Style {
+ @MainActor
+ enum Style: Sendable {
case small, medium, large, custom
var intrinsicSize: CGSize {
@@ -57,7 +59,9 @@ class SpinnerActivityIndicatorView: UIView {
}
deinit {
- unregisterSceneActivationObserver()
+ MainActor.assumeIsolated {
+ unregisterSceneActivationObserver()
+ }
}
override func didMoveToWindow() {
@@ -110,7 +114,9 @@ class SpinnerActivityIndicatorView: UIView {
forName: UIScene.willEnterForegroundNotification,
object: window?.windowScene,
queue: .main, using: { [weak self] _ in
- self?.restartAnimationIfNeeded()
+ MainActor.assumeIsolated {
+ self?.restartAnimationIfNeeded()
+ }
}
)
}
diff --git a/ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift b/ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift
index c8cfc4308f..b0302f0e39 100644
--- a/ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift
+++ b/ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift
@@ -10,7 +10,7 @@
@testable import MullvadSettings
@testable import MullvadTypes
-import XCTest
+@preconcurrency import XCTest
class ShadowsocksLoaderTests: XCTestCase {
private let sampleRelays = ServerRelaysResponseStubs.sampleRelays
@@ -91,7 +91,7 @@ class ShadowsocksLoaderTests: XCTestCase {
}
}
-class ShadowsocksRelaySelectorStub: ShadowsocksRelaySelectorProtocol {
+class ShadowsocksRelaySelectorStub: ShadowsocksRelaySelectorProtocol, @unchecked Sendable {
var entryBridgeResult: Result<REST.BridgeRelay, Error> = .failure(ShadowsocksRelaySelectorStubError())
var exitBridgeResult: Result<REST.BridgeRelay, Error> = .failure(ShadowsocksRelaySelectorStubError())
private let relays: REST.ServerRelaysResponse
@@ -114,7 +114,7 @@ class ShadowsocksRelaySelectorStub: ShadowsocksRelaySelectorProtocol {
}
}
-class ShadowsocksConfigurationCacheStub: ShadowsocksConfigurationCacheProtocol {
+class ShadowsocksConfigurationCacheStub: ShadowsocksConfigurationCacheProtocol, @unchecked Sendable {
private(set) var cachedConfiguration: ShadowsocksConfiguration?
func read() throws -> ShadowsocksConfiguration {
diff --git a/ios/MullvadVPNTests/MullvadSettings/InMemorySettingsStore.swift b/ios/MullvadVPNTests/MullvadSettings/InMemorySettingsStore.swift
index 1f19b6429c..8c44a0ed7a 100644
--- a/ios/MullvadVPNTests/MullvadSettings/InMemorySettingsStore.swift
+++ b/ios/MullvadVPNTests/MullvadSettings/InMemorySettingsStore.swift
@@ -13,7 +13,7 @@ protocol Instantiable {
init()
}
-class InMemorySettingsStore<ThrownError: Error>: SettingsStore where ThrownError: Instantiable {
+class InMemorySettingsStore<ThrownError: Error>: SettingsStore, @unchecked Sendable where ThrownError: Instantiable {
private var settings = [SettingsKey: Data]()
func read(key: SettingsKey) throws -> Data {
diff --git a/ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift b/ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift
index 35866fe0d9..988188b753 100644
--- a/ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift
+++ b/ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift
@@ -47,7 +47,7 @@ extension MigrationManagerTests {
}
}
- wait(for: [backgroundMigrationExpectation, foregroundMigrationExpectation], timeout: .UnitTest.invertedTimeout)
+ wait(for: [backgroundMigrationExpectation, foregroundMigrationExpectation], timeout: .UnitTest.timeout)
// Migration happens either in one process, or the other.
// This check guarantees it didn't happen in both simultaneously.
diff --git a/ios/MullvadVPNTests/MullvadVPN/Log/ConsolidatedApplicationLogTests.swift b/ios/MullvadVPNTests/MullvadVPN/Log/ConsolidatedApplicationLogTests.swift
index 523c02bebf..3146848827 100644
--- a/ios/MullvadVPNTests/MullvadVPN/Log/ConsolidatedApplicationLogTests.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/Log/ConsolidatedApplicationLogTests.swift
@@ -8,8 +8,8 @@
import XCTest
-class ConsolidatedApplicationLogTests: XCTestCase {
- var consolidatedLog: ConsolidatedApplicationLog!
+final class ConsolidatedApplicationLogTests: XCTestCase, @unchecked Sendable {
+ nonisolated(unsafe) var consolidatedLog: ConsolidatedApplicationLog!
let mockRedactStrings = ["sensitive", "secret"]
let mockSecurityGroupIdentifiers = ["group1", "group2"]
var createdMockFiles: [URL] = []
@@ -35,7 +35,7 @@ class ConsolidatedApplicationLogTests: XCTestCase {
createdMockFiles = []
}
- func testAddLogFiles() {
+ func testAddLogFiles() async {
var string = ""
let expectation = self.expectation(description: "Log files added")
let mockFile = createMockFile(content: content, fileName: "\(generateRandomName()).txt")
@@ -44,7 +44,7 @@ class ConsolidatedApplicationLogTests: XCTestCase {
expectation.fulfill()
}
- waitForExpectations(timeout: 1)
+ await fulfillment(of: [expectation], timeout: 1)
consolidatedLog.write(to: &string)
XCTAssertTrue(
consolidatedLog.string.contains(string),
@@ -52,7 +52,7 @@ class ConsolidatedApplicationLogTests: XCTestCase {
)
}
- func testAddError() {
+ func testAddError() async {
let expectation = self.expectation(description: "Error added to log")
let errorMessage = "Test error"
let errorDetails = "A sensitive error occurred"
@@ -61,14 +61,14 @@ class ConsolidatedApplicationLogTests: XCTestCase {
expectation.fulfill()
}
- waitForExpectations(timeout: 1)
+ await fulfillment(of: [expectation], timeout: 1)
XCTAssertTrue(
consolidatedLog.string.contains(errorMessage),
"Log should include the error message."
)
}
- func testStringOutput() {
+ func testStringOutput() async {
let expectation = self.expectation(description: "Log files added")
let mockFile = createMockFile(content: content, fileName: "\(generateRandomName()).txt")
consolidatedLog.addLogFiles(fileURLs: [mockFile]) {
@@ -76,7 +76,7 @@ class ConsolidatedApplicationLogTests: XCTestCase {
let output = self.consolidatedLog.string
XCTAssertFalse(output.isEmpty, "Output string should include redacted log content.")
}
- waitForExpectations(timeout: 1)
+ await fulfillment(of: [expectation], timeout: 1)
}
// MARK: - Private functions
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnel.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnel.swift
index 6edd2059cc..07f3a413cf 100644
--- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnel.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnel.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadTypes
import NetworkExtension
-class MockTunnel: TunnelProtocol {
+class MockTunnel: TunnelProtocol, @unchecked Sendable {
typealias TunnelManagerProtocol = SimulatorTunnelProviderManager
var status: NEVPNStatus
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift
index 552607a279..607c93e652 100644
--- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift
@@ -12,7 +12,7 @@ import MullvadSettings
import MullvadTypes
// this is still very minimal, and will be fleshed out as needed.
-class MockTunnelInteractor: TunnelInteractor {
+final class MockTunnelInteractor: TunnelInteractor, @unchecked Sendable {
var isConfigurationLoaded: Bool
var settings: LatestTunnelSettings
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/StartTunnelOperationTests.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/StartTunnelOperationTests.swift
index 7b2b061536..08b2946d99 100644
--- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/StartTunnelOperationTests.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/StartTunnelOperationTests.swift
@@ -69,7 +69,7 @@ class StartTunnelOperationTests: XCTestCase {
func testSetsReconnectIfDisconnecting() {
let interactor = makeInteractor(deviceState: loggedInDeviceState, tunnelState: .disconnecting(.nothing))
- var tunnelStatus = TunnelStatus()
+ nonisolated(unsafe) var tunnelStatus = TunnelStatus()
interactor.onUpdateTunnelStatus = { status in tunnelStatus = status }
let expectation = expectation(description: "Tunnel status set to reconnect")
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStore+Stubs.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStore+Stubs.swift
index f894627151..f6967306ab 100644
--- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStore+Stubs.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStore+Stubs.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadTypes
import NetworkExtension
-struct TunnelStoreStub: TunnelStoreProtocol {
+struct TunnelStoreStub: TunnelStoreProtocol, Sendable {
typealias TunnelType = TunnelStub
let backgroundTaskProvider: any BackgroundTaskProviding
func getPersistentTunnels() -> [TunnelType] {
@@ -26,7 +26,7 @@ class DummyTunnelStatusObserver: TunnelStatusObserver {
func tunnel(_ tunnel: any TunnelProtocol, didReceiveStatus status: NEVPNStatus) {}
}
-final class TunnelStub: TunnelProtocol, Equatable {
+final class TunnelStub: TunnelProtocol, Equatable, @unchecked Sendable {
typealias TunnelManagerProtocol = SimulatorTunnelProviderManager
static func == (lhs: TunnelStub, rhs: TunnelStub) -> Bool {
diff --git a/ios/MullvadVPNTests/MullvadVPN/View controllers/SelectLocation/CustomListRepositoryTests.swift b/ios/MullvadVPNTests/MullvadVPN/View controllers/SelectLocation/CustomListRepositoryTests.swift
index 5e08158ebb..cd1ec200fb 100644
--- a/ios/MullvadVPNTests/MullvadVPN/View controllers/SelectLocation/CustomListRepositoryTests.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/View controllers/SelectLocation/CustomListRepositoryTests.swift
@@ -11,7 +11,7 @@ import Network
import XCTest
class CustomListRepositoryTests: XCTestCase {
- static let store = InMemorySettingsStore<SettingNotFound>()
+ nonisolated(unsafe) static let store = InMemorySettingsStore<SettingNotFound>()
private var repository = CustomListRepository()
override class func setUp() {
diff --git a/ios/Operations/AsyncBlockOperation.swift b/ios/Operations/AsyncBlockOperation.swift
index 062fc528f5..69e52aff0e 100644
--- a/ios/Operations/AsyncBlockOperation.swift
+++ b/ios/Operations/AsyncBlockOperation.swift
@@ -10,11 +10,14 @@ import Foundation
import protocol MullvadTypes.Cancellable
/// Asynchronous block operation
-public class AsyncBlockOperation: AsyncOperation {
- private var executor: ((@escaping (Error?) -> Void) -> Cancellable?)?
+public class AsyncBlockOperation: AsyncOperation, @unchecked Sendable {
+ private var executor: ((@escaping @Sendable (Error?) -> Void) -> Cancellable?)?
private var cancellableTask: Cancellable?
- public init(dispatchQueue: DispatchQueue? = nil, block: @escaping (@escaping (Error?) -> Void) -> Void) {
+ public init(
+ dispatchQueue: DispatchQueue? = nil,
+ block: @escaping @Sendable (@escaping @Sendable (Error?) -> Void) -> Void
+ ) {
super.init(dispatchQueue: dispatchQueue)
executor = { finish in
block(finish)
@@ -22,7 +25,7 @@ public class AsyncBlockOperation: AsyncOperation {
}
}
- public init(dispatchQueue: DispatchQueue? = nil, block: @escaping () -> Void) {
+ public init(dispatchQueue: DispatchQueue? = nil, block: @escaping @Sendable () -> Void) {
super.init(dispatchQueue: dispatchQueue)
executor = { finish in
block()
@@ -33,7 +36,7 @@ public class AsyncBlockOperation: AsyncOperation {
public init(
dispatchQueue: DispatchQueue? = nil,
- cancellableTask: @escaping (@escaping (Error?) -> Void) -> Cancellable
+ cancellableTask: @escaping @Sendable (@escaping @Sendable (Error?) -> Void) -> Cancellable
) {
super.init(dispatchQueue: dispatchQueue)
executor = { cancellableTask($0) }
diff --git a/ios/Operations/AsyncOperation.swift b/ios/Operations/AsyncOperation.swift
index 034f17f199..771bf334fa 100644
--- a/ios/Operations/AsyncOperation.swift
+++ b/ios/Operations/AsyncOperation.swift
@@ -39,7 +39,7 @@ import Foundation
}
/// A base implementation of an asynchronous operation
-open class AsyncOperation: Operation {
+open class AsyncOperation: Operation, @unchecked Sendable {
/// Mutex lock used for guarding critical sections of operation lifecycle.
private let operationLock = NSRecursiveLock()
@@ -199,7 +199,7 @@ open class AsyncOperation: Operation {
state = .evaluatingConditions
- var results = [Bool](repeating: false, count: _conditions.count)
+ nonisolated(unsafe) var results = [Bool](repeating: false, count: _conditions.count)
let group = DispatchGroup()
for (index, condition) in _conditions.enumerated() {
diff --git a/ios/Operations/AsyncOperationQueue.swift b/ios/Operations/AsyncOperationQueue.swift
index 57c4451138..cf4c421756 100644
--- a/ios/Operations/AsyncOperationQueue.swift
+++ b/ios/Operations/AsyncOperationQueue.swift
@@ -8,7 +8,7 @@
import Foundation
-public final class AsyncOperationQueue: OperationQueue {
+public final class AsyncOperationQueue: OperationQueue, @unchecked Sendable {
override public func addOperation(_ operation: Operation) {
if let operation = operation as? AsyncOperation {
let categories = operation.conditions
@@ -50,7 +50,7 @@ public final class AsyncOperationQueue: OperationQueue {
}
}
-private final class ExclusivityManager {
+private final class ExclusivityManager: @unchecked Sendable {
static let shared = ExclusivityManager()
private var operationsByCategory = [String: [Operation]]()
diff --git a/ios/Operations/BackgroundObserver.swift b/ios/Operations/BackgroundObserver.swift
index 5165780a15..5f5add27bc 100644
--- a/ios/Operations/BackgroundObserver.swift
+++ b/ios/Operations/BackgroundObserver.swift
@@ -26,7 +26,13 @@ public final class BackgroundObserver: OperationObserver {
}
public func didAttach(to operation: Operation) {
+ #if swift(>=6)
+ let expirationHandler = cancelUponExpiration
+ ? { @MainActor in operation.cancel() } as? @MainActor @Sendable () -> Void
+ : nil
+ #else
let expirationHandler = cancelUponExpiration ? { operation.cancel() } : nil
+ #endif
taskIdentifier = backgroundTaskProvider.beginBackgroundTask(
withName: name,
diff --git a/ios/Operations/GroupOperation.swift b/ios/Operations/GroupOperation.swift
index 1f474e7b28..5d8015ec8e 100644
--- a/ios/Operations/GroupOperation.swift
+++ b/ios/Operations/GroupOperation.swift
@@ -8,7 +8,7 @@
import Foundation
-public final class GroupOperation: AsyncOperation {
+public final class GroupOperation: AsyncOperation, @unchecked Sendable {
private let operationQueue = AsyncOperationQueue()
private let children: [Operation]
diff --git a/ios/Operations/OutputOperation.swift b/ios/Operations/OutputOperation.swift
index 82274b1450..1a9e1acb87 100644
--- a/ios/Operations/OutputOperation.swift
+++ b/ios/Operations/OutputOperation.swift
@@ -9,7 +9,7 @@
import Foundation
public protocol OutputOperation: Operation {
- associatedtype Output
+ associatedtype Output: Sendable
var output: Output? { get }
}
diff --git a/ios/Operations/ResultBlockOperation.swift b/ios/Operations/ResultBlockOperation.swift
index 867dee45cf..cb4f5fff64 100644
--- a/ios/Operations/ResultBlockOperation.swift
+++ b/ios/Operations/ResultBlockOperation.swift
@@ -9,24 +9,24 @@
import Foundation
import protocol MullvadTypes.Cancellable
-public final class ResultBlockOperation<Success>: ResultOperation<Success> {
- private var executor: ((@escaping (Result<Success, Error>) -> Void) -> Cancellable?)?
+public final class ResultBlockOperation<Success: Sendable>: ResultOperation<Success>, @unchecked Sendable {
+ private var executor: ((@escaping @Sendable (Result<Success, Error>) -> Void) -> Cancellable?)?
private var cancellableTask: Cancellable?
public init(
dispatchQueue: DispatchQueue? = nil,
- executionBlock: @escaping (_ finish: @escaping (Result<Success, Error>) -> Void) -> Void
+ executionBlock: @escaping @Sendable (_ finish: @escaping (Result<Success, Error>) -> Void) -> Void
) {
super.init(dispatchQueue: dispatchQueue)
- executor = { finish in
+ executor = { @Sendable finish in
executionBlock(finish)
return nil
}
}
- public init(dispatchQueue: DispatchQueue? = nil, executionBlock: @escaping () throws -> Success) {
+ public init(dispatchQueue: DispatchQueue? = nil, executionBlock: @escaping @Sendable () throws -> Success) {
super.init(dispatchQueue: dispatchQueue)
- executor = { finish in
+ executor = { @Sendable finish in
finish(Result { try executionBlock() })
return nil
}
@@ -34,7 +34,7 @@ public final class ResultBlockOperation<Success>: ResultOperation<Success> {
public init(
dispatchQueue: DispatchQueue? = nil,
- cancellableTask: @escaping (_ finish: @escaping (Result<Success, Error>) -> Void) -> Cancellable
+ cancellableTask: @escaping (_ finish: @escaping @Sendable (Result<Success, Error>) -> Void) -> Cancellable
) {
super.init(dispatchQueue: dispatchQueue)
executor = { cancellableTask($0) }
diff --git a/ios/Operations/ResultOperation.swift b/ios/Operations/ResultOperation.swift
index e377ecf9f5..2debb4381a 100644
--- a/ios/Operations/ResultOperation.swift
+++ b/ios/Operations/ResultOperation.swift
@@ -9,8 +9,8 @@
import Foundation
/// Base class for operations producing result.
-open class ResultOperation<Success>: AsyncOperation, OutputOperation, @unchecked Sendable {
- public typealias CompletionHandler = (Result<Success, Error>) -> Void
+open class ResultOperation<Success: Sendable>: AsyncOperation, OutputOperation, @unchecked Sendable {
+ public typealias CompletionHandler = @Sendable (Result<Success, Error>) -> Void
private let nslock = NSLock()
private var _output: Success?
@@ -118,7 +118,7 @@ open class ResultOperation<Success>: AsyncOperation, OutputOperation, @unchecked
let completionQueue = _completionQueue
nslock.unlock()
- let block = {
+ let block: @Sendable () -> Void = {
// Call completion handler.
completionHandler?(result)
diff --git a/ios/OperationsTests/AsyncBlockOperationTests.swift b/ios/OperationsTests/AsyncBlockOperationTests.swift
index c19e5e8a3f..00a7796f1c 100644
--- a/ios/OperationsTests/AsyncBlockOperationTests.swift
+++ b/ios/OperationsTests/AsyncBlockOperationTests.swift
@@ -14,7 +14,7 @@ import XCTest
final class AsyncBlockOperationTests: XCTestCase {
let operationQueue = AsyncOperationQueue()
- func testBlockOperation() {
+ func testBlockOperation() async {
let executionExpectation = expectation(description: "Should execute")
let finishExpectation = expectation(description: "Should finish")
@@ -29,10 +29,10 @@ final class AsyncBlockOperationTests: XCTestCase {
operationQueue.addOperation(operation)
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [executionExpectation, finishExpectation], timeout: .UnitTest.timeout)
}
- func testSynchronousBlockOperation() {
+ func testSynchronousBlockOperation() async {
let executionExpectation = expectation(description: "Should execute")
let finishExpectation = expectation(description: "Should finish")
@@ -46,10 +46,10 @@ final class AsyncBlockOperationTests: XCTestCase {
operationQueue.addOperation(operation)
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [executionExpectation, finishExpectation], timeout: .UnitTest.timeout)
}
- func testCancellableTaskBlockOperation() {
+ func testCancellableTaskBlockOperation() async {
let executionExpectation = expectation(description: "Should execute")
let cancelExpectation = expectation(description: "Should cancel")
let finishExpectation = expectation(description: "Should finish")
@@ -73,10 +73,10 @@ final class AsyncBlockOperationTests: XCTestCase {
operationQueue.addOperation(operation)
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [executionExpectation, cancelExpectation, finishExpectation], timeout: .UnitTest.timeout)
}
- func testCancellationShouldNotFireBeforeOperationIsEnqueued() throws {
+ func testCancellationShouldNotFireBeforeOperationIsEnqueued() async throws {
let expect = expectation(description: "Cancellation should not fire.")
expect.isInverted = true
@@ -84,10 +84,10 @@ final class AsyncBlockOperationTests: XCTestCase {
operation.onCancel { _ in expect.fulfill() }
operation.cancel()
- waitForExpectations(timeout: .UnitTest.invertedTimeout)
+ await fulfillment(of: [expect], timeout: .UnitTest.invertedTimeout)
}
- func testCancellationShouldFireAfterCancelledOperationIsEnqueued() throws {
+ func testCancellationShouldFireAfterCancelledOperationIsEnqueued() async throws {
let expect = expectation(description: "Cancellation should fire.")
let operation = AsyncBlockOperation {}
@@ -95,6 +95,6 @@ final class AsyncBlockOperationTests: XCTestCase {
operation.cancel()
operationQueue.addOperation(operation)
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expect], timeout: .UnitTest.timeout)
}
}
diff --git a/ios/OperationsTests/AsyncResultBlockOperationTests.swift b/ios/OperationsTests/AsyncResultBlockOperationTests.swift
index 0cccf93241..9cb669e87b 100644
--- a/ios/OperationsTests/AsyncResultBlockOperationTests.swift
+++ b/ios/OperationsTests/AsyncResultBlockOperationTests.swift
@@ -14,7 +14,7 @@ import XCTest
final class AsyncResultBlockOperationTests: XCTestCase {
let operationQueue = AsyncOperationQueue()
- func testBlockOperation() {
+ func testBlockOperation() async {
let expectation = expectation(description: "Should finish")
let operation = ResultBlockOperation<Bool> { finish in
@@ -28,10 +28,10 @@ final class AsyncResultBlockOperationTests: XCTestCase {
operationQueue.addOperation(operation)
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expectation], timeout: .UnitTest.timeout)
}
- func testThrowingBlockOperation() {
+ func testThrowingBlockOperation() async {
let expectation = expectation(description: "Should finish")
let operation = ResultBlockOperation {
@@ -47,10 +47,10 @@ final class AsyncResultBlockOperationTests: XCTestCase {
operationQueue.addOperation(operation)
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expectation], timeout: .UnitTest.timeout)
}
- func testCancellableTaskOperation() {
+ func testCancellableTaskOperation() async {
let expectation = expectation(description: "Should finish")
let operation = ResultBlockOperation<Bool> { finish -> Cancellable in
@@ -71,6 +71,6 @@ final class AsyncResultBlockOperationTests: XCTestCase {
operationQueue.addOperation(operation)
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expectation], timeout: .UnitTest.timeout)
}
}
diff --git a/ios/OperationsTests/OperationConditionTests.swift b/ios/OperationsTests/OperationConditionTests.swift
index 26abaca2e9..02be4f3888 100644
--- a/ios/OperationsTests/OperationConditionTests.swift
+++ b/ios/OperationsTests/OperationConditionTests.swift
@@ -10,6 +10,7 @@
import Operations
import XCTest
+@MainActor
class OperationConditionTests: XCTestCase {
func testTrueCondition() {
let expectConditionEvaluation = expectation(description: "Expect condition evaluation")
diff --git a/ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift b/ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift
index 6c4e00b156..1fbdff9a7f 100644
--- a/ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift
+++ b/ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift
@@ -27,7 +27,7 @@ import WireGuardKitTypes
Other times, packet tunnel runs this operation with `rotateImmediatelyOnKeyMismatch` set to `false`, in which
case it respects the 24 hour interval between key rotation retry attempts.
*/
-final class DeviceCheckOperation: ResultOperation<DeviceCheck> {
+final class DeviceCheckOperation: ResultOperation<DeviceCheck>, @unchecked Sendable {
private let logger = Logger(label: "DeviceCheckOperation")
private let remoteService: DeviceCheckRemoteServiceProtocol
@@ -127,8 +127,8 @@ final class DeviceCheckOperation: ResultOperation<DeviceCheck> {
accountNumber: String, deviceIdentifier: String,
completion: @escaping (Result<Account, Error>, Result<Device, Error>) -> Void
) {
- var accountResult: Result<Account, Error> = .failure(OperationError.cancelled)
- var deviceResult: Result<Device, Error> = .failure(OperationError.cancelled)
+ nonisolated(unsafe) var accountResult: Result<Account, Error> = .failure(OperationError.cancelled)
+ nonisolated(unsafe) var deviceResult: Result<Device, Error> = .failure(OperationError.cancelled)
let dispatchGroup = DispatchGroup()
diff --git a/ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteService.swift b/ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteService.swift
index 2bae95fe3c..1668680631 100644
--- a/ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteService.swift
+++ b/ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteService.swift
@@ -23,7 +23,7 @@ struct DeviceCheckRemoteService: DeviceCheckRemoteServiceProtocol {
func getAccountData(
accountNumber: String,
- completion: @escaping (Result<Account, Error>) -> Void
+ completion: @escaping @Sendable (Result<Account, Error>) -> Void
) -> Cancellable {
accountsProxy.getAccountData(accountNumber: accountNumber).execute(completionHandler: completion)
}
@@ -31,7 +31,7 @@ struct DeviceCheckRemoteService: DeviceCheckRemoteServiceProtocol {
func getDevice(
accountNumber: String,
identifier: String,
- completion: @escaping (Result<Device, Error>) -> Void
+ completion: @escaping @Sendable (Result<Device, Error>) -> Void
) -> Cancellable {
devicesProxy.getDevice(
accountNumber: accountNumber,
@@ -45,7 +45,7 @@ struct DeviceCheckRemoteService: DeviceCheckRemoteServiceProtocol {
accountNumber: String,
identifier: String,
publicKey: PublicKey,
- completion: @escaping (Result<Device, Error>) -> Void
+ completion: @escaping @Sendable (Result<Device, Error>) -> Void
) -> Cancellable {
devicesProxy.rotateDeviceKey(
accountNumber: accountNumber,
diff --git a/ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteServiceProtocol.swift b/ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteServiceProtocol.swift
index 0cab26654f..05030dba89 100644
--- a/ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteServiceProtocol.swift
+++ b/ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteServiceProtocol.swift
@@ -12,14 +12,18 @@ import WireGuardKitTypes
/// A protocol that formalizes remote service dependency used by `DeviceCheckOperation`.
protocol DeviceCheckRemoteServiceProtocol {
- func getAccountData(accountNumber: String, completion: @escaping (Result<Account, Error>) -> Void)
+ func getAccountData(accountNumber: String, completion: @escaping @Sendable (Result<Account, Error>) -> Void)
-> Cancellable
- func getDevice(accountNumber: String, identifier: String, completion: @escaping (Result<Device, Error>) -> Void)
+ func getDevice(
+ accountNumber: String,
+ identifier: String,
+ completion: @escaping @Sendable (Result<Device, Error>) -> Void
+ )
-> Cancellable
func rotateDeviceKey(
accountNumber: String,
identifier: String,
publicKey: PublicKey,
- completion: @escaping (Result<Device, Error>) -> Void
+ completion: @escaping @Sendable (Result<Device, Error>) -> Void
) -> Cancellable
}
diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift
index 56953553dd..91c650ab44 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift
@@ -10,7 +10,7 @@ import Combine
import NetworkExtension
import PacketTunnelCore
-final class PacketTunnelPathObserver: DefaultPathObserverProtocol {
+final class PacketTunnelPathObserver: DefaultPathObserverProtocol, @unchecked Sendable {
private weak var packetTunnelProvider: NEPacketTunnelProvider?
private let stateLock = NSLock()
private var pathUpdatePublisher: AnyCancellable?
@@ -25,7 +25,7 @@ final class PacketTunnelPathObserver: DefaultPathObserverProtocol {
return packetTunnelProvider?.defaultPath
}
- func start(_ body: @escaping (NetworkPath) -> Void) {
+ func start(_ body: @escaping @Sendable (NetworkPath) -> Void) {
stateLock.withLock {
pathUpdatePublisher?.cancel()
diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
index 133ba93b8a..e86b0c203f 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
@@ -16,7 +16,7 @@ import NetworkExtension
import PacketTunnelCore
import WireGuardKitTypes
-class PacketTunnelProvider: NEPacketTunnelProvider {
+class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
private let internalQueue = DispatchQueue(label: "PacketTunnel-internalQueue")
private let providerLogger: Logger
@@ -179,7 +179,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
private func performSettingsMigration() {
- var hasNotMigrated = true
+ nonisolated(unsafe) var hasNotMigrated = true
repeat {
migrationManager.migrateSettings(
store: SettingsManager.store,
diff --git a/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift b/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
index eca0774e74..8ac4074a28 100644
--- a/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
+++ b/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
@@ -7,13 +7,13 @@
//
import Foundation
-import MullvadLogging
+@preconcurrency import MullvadLogging
import MullvadTypes
import NetworkExtension
import PacketTunnelCore
-import WireGuardKit
+@preconcurrency import WireGuardKit
-class WgAdapter: TunnelAdapterProtocol {
+class WgAdapter: TunnelAdapterProtocol, @unchecked Sendable {
let logger = Logger(label: "WgAdapter")
let adapter: WireGuardAdapter
diff --git a/ios/PacketTunnelCore/Actor/AnyTask.swift b/ios/PacketTunnelCore/Actor/AnyTask.swift
index 43c44b0644..40d723b402 100644
--- a/ios/PacketTunnelCore/Actor/AnyTask.swift
+++ b/ios/PacketTunnelCore/Actor/AnyTask.swift
@@ -9,7 +9,7 @@
import Foundation
/// A type-erased `Task`.
-public protocol AnyTask {
+public protocol AnyTask: Sendable {
/// Cancel task.
func cancel()
}
diff --git a/ios/PacketTunnelCore/Actor/AutoCancellingTask.swift b/ios/PacketTunnelCore/Actor/AutoCancellingTask.swift
index c80e28f880..46ac15d0a2 100644
--- a/ios/PacketTunnelCore/Actor/AutoCancellingTask.swift
+++ b/ios/PacketTunnelCore/Actor/AutoCancellingTask.swift
@@ -13,7 +13,7 @@ import Foundation
It behaves identical to `Combine.AnyCancellable`.
*/
-public final class AutoCancellingTask {
+public final class AutoCancellingTask: Sendable {
private let task: AnyTask
init(_ task: AnyTask) {
diff --git a/ios/PacketTunnelCore/Actor/EphemeralPeerNegotiationState.swift b/ios/PacketTunnelCore/Actor/EphemeralPeerNegotiationState.swift
index 18eee61f3b..f34830ff01 100644
--- a/ios/PacketTunnelCore/Actor/EphemeralPeerNegotiationState.swift
+++ b/ios/PacketTunnelCore/Actor/EphemeralPeerNegotiationState.swift
@@ -8,9 +8,9 @@
import MullvadREST
import MullvadTypes
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
-public enum EphemeralPeerNegotiationState: Equatable {
+public enum EphemeralPeerNegotiationState: Equatable, Sendable {
case single(EphemeralPeerRelayConfiguration)
case multi(entry: EphemeralPeerRelayConfiguration, exit: EphemeralPeerRelayConfiguration)
@@ -26,7 +26,7 @@ public enum EphemeralPeerNegotiationState: Equatable {
}
}
-public struct EphemeralPeerRelayConfiguration: Equatable, CustomDebugStringConvertible {
+public struct EphemeralPeerRelayConfiguration: Equatable, CustomDebugStringConvertible, Sendable {
public let relay: SelectedRelay
public let configuration: EphemeralPeerConfiguration
@@ -40,7 +40,7 @@ public struct EphemeralPeerRelayConfiguration: Equatable, CustomDebugStringConve
}
}
-public struct EphemeralPeerConfiguration: Equatable, CustomDebugStringConvertible {
+public struct EphemeralPeerConfiguration: Equatable, CustomDebugStringConvertible, Sendable {
public let privateKey: PrivateKey
public let preSharedKey: PreSharedKey?
public let allowedIPs: [IPAddressRange]
diff --git a/ios/PacketTunnelCore/Actor/ObservedState.swift b/ios/PacketTunnelCore/Actor/ObservedState.swift
index 8b3779284e..16c4b08f9d 100644
--- a/ios/PacketTunnelCore/Actor/ObservedState.swift
+++ b/ios/PacketTunnelCore/Actor/ObservedState.swift
@@ -11,10 +11,10 @@ import Foundation
import MullvadREST
import MullvadTypes
import Network
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
/// A serializable representation of internal state.
-public enum ObservedState: Equatable, Codable {
+public enum ObservedState: Equatable, Codable, Sendable {
case initial
case connecting(ObservedConnectionState)
case reconnecting(ObservedConnectionState)
@@ -26,7 +26,7 @@ public enum ObservedState: Equatable, Codable {
}
/// A serializable representation of internal connection state.
-public struct ObservedConnectionState: Equatable, Codable {
+public struct ObservedConnectionState: Equatable, Codable, Sendable {
public var selectedRelays: SelectedRelays
public var relayConstraints: RelayConstraints
public var networkReachability: NetworkReachability
@@ -65,7 +65,7 @@ public struct ObservedConnectionState: Equatable, Codable {
}
/// A serializable representation of internal blocked state.
-public struct ObservedBlockedState: Equatable, Codable {
+public struct ObservedBlockedState: Equatable, Codable, Sendable {
public var reason: BlockedStateReason
public var relayConstraints: RelayConstraints?
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift
index 3610c3ff50..e4ec189d51 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift
@@ -6,6 +6,7 @@
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//
+import Combine
import Foundation
extension PacketTunnelActor {
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
index 9fbc650d70..d15157b7e0 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
@@ -7,7 +7,7 @@
//
import Foundation
-import MullvadLogging
+@preconcurrency import MullvadLogging
import MullvadREST
import MullvadRustRuntime
import MullvadSettings
@@ -38,11 +38,11 @@ public actor PacketTunnelActor {
@Published internal(set) public var observedState: ObservedState = .initial
- nonisolated let logger = Logger(label: "PacketTunnelActor")
+ nonisolated(unsafe) let logger = Logger(label: "PacketTunnelActor")
let timings: PacketTunnelActorTimings
let tunnelAdapter: TunnelAdapterProtocol
- nonisolated let tunnelMonitor: TunnelMonitorProtocol
+ let tunnelMonitor: TunnelMonitorProtocol
let defaultPathObserver: DefaultPathObserverProtocol
let blockedStateErrorMapper: BlockedStateErrorMapperProtocol
public let relaySelector: RelaySelectorProtocol
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
index 3e7ab37e54..a6e1d20504 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
@@ -12,7 +12,7 @@ import WireGuardKitTypes
extension PacketTunnelActor {
/// Describes events that the state machine handles. These can be user commands or non-user-initiated events
- enum Event {
+ enum Event: Sendable {
/// Start tunnel.
case start(StartOptions)
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift
index 5b01778f2d..e69bfc0214 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift
@@ -12,7 +12,7 @@ import WireGuardKitTypes
extension PacketTunnelActor {
/// A structure encoding an effect; each event will yield zero or more of those, which can then be sequentially executed.
- enum Effect: Equatable {
+ enum Effect: Equatable, Sendable {
case startDefaultPathObserver
case stopDefaultPathObserver
case startTunnelMonitor
@@ -54,7 +54,7 @@ extension PacketTunnelActor {
}
}
- struct Reducer {
+ struct Reducer: Sendable {
static func reduce(_ state: inout State, _ event: Event) -> [Effect] {
switch event {
case let .start(options):
diff --git a/ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift b/ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift
index a1107efc2a..49f501ee90 100644
--- a/ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift
+++ b/ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadSettings
import MullvadTypes
import Network
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
/// A type that implements a reader that can return settings required by `PacketTunnelActor` in order to configure the tunnel.
public protocol SettingsReaderProtocol {
@@ -24,7 +24,7 @@ public protocol SettingsReaderProtocol {
}
/// Struct holding settings necessary to configure packet tunnel adapter.
-public struct Settings: Equatable {
+public struct Settings: Equatable, Sendable {
/// Private key used by device.
public var privateKey: PrivateKey
@@ -89,7 +89,7 @@ extension Settings {
}
/// Enum describing selected DNS servers option.
-public enum SelectedDNSServers: Equatable {
+public enum SelectedDNSServers: Equatable, Sendable {
/// Custom DNS servers.
case custom([IPAddress])
/// Mullvad server acting as a blocking DNS proxy.
diff --git a/ios/PacketTunnelCore/Actor/Protocols/TunnelAdapterProtocol.swift b/ios/PacketTunnelCore/Actor/Protocols/TunnelAdapterProtocol.swift
index f99ccb0e82..630e1c6515 100644
--- a/ios/PacketTunnelCore/Actor/Protocols/TunnelAdapterProtocol.swift
+++ b/ios/PacketTunnelCore/Actor/Protocols/TunnelAdapterProtocol.swift
@@ -10,10 +10,10 @@ import Foundation
import MullvadTypes
import Network
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
/// Protocol describing interface for any kind of adapter implementing a VPN tunnel.
-public protocol TunnelAdapterProtocol {
+public protocol TunnelAdapterProtocol: Sendable {
/// Start tunnel adapter or update active configuration.
func start(configuration: TunnelAdapterConfiguration, daita: DaitaConfiguration?) async throws
diff --git a/ios/PacketTunnelCore/Actor/StartOptions.swift b/ios/PacketTunnelCore/Actor/StartOptions.swift
index 9af92fe34c..25e38e93b3 100644
--- a/ios/PacketTunnelCore/Actor/StartOptions.swift
+++ b/ios/PacketTunnelCore/Actor/StartOptions.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadREST
/// Packet tunnel start options parsed from dictionary passed to packet tunnel with a call to `startTunnel()`.
-public struct StartOptions {
+public struct StartOptions: Sendable {
/// The system that triggered the launch of packet tunnel.
public var launchSource: LaunchSource
@@ -36,7 +36,7 @@ public struct StartOptions {
}
/// The source facility that triggered a launch of packet tunnel extension.
-public enum LaunchSource: String, CustomStringConvertible {
+public enum LaunchSource: String, CustomStringConvertible, Sendable {
/// Launched by the main bundle app using network extension framework.
case app
diff --git a/ios/PacketTunnelCore/Actor/State.swift b/ios/PacketTunnelCore/Actor/State.swift
index 88b69b0485..537f201ee2 100644
--- a/ios/PacketTunnelCore/Actor/State.swift
+++ b/ios/PacketTunnelCore/Actor/State.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadREST
import MullvadRustRuntime
import MullvadTypes
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
/**
Tunnel actor state with metadata describing the current phase of packet tunnel lifecycle.
@@ -87,7 +87,7 @@ enum State: Equatable {
}
/// Enum describing network availability.
-public enum NetworkReachability: Equatable, Codable {
+public enum NetworkReachability: Equatable, Codable, Sendable {
case undetermined, reachable, unreachable
}
@@ -100,7 +100,7 @@ protocol StateAssociatedData {
extension State {
/// Policy describing what WG key to use for tunnel communication.
- enum KeyPolicy {
+ enum KeyPolicy: Sendable {
/// Use current key stored in device data.
case useCurrent
@@ -156,7 +156,7 @@ extension State {
}
/// Data associated with error state.
- struct BlockingData: StateAssociatedData {
+ struct BlockingData: StateAssociatedData, Sendable {
/// Reason why block state was entered.
public var reason: BlockedStateReason
@@ -188,7 +188,7 @@ extension State {
}
/// Reason why packet tunnel entered error state.
-public enum BlockedStateReason: String, Codable, Equatable {
+public enum BlockedStateReason: String, Codable, Equatable, Sendable {
/// Device is locked.
case deviceLocked
@@ -244,7 +244,7 @@ extension State.BlockingData {
}
/// Describes which relay the tunnel should connect to next.
-public enum NextRelays: Equatable, Codable {
+public enum NextRelays: Equatable, Codable, Sendable {
/// Select next relays randomly.
case random
@@ -256,7 +256,7 @@ public enum NextRelays: Equatable, Codable {
}
/// Describes the reason for reconnection request.
-public enum ActorReconnectReason: Equatable {
+public enum ActorReconnectReason: Equatable, Sendable {
/// Initiated by user.
case userInitiated
diff --git a/ios/PacketTunnelCore/Actor/Task+Duration.swift b/ios/PacketTunnelCore/Actor/Task+Duration.swift
index 46e90bd196..91f2ac39ba 100644
--- a/ios/PacketTunnelCore/Actor/Task+Duration.swift
+++ b/ios/PacketTunnelCore/Actor/Task+Duration.swift
@@ -36,7 +36,7 @@ extension Task where Success == Never, Failure == Never {
*/
@available(iOS, introduced: 15.0, obsoleted: 16.0, message: "Replace with Task.sleep(for:tolerance:clock:).")
static func sleepUsingContinuousClock(for duration: Duration) async throws {
- let timer = DispatchSource.makeTimerSource()
+ nonisolated(unsafe) let timer = DispatchSource.makeTimerSource()
try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
diff --git a/ios/PacketTunnelCore/Actor/Timings.swift b/ios/PacketTunnelCore/Actor/Timings.swift
index 5a62f7b058..947bbaf4a8 100644
--- a/ios/PacketTunnelCore/Actor/Timings.swift
+++ b/ios/PacketTunnelCore/Actor/Timings.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadTypes
/// Struct holding all timings used by tunnel actor.
-public struct PacketTunnelActorTimings {
+public struct PacketTunnelActorTimings: Sendable {
/// Periodicity at which actor will attempt to restart when an error occurred on system boot when filesystem is locked until device is unlocked or tunnel adapter error.
public var bootRecoveryPeriodicity: Duration
diff --git a/ios/PacketTunnelCore/TunnelMonitor/DefaultPathObserverProtocol.swift b/ios/PacketTunnelCore/TunnelMonitor/DefaultPathObserverProtocol.swift
index d4d192fb74..b330ff93d0 100644
--- a/ios/PacketTunnelCore/TunnelMonitor/DefaultPathObserverProtocol.swift
+++ b/ios/PacketTunnelCore/TunnelMonitor/DefaultPathObserverProtocol.swift
@@ -10,19 +10,19 @@ import Foundation
import NetworkExtension
/// A type providing default path access and observation.
-public protocol DefaultPathObserverProtocol {
+public protocol DefaultPathObserverProtocol: Sendable {
/// Returns current default path or `nil` if unknown yet.
var defaultPath: NetworkPath? { get }
/// Start observing changes to `defaultPath`.
/// This call must be idempotent. Multiple calls to start should replace the existing handler block.
- func start(_ body: @escaping (NetworkPath) -> Void)
+ func start(_ body: @escaping @Sendable (NetworkPath) -> Void)
/// Stop observing changes to `defaultPath`.
func stop()
}
/// A type that represents a network path.
-public protocol NetworkPath {
+public protocol NetworkPath: Sendable {
var status: NetworkExtension.NWPathStatus { get }
}
diff --git a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorProtocol.swift b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorProtocol.swift
index 0a47d01fb7..63677eb9ee 100644
--- a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorProtocol.swift
+++ b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorProtocol.swift
@@ -10,7 +10,7 @@ import Foundation
import Network
/// Tunnel monitor event.
-public enum TunnelMonitorEvent {
+public enum TunnelMonitorEvent: Sendable {
/// Dispatched after receiving the first ping response
case connectionEstablished
@@ -20,7 +20,7 @@ public enum TunnelMonitorEvent {
}
/// A type that can provide tunnel monitoring.
-public protocol TunnelMonitorProtocol: AnyObject {
+public protocol TunnelMonitorProtocol: AnyObject, Sendable {
/// Event handler that starts receiving events after the call to `start(probeAddress:)`.
var onEvent: ((TunnelMonitorEvent) -> Void)? { get set }
diff --git a/ios/PacketTunnelCore/URLRequestProxy/ProxyURLResponse.swift b/ios/PacketTunnelCore/URLRequestProxy/ProxyURLResponse.swift
index 1e13bf9b83..65c7e8e6e2 100644
--- a/ios/PacketTunnelCore/URLRequestProxy/ProxyURLResponse.swift
+++ b/ios/PacketTunnelCore/URLRequestProxy/ProxyURLResponse.swift
@@ -9,7 +9,7 @@
import Foundation
/// Struct describing serializable URLResponse data.
-public struct ProxyURLResponse: Codable {
+public struct ProxyURLResponse: Codable, Sendable {
public let data: Data?
public let response: HTTPURLResponseWrapper?
public let error: URLErrorWrapper?
@@ -21,7 +21,7 @@ public struct ProxyURLResponse: Codable {
}
}
-public struct URLErrorWrapper: Codable {
+public struct URLErrorWrapper: Codable, Sendable {
public let code: Int?
public let localizedDescription: String
@@ -37,7 +37,7 @@ public struct URLErrorWrapper: Codable {
}
}
-public struct HTTPURLResponseWrapper: Codable {
+public struct HTTPURLResponseWrapper: Codable, Sendable {
public let url: URL?
public let statusCode: Int
public let headerFields: [String: String]?
diff --git a/ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift b/ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift
index 8bde84f781..6a036ee6b0 100644
--- a/ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift
@@ -15,7 +15,7 @@ struct NetworkPathStub: NetworkPath {
}
/// Default path observer fake that uses in-memory storage to keep current path and provides a method to simulate path change from tests.
-class DefaultPathObserverFake: DefaultPathObserverProtocol {
+class DefaultPathObserverFake: DefaultPathObserverProtocol, @unchecked Sendable {
var defaultPath: NetworkPath? {
return stateLock.withLock { innerPath }
}
diff --git a/ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift b/ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift
index 3c8e9552e7..c8afc4acd5 100644
--- a/ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift
@@ -11,7 +11,7 @@ import PacketTunnelCore
@testable import WireGuardKitTypes
/// Dummy tunnel adapter that does nothing and reports no errors.
-class TunnelAdapterDummy: TunnelAdapterProtocol {
+class TunnelAdapterDummy: TunnelAdapterProtocol, @unchecked Sendable {
func startMultihop(
entryConfiguration: TunnelAdapterConfiguration?,
exitConfiguration: TunnelAdapterConfiguration,
diff --git a/ios/PacketTunnelCoreTests/Mocks/TunnelMonitorStub.swift b/ios/PacketTunnelCoreTests/Mocks/TunnelMonitorStub.swift
index 60609fee36..26abbf05f8 100644
--- a/ios/PacketTunnelCoreTests/Mocks/TunnelMonitorStub.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/TunnelMonitorStub.swift
@@ -11,7 +11,7 @@ import Network
import PacketTunnelCore
/// Tunnel monitor stub that can be configured with block handler to simulate a specific behavior.
-class TunnelMonitorStub: TunnelMonitorProtocol {
+class TunnelMonitorStub: TunnelMonitorProtocol, @unchecked Sendable {
enum Command {
case start, stop
}
diff --git a/ios/Routing/Coordinator.swift b/ios/Routing/Coordinator.swift
index 46e343e648..8ee1ff8c5c 100644
--- a/ios/Routing/Coordinator.swift
+++ b/ios/Routing/Coordinator.swift
@@ -15,7 +15,8 @@ import UIKit
Coordinators help to abstract the navigation and business logic from view controllers making them
more manageable and reusable.
*/
-open class Coordinator: NSObject {
+@MainActor
+open class Coordinator: NSObject, Sendable {
/// Private trace log.
private lazy var logger = Logger(label: "\(Self.self)")
@@ -82,7 +83,7 @@ open class Coordinator: NSObject {
/**
Protocol describing coordinators that can be presented using modal presentation.
*/
-public protocol Presentable: Coordinator {
+public protocol Presentable: Coordinator, Sendable {
/**
View controller that is presented modally. It's expected it to be the topmost view controller
managed by coordinator.
@@ -177,7 +178,7 @@ extension Presentable {
Automatically removes itself from parent.
*/
- public func dismiss(animated: Bool, completion: (() -> Void)? = nil) {
+ public func dismiss(animated: Bool, completion: (@MainActor () -> Void)? = nil) {
removeFromParent()
presentedViewController.dismiss(animated: animated, completion: completion)
@@ -186,7 +187,7 @@ extension Presentable {
/**
Add block based observer triggered if coordinator is dismissed via user interaction.
*/
- public func onInteractiveDismissal(_ handler: @escaping (Coordinator) -> Void) {
+ public func onInteractiveDismissal(_ handler: @escaping @Sendable (Coordinator) -> Void) {
interactiveDismissalObservers.append(handler)
}
}
diff --git a/ios/Routing/ModalPresentationConfiguration.swift b/ios/Routing/ModalPresentationConfiguration.swift
index d50297fefc..15fccafdea 100644
--- a/ios/Routing/ModalPresentationConfiguration.swift
+++ b/ios/Routing/ModalPresentationConfiguration.swift
@@ -11,6 +11,7 @@ import UIKit
/**
A struct holding modal presentation configuration.
*/
+@MainActor
public struct ModalPresentationConfiguration {
var preferredContentSize: CGSize?
var modalPresentationStyle: UIModalPresentationStyle?
diff --git a/ios/Routing/PresentationControllerDismissalInterceptor.swift b/ios/Routing/PresentationControllerDismissalInterceptor.swift
index 90d92a8703..e56e6f15a1 100644
--- a/ios/Routing/PresentationControllerDismissalInterceptor.swift
+++ b/ios/Routing/PresentationControllerDismissalInterceptor.swift
@@ -15,7 +15,7 @@ import UIKit
final class PresentationControllerDismissalInterceptor: NSObject,
UIAdaptivePresentationControllerDelegate {
private let dismissHandler: (UIPresentationController) -> Void
- private let forwardingTarget: UIAdaptivePresentationControllerDelegate?
+ nonisolated(unsafe) private let forwardingTarget: UIAdaptivePresentationControllerDelegate?
private let protocolSelectors: [Selector]
init(
diff --git a/ios/Routing/Router/AppRouteProtocol.swift b/ios/Routing/Router/AppRouteProtocol.swift
index 85fbd53028..b15776ae27 100644
--- a/ios/Routing/Router/AppRouteProtocol.swift
+++ b/ios/Routing/Router/AppRouteProtocol.swift
@@ -11,7 +11,7 @@ import Foundation
/**
Formal protocol describing a group of routes.
*/
-public protocol AppRouteGroupProtocol: Comparable, Equatable, Hashable {
+public protocol AppRouteGroupProtocol: Comparable, Equatable, Hashable, Sendable {
/**
Returns `true` if group is presented modally, otherwise `false` if group is a part of root view
controller.
@@ -44,7 +44,7 @@ extension AppRouteGroupProtocol {
/**
Formal protocol describing a single route.
*/
-public protocol AppRouteProtocol: Equatable, Hashable {
+public protocol AppRouteProtocol: Equatable, Hashable, Sendable {
associatedtype RouteGroupType: AppRouteGroupProtocol
/**
diff --git a/ios/Routing/Router/ApplicationRouter.swift b/ios/Routing/Router/ApplicationRouter.swift
index d565875143..c55daf2400 100644
--- a/ios/Routing/Router/ApplicationRouter.swift
+++ b/ios/Routing/Router/ApplicationRouter.swift
@@ -13,8 +13,9 @@ import UIKit
/**
Main application router.
*/
-public final class ApplicationRouter<RouteType: AppRouteProtocol> {
- private let logger = Logger(label: "ApplicationRouter")
+@MainActor
+public final class ApplicationRouter<RouteType: AppRouteProtocol>: Sendable {
+ nonisolated(unsafe) private let logger = Logger(label: "ApplicationRouter")
private(set) var modalStack: [RouteType.RouteGroupType] = []
private(set) var presentedRoutes: [RouteType.RouteGroupType: [PresentedRoute<RouteType>]] = [:]
@@ -95,7 +96,7 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
_ route: RouteType,
animated: Bool,
metadata: Any?,
- completion: @escaping (PendingPresentationResult) -> Void
+ completion: @escaping @Sendable @MainActor (PendingPresentationResult) -> Void
) {
/**
Pass sub-route for routes supporting sub-navigation.
@@ -160,15 +161,19 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
/*
Synchronize router when modal controllers are removed by swipe.
*/
- if let presentable = coordinator as? Presentable {
- presentable.onInteractiveDismissal { [weak self] coordinator in
- self?.handleInteractiveDismissal(route: route, coordinator: coordinator)
+ MainActor.assumeIsolated {
+ if let presentable = coordinator as? Presentable {
+ presentable.onInteractiveDismissal { [weak self] coordinator in
+ MainActor.assumeIsolated {
+ self?.handleInteractiveDismissal(route: route, coordinator: coordinator)
+ }
+ }
}
- }
- self.addPresentedRoute(PresentedRoute(route: route, coordinator: coordinator))
+ self.addPresentedRoute(PresentedRoute(route: route, coordinator: coordinator))
- completion(.success)
+ completion(.success)
+ }
}
} else {
completion(.drop)
@@ -178,7 +183,7 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
private func dismissGroup(
_ dismissGroup: RouteType.RouteGroupType,
animated: Bool,
- completion: @escaping (PendingDismissalResult) -> Void
+ completion: @escaping @Sendable (PendingDismissalResult) -> Void
) {
/**
Check if routes corresponding to the group requested for dismissal are present.
@@ -224,7 +229,7 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
private func dismissRoute(
_ dismissRoute: RouteType,
animated: Bool,
- completion: @escaping (PendingDismissalResult) -> Void
+ completion: @escaping @Sendable (PendingDismissalResult) -> Void
) {
var routes = presentedRoutes[dismissRoute.routeGroup] ?? []
@@ -304,19 +309,21 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
case let .dismiss(dismissMatch):
handleDismissal(dismissMatch, animated: pendingRoute.animated) { result in
- switch result {
- case .success, .drop:
- self.finishPendingRoute(pendingRoute)
+ MainActor.assumeIsolated {
+ switch result {
+ case .success, .drop:
+ self.finishPendingRoute(pendingRoute)
- case .blockedByModalAbove:
- /**
- If router cannot dismiss modal because there is one above,
- try walking down the queue and see if there is a dismissal request that could
- resolve that.
- */
- self.processPendingRoutes(
- skipRouteGroups: skipRouteGroups.union([dismissMatch.routeGroup])
- )
+ case .blockedByModalAbove:
+ /**
+ If router cannot dismiss modal because there is one above,
+ try walking down the queue and see if there is a dismissal request that could
+ resolve that.
+ */
+ self.processPendingRoutes(
+ skipRouteGroups: skipRouteGroups.union([dismissMatch.routeGroup])
+ )
+ }
}
}
}
@@ -325,7 +332,7 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
private func handleDismissal(
_ dismissMatch: DismissMatch<RouteType>,
animated: Bool,
- completion: @escaping (PendingDismissalResult) -> Void
+ completion: @escaping @Sendable (PendingDismissalResult) -> Void
) {
switch dismissMatch {
case let .singleRoute(route):
diff --git a/ios/Routing/Router/ApplicationRouterDelegate.swift b/ios/Routing/Router/ApplicationRouterDelegate.swift
index a98870d303..2200066a1f 100644
--- a/ios/Routing/Router/ApplicationRouterDelegate.swift
+++ b/ios/Routing/Router/ApplicationRouterDelegate.swift
@@ -11,7 +11,7 @@ import Foundation
/**
Application router delegate
*/
-public protocol ApplicationRouterDelegate<RouteType>: AnyObject {
+public protocol ApplicationRouterDelegate<RouteType>: AnyObject, Sendable {
associatedtype RouteType: AppRouteProtocol
/**
@@ -21,7 +21,7 @@ public protocol ApplicationRouterDelegate<RouteType>: AnyObject {
_ router: ApplicationRouter<RouteType>,
presentWithContext context: RoutePresentationContext<RouteType>,
animated: Bool,
- completion: @escaping (Coordinator) -> Void
+ completion: @escaping @Sendable (Coordinator) -> Void
)
/**
@@ -30,7 +30,7 @@ public protocol ApplicationRouterDelegate<RouteType>: AnyObject {
func applicationRouter(
_ router: ApplicationRouter<RouteType>,
dismissWithContext context: RouteDismissalContext<RouteType>,
- completion: @escaping () -> Void
+ completion: @escaping @Sendable () -> Void
)
/**
@@ -57,6 +57,6 @@ public protocol ApplicationRouterDelegate<RouteType>: AnyObject {
func applicationRouter(
_ router: ApplicationRouter<RouteType>,
handleSubNavigationWithContext context: RouteSubnavigationContext<RouteType>,
- completion: @escaping () -> Void
+ completion: @escaping @Sendable @MainActor () -> Void
)
}
diff --git a/ios/Routing/Router/ApplicationRouterTypes.swift b/ios/Routing/Router/ApplicationRouterTypes.swift
index 7be142adaf..1a661dfd2a 100644
--- a/ios/Routing/Router/ApplicationRouterTypes.swift
+++ b/ios/Routing/Router/ApplicationRouterTypes.swift
@@ -11,10 +11,10 @@ import Foundation
/**
Struct describing a routing request for presentation or dismissal.
*/
-struct PendingRoute<RouteType: AppRouteProtocol> {
+struct PendingRoute<RouteType: AppRouteProtocol>: Sendable {
var operation: RouteOperation<RouteType>
var animated: Bool
- var metadata: Any?
+ nonisolated(unsafe) var metadata: Any?
}
extension PendingRoute: Equatable {
@@ -26,7 +26,7 @@ extension PendingRoute: Equatable {
/**
Enum type describing an attempt to fulfill the route presentation request.
**/
-enum PendingPresentationResult {
+enum PendingPresentationResult: Sendable {
/**
Successfully presented the route.
*/
@@ -52,7 +52,7 @@ enum PendingPresentationResult {
/**
Enum type describing an attempt to fulfill the route dismissal request.
*/
-enum PendingDismissalResult {
+enum PendingDismissalResult: Sendable {
/**
Successfully dismissed the route.
*/
@@ -76,7 +76,7 @@ enum PendingDismissalResult {
/**
Enum describing operation over the route.
*/
-enum RouteOperation<RouteType: AppRouteProtocol>: Equatable, CustomDebugStringConvertible {
+enum RouteOperation<RouteType: AppRouteProtocol>: Equatable, CustomDebugStringConvertible, Sendable {
/**
Present route.
*/
@@ -114,7 +114,7 @@ enum RouteOperation<RouteType: AppRouteProtocol>: Equatable, CustomDebugStringCo
/**
Enum type describing a single route or a group of routes requested to be dismissed.
*/
-enum DismissMatch<RouteType: AppRouteProtocol>: Equatable, CustomDebugStringConvertible {
+enum DismissMatch<RouteType: AppRouteProtocol>: Equatable, CustomDebugStringConvertible, Sendable {
case group(RouteType.RouteGroupType)
case singleRoute(RouteType)
@@ -143,7 +143,7 @@ enum DismissMatch<RouteType: AppRouteProtocol>: Equatable, CustomDebugStringConv
/**
Struct describing presented route.
*/
-public struct PresentedRoute<RouteType: AppRouteProtocol>: Equatable {
+public struct PresentedRoute<RouteType: AppRouteProtocol>: Equatable, Sendable {
public var route: RouteType
public var coordinator: Coordinator
}
@@ -171,7 +171,7 @@ public struct RouteDismissalContext<RouteType: AppRouteProtocol> {
/**
Struct holding information used by delegate to perform presentation of a specific route.
*/
-public struct RoutePresentationContext<RouteType: AppRouteProtocol> {
+public struct RoutePresentationContext<RouteType: AppRouteProtocol>: Sendable {
/**
Route that's being presented.
*/
@@ -185,13 +185,13 @@ public struct RoutePresentationContext<RouteType: AppRouteProtocol> {
/**
Metadata associated with the route.
*/
- public var metadata: Any?
+ nonisolated(unsafe) public var metadata: Any?
}
/**
Struct holding information used by delegate to perform sub-navigation of the route in subject.
*/
-public struct RouteSubnavigationContext<RouteType: AppRouteProtocol> {
+public struct RouteSubnavigationContext<RouteType: AppRouteProtocol>: Sendable {
public var presentedRoute: PresentedRoute<RouteType>
public var route: RouteType
public var isAnimated: Bool
diff --git a/ios/RoutingTests/RouterBlockDelegate.swift b/ios/RoutingTests/RouterBlockDelegate.swift
index 977454b7e4..07204d5fa9 100644
--- a/ios/RoutingTests/RouterBlockDelegate.swift
+++ b/ios/RoutingTests/RouterBlockDelegate.swift
@@ -9,26 +9,28 @@
import Foundation
import Routing
-class RouterBlockDelegate<RouteType: AppRouteProtocol>: ApplicationRouterDelegate {
+final class RouterBlockDelegate<RouteType: AppRouteProtocol>: ApplicationRouterDelegate, @unchecked Sendable {
var handleRoute: ((RoutePresentationContext<RouteType>, Bool, (Coordinator) -> Void) -> Void)?
var handleDismiss: ((RouteDismissalContext<RouteType>, () -> Void) -> Void)?
var shouldPresent: ((RouteType) -> Bool)?
var shouldDismiss: ((RouteDismissalContext<RouteType>) -> Bool)?
- var handleSubnavigation: ((RouteSubnavigationContext<RouteType>, () -> Void) -> Void)?
+ var handleSubnavigation: (@Sendable @MainActor (RouteSubnavigationContext<RouteType>, () -> Void) -> Void)?
- func applicationRouter(
+ nonisolated func applicationRouter(
_ router: ApplicationRouter<RouteType>,
presentWithContext context: RoutePresentationContext<RouteType>,
animated: Bool,
- completion: @escaping (Coordinator) -> Void
+ completion: @escaping @Sendable (Coordinator) -> Void
) {
- handleRoute?(context, animated, completion) ?? completion(Coordinator())
+ MainActor.assumeIsolated {
+ handleRoute?(context, animated, completion) ?? completion(Coordinator())
+ }
}
func applicationRouter(
_ router: ApplicationRouter<RouteType>,
dismissWithContext context: RouteDismissalContext<RouteType>,
- completion: @escaping () -> Void
+ completion: @escaping @Sendable () -> Void
) {
handleDismiss?(context, completion) ?? completion()
}
@@ -47,8 +49,10 @@ class RouterBlockDelegate<RouteType: AppRouteProtocol>: ApplicationRouterDelegat
func applicationRouter(
_ router: ApplicationRouter<RouteType>,
handleSubNavigationWithContext context: RouteSubnavigationContext<RouteType>,
- completion: @escaping () -> Void
+ completion: @escaping @Sendable @MainActor () -> Void
) {
- handleSubnavigation?(context, completion) ?? completion()
+ MainActor.assumeIsolated {
+ handleSubnavigation?(context, completion) ?? completion()
+ }
}
}
diff --git a/ios/RoutingTests/RoutingTests.swift b/ios/RoutingTests/RoutingTests.swift
index c271a98970..bd52c3cb07 100644
--- a/ios/RoutingTests/RoutingTests.swift
+++ b/ios/RoutingTests/RoutingTests.swift
@@ -9,6 +9,7 @@
@testable import Routing
import XCTest
+@MainActor
final class RoutingTests: XCTestCase {
private func createDelegate<T: AppRouteProtocol>(shouldPresent: Bool = true) -> RouterBlockDelegate<T> {
let delegate = RouterBlockDelegate<T>()
@@ -18,11 +19,9 @@ final class RoutingTests: XCTestCase {
return delegate
}
-}
-// MARK: Horizontal flow tests
+ // MARK: Horizontal flow tests
-extension RoutingTests {
func testPresentHorizontalRoute() throws {
enum TestRoute: AppRouteProtocol {
case one
@@ -71,11 +70,9 @@ extension RoutingTests {
XCTAssertEqual(router.presentedRoutes[.horizontal]?.count, 1)
}
-}
-// MARK: Modal flow tests
+ // MARK: Modal flow tests
-extension RoutingTests {
func testPresentModalRoutesOfDifferentLevels() throws {
enum TestRoute: AppRouteProtocol {
case one, two