-
Notifications
You must be signed in to change notification settings - Fork 110
Swift 6: complete concurrency checking #825
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
ddf78cc
b73c9c5
2f64d75
87292cf
ab7356d
391b156
26dd6aa
ef3bbc0
bb0ed90
87fcb5a
994196f
4c94da4
6b78288
8eae0dd
6e1e8c9
f10725c
c0b9111
79ef1c1
e28503d
f8d9504
fa2872a
4b305c5
1ab09e2
1170a52
7a0cfe3
7d0237c
9769ff4
365f9f6
9087e48
55d666c
db9e461
ad036e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
// swift-tools-version:5.9 | ||
// swift-tools-version:6.0 | ||
|
||
import Foundation | ||
import PackageDescription | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,11 +33,6 @@ do | |
replaceDeclaration 'signpost(log' 'signpost(nukeLog' $f | ||
replaceDeclaration ' Cache(' ' NukeCache(' $f | ||
replaceDeclaration ' Cache<' ' NukeCache<' $f | ||
replaceDeclaration ' Image?' ' NukeImage?' $f | ||
replaceDeclaration ' Image(' ' NukeImage(' $f | ||
replaceDeclaration 'struct Image:' 'struct NukeImage:' $f | ||
replaceDeclaration 'extension Image {' 'extension NukeImage {' $f | ||
replaceDeclaration 'Content == Image' 'Content == NukeImage' $f | ||
Comment on lines
-36
to
-40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nuke was upgraded to 12.8 (supports Swift 6), some of the types are not there anymore |
||
replaceDeclaration ' VideoPlayerView' ' NukeVideoPlayerView' $f | ||
replaceDeclaration 'typealias Color' 'typealias NukeColor' $f | ||
replaceDeclaration 'extension Color' 'extension NukeColor' $f | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,20 +21,21 @@ public class Appearance { | |
} | ||
|
||
/// Provider for custom localization which is dependent on App Bundle. | ||
public static var localizationProvider: (_ key: String, _ table: String) -> String = { key, table in | ||
nonisolated(unsafe) | ||
public static var localizationProvider: @Sendable(_ key: String, _ table: String) -> String = { key, table in | ||
Bundle.streamChatUI.localizedString(forKey: key, value: nil, table: table) | ||
} | ||
Comment on lines
+24
to
27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alternative could be making it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is fine |
||
} | ||
|
||
// MARK: - Appearance + Default | ||
|
||
public extension Appearance { | ||
static var `default`: Appearance = .init() | ||
nonisolated(unsafe) static var `default`: Appearance = .init() | ||
Comment on lines
-32
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also fine to keep it like this, it anyway contains only UI stuff - shouldn't be called from a bg thread |
||
} | ||
|
||
/// Provides the default value of the `Appearance` class. | ||
public struct AppearanceKey: EnvironmentKey { | ||
public static let defaultValue: Appearance = Appearance() | ||
public static var defaultValue: Appearance { .init() } | ||
} | ||
|
||
extension EnvironmentValues { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ import StreamChat | |
import SwiftUI | ||
|
||
/// View model for the `AddUsersView`. | ||
class AddUsersViewModel: ObservableObject { | ||
@MainActor class AddUsersViewModel: ObservableObject { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since view model factory is MainActor and view models are used from view, then it made more than sense to make all the view models main actor. |
||
|
||
@Injected(\.chatClient) private var chatClient | ||
|
||
|
@@ -46,9 +46,11 @@ class AddUsersViewModel: ObservableObject { | |
if !loadingNextUsers { | ||
loadingNextUsers = true | ||
searchController.loadNextUsers { [weak self] _ in | ||
guard let self = self else { return } | ||
self.users = self.searchController.userArray | ||
self.loadingNextUsers = false | ||
MainActor.ensureIsolated { [weak self] in | ||
|
||
guard let self = self else { return } | ||
self.users = self.searchController.userArray | ||
self.loadingNextUsers = false | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -57,16 +59,20 @@ class AddUsersViewModel: ObservableObject { | |
let filter: Filter<UserListFilterScope> = .notIn(.id, values: loadedUserIds) | ||
let query = UserListQuery(filter: filter) | ||
searchController.search(query: query) { [weak self] error in | ||
guard let self = self, error == nil else { return } | ||
self.users = self.searchController.userArray | ||
MainActor.ensureIsolated { [weak self] in | ||
guard let self = self, error == nil else { return } | ||
self.users = self.searchController.userArray | ||
} | ||
} | ||
} | ||
|
||
private func searchUsers(term: String) { | ||
searchController.search(term: searchText) { [weak self] error in | ||
guard let self = self, error == nil else { return } | ||
self.users = self.searchController.userArray.filter { user in | ||
!self.loadedUserIds.contains(user.id) | ||
MainActor.ensureIsolated { [weak self] in | ||
guard let self = self, error == nil else { return } | ||
self.users = self.searchController.userArray.filter { user in | ||
!self.loadedUserIds.contains(user.id) | ||
} | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ import StreamChat | |
import SwiftUI | ||
|
||
// View model for the `ChatChannelInfoView`. | ||
public class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDelegate { | ||
@MainActor public class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDelegate { | ||
|
||
@Injected(\.chatClient) private var chatClient | ||
|
||
|
@@ -172,7 +172,7 @@ public class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDe | |
loadAdditionalUsers() | ||
} | ||
|
||
public func leaveConversationTapped(completion: @escaping () -> Void) { | ||
public func leaveConversationTapped(completion: @escaping @MainActor() -> Void) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For avoiding actor isolation in the view. Better force main on the view model level. |
||
if !channel.isDirectMessageChannel { | ||
removeUserFromConversation(completion: completion) | ||
} else { | ||
|
@@ -195,20 +195,22 @@ public class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDe | |
) | ||
} | ||
|
||
public func channelController( | ||
nonisolated public func channelController( | ||
_ channelController: ChatChannelController, | ||
didUpdateChannel channel: EntityChange<ChatChannel> | ||
) { | ||
if let channel = channelController.channel { | ||
self.channel = channel | ||
if self.channel.lastActiveMembers.count > participants.count { | ||
participants = channel.lastActiveMembers.map { member in | ||
ParticipantInfo( | ||
chatUser: member, | ||
displayName: member.name ?? member.id, | ||
onlineInfoText: onlineInfo(for: member), | ||
isDeactivated: member.isDeactivated | ||
) | ||
MainActor.ensureIsolated { | ||
if let channel = channelController.channel { | ||
self.channel = channel | ||
if self.channel.lastActiveMembers.count > participants.count { | ||
participants = channel.lastActiveMembers.map { member in | ||
ParticipantInfo( | ||
chatUser: member, | ||
displayName: member.name ?? member.id, | ||
onlineInfoText: onlineInfo(for: member), | ||
isDeactivated: member.isDeactivated | ||
) | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -221,23 +223,27 @@ public class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDe | |
|
||
// MARK: - private | ||
|
||
private func removeUserFromConversation(completion: @escaping () -> Void) { | ||
private func removeUserFromConversation(completion: @escaping @MainActor() -> Void) { | ||
guard let userId = chatClient.currentUserId else { return } | ||
channelController.removeMembers(userIds: [userId]) { [weak self] error in | ||
if error != nil { | ||
self?.errorShown = true | ||
} else { | ||
completion() | ||
MainActor.ensureIsolated { [weak self] in | ||
if error != nil { | ||
self?.errorShown = true | ||
} else { | ||
completion() | ||
} | ||
} | ||
} | ||
} | ||
|
||
private func deleteChannel(completion: @escaping () -> Void) { | ||
private func deleteChannel(completion: @escaping @MainActor() -> Void) { | ||
channelController.deleteChannel { [weak self] error in | ||
if error != nil { | ||
self?.errorShown = true | ||
} else { | ||
completion() | ||
MainActor.ensureIsolated { [weak self] in | ||
if error != nil { | ||
self?.errorShown = true | ||
} else { | ||
completion() | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -249,19 +255,21 @@ public class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDe | |
|
||
loadingUsers = true | ||
memberListController.loadNextMembers { [weak self] error in | ||
guard let self = self else { return } | ||
self.loadingUsers = false | ||
if error == nil { | ||
let newMembers = self.memberListController.members.map { member in | ||
ParticipantInfo( | ||
chatUser: member, | ||
displayName: member.name ?? member.id, | ||
onlineInfoText: self.onlineInfo(for: member), | ||
isDeactivated: member.isDeactivated | ||
) | ||
} | ||
if newMembers.count > self.participants.count { | ||
self.participants = newMembers | ||
MainActor.ensureIsolated { [weak self] in | ||
|
||
guard let self = self else { return } | ||
self.loadingUsers = false | ||
if error == nil { | ||
let newMembers = self.memberListController.members.map { member in | ||
ParticipantInfo( | ||
chatUser: member, | ||
displayName: member.name ?? member.id, | ||
onlineInfoText: self.onlineInfo(for: member), | ||
isDeactivated: member.isDeactivated | ||
) | ||
} | ||
if newMembers.count > self.participants.count { | ||
self.participants = newMembers | ||
} | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Xcode 15 builds are going away, disabling it for now and a cleanup happens separately