Skip to content

Commit

Permalink
Replace detached tasks with Task.dispatch(on:) (#201)
Browse files Browse the repository at this point in the history
* Use Task.detach(on:) everywhere that was blocking.
* Don't wrap the continuations in a Task 🤦‍♂️
* Add sendable requirement to closure.
  • Loading branch information
pixlwave authored Sep 21, 2022
1 parent 60f0f16 commit 8462862
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 129 deletions.
12 changes: 6 additions & 6 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 52;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -137,6 +137,7 @@
54C774874BED4A8FAD1F22FE /* AnalyticsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77B3D4950F1707E66E4A45A /* AnalyticsConfiguration.swift */; };
563A05B43207D00A6B698211 /* OIDCService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9010EE0CC913D095887EF36E /* OIDCService.swift */; };
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */; };
5706B6600CCCFE254F7D2495 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA2CA6D7090404B1074E887F /* Task.swift */; };
59C41313AED7566C3AC51163 /* RoomSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */; };
5B2C4C17888FC095ED6880B2 /* SplashViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 48971F1FFD7FC5C466889FC7 /* SplashViewController.xib */; };
5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */; };
Expand Down Expand Up @@ -321,7 +322,6 @@
E01373F2043E76393A0CE073 /* AnalyticsPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11B74ACE8D71747E1044A9C /* AnalyticsPromptViewModel.swift */; };
E0A4DCA633D174EB43AD599F /* BackgroundTaskProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA028DCD4157F9A1F999827 /* BackgroundTaskProtocol.swift */; };
E1DF24D085572A55C9758A2D /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; };
E290C78E7F09F47FD2662986 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = A40C19719687984FD9478FBE /* Task.swift */; };
E3CA565A4B9704F191B191F0 /* JoinedRoomSize+MemberCount.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */; };
E47CD939D8480657D4B706C6 /* AnalyticsPromptCheckmarkItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA7B2E9CC5DC3B76ADC35A43 /* AnalyticsPromptCheckmarkItem.swift */; };
E481C8FDCB6C089963C95344 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 527578916BD388A09F5A8036 /* DTCoreText */; };
Expand Down Expand Up @@ -628,7 +628,7 @@
8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineProvider.swift; sourceTree = "<group>"; };
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = "<group>"; };
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = "<group>"; };
8F7D42E66E939B709C1EC390 /* MockRoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomSummaryProvider.swift; sourceTree = "<group>"; };
9010EE0CC913D095887EF36E /* OIDCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCService.swift; sourceTree = "<group>"; };
90733775209F4D4D366A268F /* RootRouterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -667,7 +667,6 @@
A1ED7E89865201EE7D53E6DA /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = "<group>"; };
A2B6433F516F1E6DFA0E2D89 /* vls */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vls; path = vls.lproj/Localizable.strings; sourceTree = "<group>"; };
A30A1758E2B73EF38E7C42F8 /* ServerSelectionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionModels.swift; sourceTree = "<group>"; };
A40C19719687984FD9478FBE /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = "<group>"; };
A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = "<group>"; };
A443FAE2EE820A5790C35C8D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = "<group>"; };
A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserSession.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -821,6 +820,7 @@
F77C060C2ACC4CB7336A29E7 /* EmoteRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItem.swift; sourceTree = "<group>"; };
F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = "<group>"; };
FA154570F693D93513E584C1 /* RoomMessageFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageFactory.swift; sourceTree = "<group>"; };
FA2CA6D7090404B1074E887F /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = "<group>"; };
FAB10E673916D2B8D21FD197 /* TemplateModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateModels.swift; sourceTree = "<group>"; };
FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -1092,9 +1092,9 @@
children = (
B6E89E530A8E92EC44301CA1 /* Bundle.swift */,
A9FAFE1C2149E6AC8156ED2B /* Collection.swift */,
FA2CA6D7090404B1074E887F /* Task.swift */,
E26747B3154A5DBC3A7E24A5 /* Image.swift */,
40B21E611DADDEF00307E7AC /* String.swift */,
A40C19719687984FD9478FBE /* Task.swift */,
287FC98AF2664EAD79C0D902 /* UIDevice.swift */,
227AC5D71A4CE43512062243 /* URL.swift */,
);
Expand Down Expand Up @@ -2347,6 +2347,7 @@
DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */,
C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */,
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */,
5706B6600CCCFE254F7D2495 /* Task.swift in Sources */,
FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */,
06E93B2E3B32740B40F47CC5 /* ElementNavigationController.swift in Sources */,
9738F894DB1BD383BE05767A /* ElementSettings.swift in Sources */,
Expand Down Expand Up @@ -2490,7 +2491,6 @@
2F94054F50E312AF30BE07F3 /* String.swift in Sources */,
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */,
066A1E9B94723EE9F3038044 /* Strings.swift in Sources */,
E290C78E7F09F47FD2662986 /* Task.swift in Sources */,
43FD77998F33C32718C51450 /* TemplateCoordinator.swift in Sources */,
63C9AF0FB8278AF1C0388A0C /* TemplateModels.swift in Sources */,
1555A7643D85187D4851040C /* TemplateScreen.swift in Sources */,
Expand Down
59 changes: 36 additions & 23 deletions ElementX/Sources/Other/Extensions/Task.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,46 @@

import Foundation

extension Task where Failure == Never {
static func dispatched(on queue: DispatchQueue,
priority: TaskPriority? = nil,
operation: @escaping @Sendable () -> Success) -> Task<Success, Failure> {
Task.detached(priority: priority) {
await withCheckedContinuation { continuation in
queue.async {
continuation.resume(returning: operation())
}
public extension Task where Success == Never, Failure == Never {
/// Dispatches the given closure onto the given queue, wrapped within
/// a continuation to make it non-blocking and awaitable.
///
/// Use this method to `await` blocking calls to the SDK from a `Task`.
///
/// - Parameters:
/// - queue: The queue to run the closure on.
/// - function: A string identifying the declaration that is the notional
/// source for the continuation, used to identify the continuation in
/// runtime diagnostics related to misuse of this continuation.
/// - body: A sendable closure. Use of sendable won't work as it isn't
/// async, but is added to enforce actor semantics.
static func dispatch<T>(on queue: DispatchQueue, function: String = #function, _ body: @escaping @Sendable () -> T) async -> T {
await withCheckedContinuation(function: function) { continuation in
queue.async {
continuation.resume(returning: body())
}
}
}
}

extension Task where Failure == Error {
static func dispatched(on queue: DispatchQueue,
priority: TaskPriority? = nil,
operation: @escaping @Sendable () throws -> Success) -> Task<Success, Failure> {
Task.detached(priority: priority) {
try await withCheckedThrowingContinuation { continuation in
queue.async {
do {
let result = try operation()
continuation.resume(returning: result)
} catch {
continuation.resume(throwing: error)
}
/// Dispatches the given throwing closure onto the given queue, wrapped within
/// a continuation to make it non-blocking and awaitable.
///
/// Use this method to `await` blocking calls to the SDK from a `Task`.
///
/// - Parameters:
/// - queue: The queue to run the closure on.
/// - function: A string identifying the declaration that is the notional
/// source for the continuation, used to identify the continuation in
/// runtime diagnostics related to misuse of this continuation.
/// - body: A sendable closure. Use of sendable won't work as it isn't
/// async, but is added to enforce actor semantics.
static func dispatch<T>(on queue: DispatchQueue, function: String = #function, _ body: @escaping @Sendable () throws -> T) async throws -> T {
try await withCheckedThrowingContinuation(function: function) { continuation in
queue.async {
do {
continuation.resume(returning: try body())
} catch {
continuation.resume(throwing: error)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
private let linkColor = UIColor.blue

func fromPlain(_ string: String?) async -> AttributedString? {
await Task.detached {
await Task.dispatch(on: .global()) {
fromPlain(string)
}.value
}
}

func fromPlain(_ string: String?) -> AttributedString? {
Expand All @@ -41,9 +41,9 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
}

func fromHTML(_ htmlString: String?) async -> AttributedString? {
await Task.detached {
await Task.dispatch(on: .global()) {
fromHTML(htmlString)
}.value
}
}

// Do not use the default HTML renderer of NSAttributedString because this method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,29 +40,26 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
// MARK: - Public

func configure(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError> {
let task = Task.detached { () -> LoginHomeserver in
do {
var homeserver = LoginHomeserver(address: homeserverAddress, loginMode: .unknown)

try self.authenticationService.configureHomeserver(serverName: homeserverAddress)

guard let details = self.authenticationService.homeserverDetails() else { return homeserver }
try await Task.dispatch(on: .global()) {
try self.authenticationService.configureHomeserver(serverName: homeserverAddress)
}

if let issuer = details.authenticationIssuer(), let issuerURL = URL(string: issuer) {
homeserver.loginMode = .oidc(issuerURL)
} else if details.supportsPasswordLogin() {
homeserver.loginMode = .password
} else {
homeserver.loginMode = .unsupported
if let details = authenticationService.homeserverDetails() {
if let issuer = details.authenticationIssuer(), let issuerURL = URL(string: issuer) {
homeserver.loginMode = .oidc(issuerURL)
} else if details.supportsPasswordLogin() {
homeserver.loginMode = .password
} else {
homeserver.loginMode = .unsupported
}
}

return homeserver
}

switch await task.result {
case .success(let homeserver):
self.homeserver = homeserver
return .success(())
case .failure(let error):
} catch {
MXLog.error("Failed configuring a server: \(error)")
return .failure(.invalidHomeserverAddress)
}
Expand Down Expand Up @@ -105,20 +102,19 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
}

func login(username: String, password: String, initialDeviceName: String?, deviceId: String?) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
Benchmark.startTrackingForIdentifier("Login", message: "Started new login")

let loginTask: Task<Client, Error> = Task.detached {
try self.authenticationService.login(username: username,
password: password,
initialDeviceName: initialDeviceName,
deviceId: deviceId)
}
do {
Benchmark.startTrackingForIdentifier("Login", message: "Started new login")

switch await loginTask.result {
case .success(let client):
let client = try await Task.dispatch(on: .global()) {
try self.authenticationService.login(username: username,
password: password,
initialDeviceName: initialDeviceName,
deviceId: deviceId)
}

Benchmark.endTrackingForIdentifier("Login", message: "Finished login")
return await userSession(for: client)
case .failure(let error):
} catch {
Benchmark.endTrackingForIdentifier("Login", message: "Login failed")

MXLog.error("Failed logging in with error: \(error)")
Expand Down
17 changes: 7 additions & 10 deletions ElementX/Sources/Services/Client/ClientProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,27 +182,25 @@ class ClientProxy: ClientProxyProtocol {
}

func loadUserDisplayName() async -> Result<String, ClientProxyError> {
await Task.detached {
await Task.dispatch(on: .global()) {
do {
let displayName = try self.client.displayName()
return .success(displayName)
} catch {
return .failure(.failedRetrievingDisplayName)
}
}
.value
}

func loadUserAvatarURLString() async -> Result<String, ClientProxyError> {
await Task.detached {
await Task.dispatch(on: .global()) {
do {
let avatarURL = try self.client.avatarUrl()
return .success(avatarURL)
} catch {
return .failure(.failedRetrievingAvatarURL)
}
}
.value
}

func accountDataEvent<Content>(type: String) async -> Result<Content?, ClientProxyError> where Content: Decodable {
Expand All @@ -218,29 +216,28 @@ class ClientProxy: ClientProxyProtocol {
}

func loadMediaContentForSource(_ source: MatrixRustSDK.MediaSource) async throws -> Data {
try await Task.detached {
try await Task.dispatch(on: .global()) {
let bytes = try self.client.getMediaContent(source: source)
return Data(bytes: bytes, count: bytes.count)
}.value
}
}

func loadMediaThumbnailForSource(_ source: MatrixRustSDK.MediaSource, width: UInt, height: UInt) async throws -> Data {
try await Task.detached {
try await Task.dispatch(on: .global()) {
let bytes = try self.client.getMediaThumbnail(source: source, width: UInt64(width), height: UInt64(height))
return Data(bytes: bytes, count: bytes.count)
}.value
}
}

func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError> {
await Task.detached {
await Task.dispatch(on: .global()) {
do {
let sessionVerificationController = try self.client.getSessionVerificationController()
return .success(SessionVerificationControllerProxy(sessionVerificationController: sessionVerificationController))
} catch {
return .failure(.failedRetrievingSessionVerificationController)
}
}
.value
}

func logout() async {
Expand Down
Loading

0 comments on commit 8462862

Please sign in to comment.