mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
vector-im/element-x-ios/issues/53 - Adopt MainActors, dispatch heavy operations do detached tasks and ensure combine publisher call back on the right queue
This commit is contained in:
committed by
Stefan Ceriu
parent
dde7786152
commit
f5890b68a2
@@ -14,6 +14,7 @@ enum AuthenticationCoordinatorError: Error {
|
||||
case failedSettingUpSession
|
||||
}
|
||||
|
||||
@MainActor
|
||||
protocol AuthenticationCoordinatorDelegate: AnyObject {
|
||||
|
||||
func authenticationCoordinatorDidStartLoading(_ authenticationCoordinator: AuthenticationCoordinator)
|
||||
|
||||
@@ -27,12 +27,13 @@ private class WeakUserSessionWrapper: ClientDelegate {
|
||||
self.userSession = userSession
|
||||
}
|
||||
|
||||
func didReceiveSyncUpdate() {
|
||||
@MainActor func didReceiveSyncUpdate() {
|
||||
self.userSession?.didReceiveSyncUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
class UserSession: ClientDelegate {
|
||||
@MainActor
|
||||
class UserSession {
|
||||
|
||||
private let client: Client
|
||||
|
||||
@@ -74,26 +75,26 @@ class UserSession: ClientDelegate {
|
||||
}
|
||||
|
||||
func loadUserDisplayName() async -> Result<String, UserSessionError> {
|
||||
await withCheckedContinuation { continuation in
|
||||
await Task.detached { () -> Result<String, UserSessionError> in
|
||||
do {
|
||||
let displayName = try self.client.displayName()
|
||||
continuation.resume(returning: .success(displayName))
|
||||
return .success(displayName)
|
||||
} catch {
|
||||
continuation.resume(returning: .failure(.failedRetrievingDisplayName))
|
||||
return .failure(.failedRetrievingDisplayName)
|
||||
}
|
||||
|
||||
}
|
||||
}.value
|
||||
}
|
||||
|
||||
func loadUserAvatarURL() async -> Result<String, UserSessionError> {
|
||||
await withCheckedContinuation { continuation in
|
||||
await Task.detached { () -> Result<String, UserSessionError> in
|
||||
do {
|
||||
let avatarURL = try self.client.avatarUrl()
|
||||
continuation.resume(returning: .success(avatarURL))
|
||||
return .success(avatarURL)
|
||||
} catch {
|
||||
continuation.resume(returning: .failure(.failedRetrievingDisplayName))
|
||||
return .failure(.failedRetrievingDisplayName)
|
||||
}
|
||||
}
|
||||
}.value
|
||||
}
|
||||
|
||||
// MARK: ClientDelegate
|
||||
@@ -101,8 +102,8 @@ class UserSession: ClientDelegate {
|
||||
func didReceiveSyncUpdate() {
|
||||
Benchmark.logElapsedDurationForIdentifier("ClientSync", message: "Received sync update")
|
||||
|
||||
Task {
|
||||
await updateRooms()
|
||||
Task.detached {
|
||||
await self.updateRooms()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,33 +46,33 @@ struct MediaProvider: MediaProviderProtocol {
|
||||
return .success(image)
|
||||
}
|
||||
|
||||
return await withCheckedContinuation { continuation in
|
||||
let cachedImageLoadResult = await withCheckedContinuation({ continuation in
|
||||
imageCache.retrieveImage(forKey: source.underlyingSource.url()) { result in
|
||||
if case let .success(cacheResult) = result,
|
||||
let image = cacheResult.image {
|
||||
continuation.resume(returning: .success(image))
|
||||
return
|
||||
continuation.resume(returning: result)
|
||||
}
|
||||
})
|
||||
|
||||
if case let .success(cacheResult) = cachedImageLoadResult,
|
||||
let image = cacheResult.image {
|
||||
return .success(image)
|
||||
}
|
||||
|
||||
return await Task.detached { () -> Result<UIImage, MediaProviderError> in
|
||||
do {
|
||||
let imageData = try client.getMediaContent(source: source.underlyingSource)
|
||||
|
||||
guard let image = UIImage(data: Data(bytes: imageData, count: imageData.count)) else {
|
||||
MXLog.error("Invalid image data")
|
||||
return .failure(.invalidImageData)
|
||||
}
|
||||
|
||||
processingQueue.async {
|
||||
do {
|
||||
let imageData = try client.getMediaContent(source: source.underlyingSource)
|
||||
|
||||
guard let image = UIImage(data: Data(bytes: imageData, count: imageData.count)) else {
|
||||
MXLog.error("Invalid image data")
|
||||
continuation.resume(returning: .failure(.invalidImageData))
|
||||
return
|
||||
}
|
||||
|
||||
imageCache.store(image, forKey: source.underlyingSource.url())
|
||||
|
||||
continuation.resume(returning: .success(image))
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving image with error: \(error)")
|
||||
continuation.resume(returning: .failure(.failedRetrievingImage))
|
||||
}
|
||||
}
|
||||
imageCache.store(image, forKey: source.underlyingSource.url())
|
||||
|
||||
return .success(image)
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving image with error: \(error)")
|
||||
return .failure(.failedRetrievingImage)
|
||||
}
|
||||
}
|
||||
}.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ enum MediaProviderError: Error {
|
||||
case invalidImageData
|
||||
}
|
||||
|
||||
@MainActor
|
||||
protocol MediaProviderProtocol {
|
||||
func imageFromSource(_ source: MediaSource?) -> UIImage?
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
class MemberDetailProviderManager {
|
||||
|
||||
private var memberDetailProviders: [String: MemberDetailProviderProtocol] = [:]
|
||||
|
||||
@@ -14,6 +14,7 @@ enum MemberDetailProviderError: Error {
|
||||
case failedRetrievingUserDisplayName
|
||||
}
|
||||
|
||||
@MainActor
|
||||
protocol MemberDetailProviderProtocol {
|
||||
func avatarURLForUserId(_ userId: String) -> String?
|
||||
func loadAvatarURLForUserId(_ userId: String) async -> Result<String?, MemberDetailProviderError>
|
||||
|
||||
@@ -44,9 +44,7 @@ class RoomProxy: RoomProxyProtocol {
|
||||
|
||||
room.setDelegate(delegate: WeakRoomProxyWrapper(roomProxy: self))
|
||||
|
||||
Task {
|
||||
backwardStream = room.startLiveEventListener()
|
||||
}
|
||||
backwardStream = room.startLiveEventListener()
|
||||
}
|
||||
|
||||
var id: String {
|
||||
@@ -86,78 +84,76 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
|
||||
func loadAvatarURLForUserId(_ userId: String) async -> Result<String?, RoomProxyError> {
|
||||
await withCheckedContinuation({ continuation in
|
||||
await Task.detached { () -> Result<String?, RoomProxyError> in
|
||||
do {
|
||||
let avatarURL = try self.room.memberAvatarUrl(userId: userId)
|
||||
continuation.resume(returning: .success(avatarURL))
|
||||
return .success(avatarURL)
|
||||
} catch {
|
||||
continuation.resume(returning: .failure(.failedRetrievingMemberAvatarURL))
|
||||
return .failure(.failedRetrievingMemberAvatarURL)
|
||||
}
|
||||
})
|
||||
}.value
|
||||
}
|
||||
|
||||
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError> {
|
||||
await withCheckedContinuation({ continuation in
|
||||
await Task.detached { () -> Result<String?, RoomProxyError> in
|
||||
do {
|
||||
let displayName = try self.room.memberDisplayName(userId: userId)
|
||||
continuation.resume(returning: .success(displayName))
|
||||
return .success(displayName)
|
||||
} catch {
|
||||
continuation.resume(returning: .failure(.failedRetrievingMemberDisplayName))
|
||||
return .failure(.failedRetrievingMemberDisplayName)
|
||||
}
|
||||
})
|
||||
}.value
|
||||
}
|
||||
|
||||
func loadDisplayName() async -> Result<String, RoomProxyError> {
|
||||
await withCheckedContinuation({ continuation in
|
||||
if let displayName = displayName {
|
||||
continuation.resume(returning: .success(displayName))
|
||||
return
|
||||
await Task.detached { () -> Result<String, RoomProxyError> in
|
||||
if let displayName = self.displayName {
|
||||
return .success(displayName)
|
||||
}
|
||||
|
||||
do {
|
||||
let displayName = try self.room.displayName()
|
||||
self.displayName = displayName
|
||||
|
||||
continuation.resume(returning: .success(displayName))
|
||||
return .success(displayName)
|
||||
} catch {
|
||||
continuation.resume(returning: .failure(.failedRetrievingDisplayName))
|
||||
return .failure(.failedRetrievingDisplayName)
|
||||
}
|
||||
})
|
||||
}.value
|
||||
}
|
||||
|
||||
func paginateBackwards(count: UInt) async -> Result<Void, RoomProxyError> {
|
||||
await withCheckedContinuation { continuation in
|
||||
await Task.detached { () -> Result<Void, RoomProxyError> in
|
||||
guard let backwardStream = self.backwardStream else {
|
||||
continuation.resume(returning: .failure(.backwardStreamNotAvailable))
|
||||
return
|
||||
return .failure(RoomProxyError.backwardStreamNotAvailable)
|
||||
}
|
||||
|
||||
|
||||
Benchmark.startTrackingForIdentifier("BackPagination \(self.id)", message: "Backpaginating \(count) message(s) in room \(self.id)")
|
||||
let sdkMessages = backwardStream.paginateBackwards(count: UInt64(count))
|
||||
Benchmark.endTrackingForIdentifier("BackPagination \(self.id)", message: "Finished backpaginating \(count) message(s) in room \(self.id)")
|
||||
|
||||
|
||||
let messages = sdkMessages.map { message in
|
||||
self.messageFactory.buildRoomMessageFrom(message)
|
||||
}.reversed()
|
||||
|
||||
|
||||
self.messages.insert(contentsOf: messages, at: 0)
|
||||
|
||||
continuation.resume(returning: .success(()))
|
||||
}
|
||||
return .success(())
|
||||
}.value
|
||||
}
|
||||
|
||||
func sendMessage(_ message: String) async -> Result<Void, RoomProxyError> {
|
||||
let messageContent = messageEventContentFromMarkdown(md: message)
|
||||
let transactionId = genTransactionId()
|
||||
|
||||
return await withCheckedContinuation { continuation in
|
||||
return await Task(priority: .high) { () -> Result<Void, RoomProxyError> in
|
||||
do {
|
||||
try self.room.send(msg: messageContent, txnId: transactionId)
|
||||
continuation.resume(returning: .success(()))
|
||||
return .success(())
|
||||
} catch {
|
||||
continuation.resume(returning: .failure(.failedSendingMessage))
|
||||
return .failure(.failedSendingMessage)
|
||||
}
|
||||
}
|
||||
}.value
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
protocol EventBriefFactoryProtocol {
|
||||
func eventBriefForMessage(_ message: RoomMessageProtocol?) async -> EventBrief?
|
||||
}
|
||||
|
||||
@@ -98,13 +98,13 @@ class RoomSummary: RoomSummaryProtocol {
|
||||
}
|
||||
|
||||
await withTaskGroup(of: Void.self) { group in
|
||||
group.addTask {
|
||||
group.addTask(priority: .medium) {
|
||||
await self.loadDisplayName()
|
||||
}
|
||||
group.addTask {
|
||||
group.addTask(priority: .medium) {
|
||||
await self.loadAvatar()
|
||||
}
|
||||
group.addTask {
|
||||
group.addTask(priority: .medium) {
|
||||
await self.loadLastMessage()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ enum RoomSummaryCallback {
|
||||
case updatedData
|
||||
}
|
||||
|
||||
@MainActor
|
||||
protocol RoomSummaryProtocol {
|
||||
var id: String { get }
|
||||
var name: String? { get }
|
||||
|
||||
@@ -31,7 +31,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
self.mediaProvider = mediaProvider
|
||||
self.memberDetailProvider = memberDetailProvider
|
||||
|
||||
self.timelineProvider.callbacks.sink { [weak self] callback in
|
||||
self.timelineProvider
|
||||
.callbacks
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch callback {
|
||||
|
||||
@@ -18,6 +18,7 @@ enum RoomTimelineControllerError: Error {
|
||||
case generic
|
||||
}
|
||||
|
||||
@MainActor
|
||||
protocol RoomTimelineControllerProtocol {
|
||||
var timelineItems: [RoomTimelineItemProtocol] { get }
|
||||
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
||||
|
||||
@@ -18,6 +18,7 @@ enum RoomTimelineProviderError: Error {
|
||||
case generic
|
||||
}
|
||||
|
||||
@MainActor
|
||||
protocol RoomTimelineProviderProtocol {
|
||||
var callbacks: PassthroughSubject<RoomTimelineProviderCallback, Never> { get }
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
struct RoomTimelineItemFactory {
|
||||
private let mediaProvider: MediaProviderProtocol
|
||||
private let memberDetailProvider: MemberDetailProviderProtocol
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
struct RoomTimelineViewFactory {
|
||||
func buildTimelineViewFor(_ timelineItem: RoomTimelineItemProtocol) -> RoomTimelineViewProvider {
|
||||
switch timelineItem {
|
||||
|
||||
Reference in New Issue
Block a user