diff --git a/Examples/ElizaSharedSources/AppSources/MessagingView.swift b/Examples/ElizaSharedSources/AppSources/MessagingView.swift index 9290d408..da37414b 100644 --- a/Examples/ElizaSharedSources/AppSources/MessagingView.swift +++ b/Examples/ElizaSharedSources/AppSources/MessagingView.swift @@ -70,9 +70,9 @@ struct MessagingView: View { HStack { TextField("Write your message...", text: self.$currentMessage) - .onSubmit(self.sendMessage) + .onSubmit { self.sendMessage() } .submitLabel(.send) - Button("Send", action: self.sendMessage) + Button("Send", action: { self.sendMessage() }) .foregroundColor(.blue) } } @@ -82,7 +82,7 @@ struct MessagingView: View { .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("End Chat") { - self.viewModel.endChat() + Task { await self.viewModel.endChat() } self.presentationMode.wrappedValue.dismiss() } } diff --git a/Examples/ElizaSharedSources/AppSources/MessagingViewModel.swift b/Examples/ElizaSharedSources/AppSources/MessagingViewModel.swift index dcc12dda..e7343ac1 100644 --- a/Examples/ElizaSharedSources/AppSources/MessagingViewModel.swift +++ b/Examples/ElizaSharedSources/AppSources/MessagingViewModel.swift @@ -35,17 +35,18 @@ protocol MessagingViewModel: ObservableObject { func send(_ message: String) async /// End the chat session (and close connections if needed). - func endChat() + func endChat() async } /// View model that uses unary requests for messaging. +@MainActor final class UnaryMessagingViewModel: MessagingViewModel { private let protocolClient: ProtocolClient private lazy var elizaClient = Buf_Connect_Demo_Eliza_V1_ElizaServiceClient( client: self.protocolClient ) - @MainActor @Published private(set) var messages = [Message]() + @Published private(set) var messages = [Message]() init(protocolOption: ProtocolClientOption) { self.protocolClient = ProtocolClient( @@ -58,24 +59,24 @@ final class UnaryMessagingViewModel: MessagingViewModel { func send(_ sentence: String) async { let request = SayRequest.with { $0.sentence = sentence } - await self.addMessage(Message(message: sentence, author: .user)) + self.addMessage(Message(message: sentence, author: .user)) let response = await self.elizaClient.say(request: request) os_log(.debug, "Eliza unary response: %@", String(describing: response)) - await self.addMessage(Message( + self.addMessage(Message( message: response.message?.sentence ?? "No response", author: .eliza )) } - func endChat() {} + func endChat() async {} - @MainActor private func addMessage(_ message: Message) { self.messages.append(message) } } /// View model that uses bidirectional streaming for messaging. +@MainActor final class BidirectionalStreamingMessagingViewModel: MessagingViewModel { private let protocolClient: ProtocolClient private lazy var elizaClient = Buf_Connect_Demo_Eliza_V1_ElizaServiceClient( @@ -83,7 +84,7 @@ final class BidirectionalStreamingMessagingViewModel: MessagingViewModel { ) private lazy var elizaStream = self.elizaClient.converse() - @MainActor @Published private(set) var messages = [Message]() + @Published private(set) var messages = [Message]() init(protocolOption: ProtocolClientOption) { self.protocolClient = ProtocolClient( @@ -98,7 +99,7 @@ final class BidirectionalStreamingMessagingViewModel: MessagingViewModel { func send(_ sentence: String) async { do { let request = ConverseRequest.with { $0.sentence = sentence } - await self.addMessage(Message(message: sentence, author: .user)) + self.addMessage(Message(message: sentence, author: .user)) try self.elizaStream.send(request) } catch let error { os_log( @@ -120,7 +121,7 @@ final class BidirectionalStreamingMessagingViewModel: MessagingViewModel { case .message(let message): os_log(.debug, "Eliza message: %@", String(describing: message)) - await self.addMessage(Message(message: message.sentence, author: .eliza)) + self.addMessage(Message(message: message.sentence, author: .eliza)) case .complete(_, let error, let trailers): os_log(.debug, "Eliza completed with trailers: %@", trailers ?? [:]) @@ -131,13 +132,12 @@ final class BidirectionalStreamingMessagingViewModel: MessagingViewModel { } else { sentence = "[Conversation ended]" } - await self.addMessage(Message(message: sentence, author: .eliza)) + self.addMessage(Message(message: sentence, author: .eliza)) } } } } - @MainActor private func addMessage(_ message: Message) { self.messages.append(message) print(self.messages) diff --git a/Examples/ElizaSwiftPackageApp/ElizaSwiftPackageApp.xcodeproj/project.pbxproj b/Examples/ElizaSwiftPackageApp/ElizaSwiftPackageApp.xcodeproj/project.pbxproj index a3233ff9..71e77448 100644 --- a/Examples/ElizaSwiftPackageApp/ElizaSwiftPackageApp.xcodeproj/project.pbxproj +++ b/Examples/ElizaSwiftPackageApp/ElizaSwiftPackageApp.xcodeproj/project.pbxproj @@ -347,6 +347,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; @@ -380,6 +381,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; diff --git a/Libraries/Connect/Interfaces/Code.swift b/Libraries/Connect/Interfaces/Code.swift index f9439b10..e411639c 100644 --- a/Libraries/Connect/Interfaces/Code.swift +++ b/Libraries/Connect/Interfaces/Code.swift @@ -14,7 +14,7 @@ /// Indicates a status of an RPC. /// The zero code in gRPC is OK, which indicates that the operation was a success. -public enum Code: Int, CaseIterable, Equatable { +public enum Code: Int, CaseIterable, Equatable, Sendable { case ok = 0 case canceled = 1 case unknown = 2 diff --git a/Libraries/Connect/Interfaces/ConnectEndStreamResponse.swift b/Libraries/Connect/Interfaces/ConnectEndStreamResponse.swift index 931f1198..e7ae883d 100644 --- a/Libraries/Connect/Interfaces/ConnectEndStreamResponse.swift +++ b/Libraries/Connect/Interfaces/ConnectEndStreamResponse.swift @@ -14,7 +14,7 @@ /// Structure modeling the final JSON message that is returned by Connect streams: /// https://connect.build/docs/protocol#error-end-stream -struct ConnectEndStreamResponse { +struct ConnectEndStreamResponse: Sendable { /// Connect error that was returned with the response. let error: ConnectError? /// Additional metadata that was passed with the response. Keys are guaranteed to be lowercased. diff --git a/Libraries/Connect/Interfaces/ConnectError.swift b/Libraries/Connect/Interfaces/ConnectError.swift index e3d68409..51292f14 100644 --- a/Libraries/Connect/Interfaces/ConnectError.swift +++ b/Libraries/Connect/Interfaces/ConnectError.swift @@ -17,7 +17,7 @@ import SwiftProtobuf /// Typed error provided by Connect RPCs that may optionally wrap additional typed custom errors /// using `details`. -public struct ConnectError: Swift.Error { +public struct ConnectError: Swift.Error, Sendable { /// The resulting status code. public let code: Code /// User-readable error message. @@ -64,7 +64,7 @@ public struct ConnectError: Swift.Error { /// /// The `google.golang.org/genproto/googleapis/rpc/errdetails` package contains a /// variety of Protobuf messages commonly used as error details. - public struct Detail: Swift.Decodable { + public struct Detail: Swift.Decodable, Sendable { public let type: String public let payload: Data? diff --git a/Libraries/Connect/Interfaces/HTTPRequest.swift b/Libraries/Connect/Interfaces/HTTPRequest.swift index c022029f..7f43fc44 100644 --- a/Libraries/Connect/Interfaces/HTTPRequest.swift +++ b/Libraries/Connect/Interfaces/HTTPRequest.swift @@ -15,7 +15,7 @@ import Foundation /// HTTP request used for sending primitive data to the server. -public struct HTTPRequest { +public struct HTTPRequest: Sendable { /// Target URL for the request. public let url: URL /// Value to assign to the `content-type` header. diff --git a/Libraries/Connect/Interfaces/HTTPResponse.swift b/Libraries/Connect/Interfaces/HTTPResponse.swift index a73b0fa5..c480a72b 100644 --- a/Libraries/Connect/Interfaces/HTTPResponse.swift +++ b/Libraries/Connect/Interfaces/HTTPResponse.swift @@ -15,7 +15,7 @@ import Foundation /// Unary HTTP response received from the server. -public struct HTTPResponse { +public struct HTTPResponse: Sendable { /// The status code of the response. public let code: Code /// Response headers specified by the server. diff --git a/Libraries/Connect/Interfaces/MethodSpec.swift b/Libraries/Connect/Interfaces/MethodSpec.swift index ff8cec48..4d30715e 100644 --- a/Libraries/Connect/Interfaces/MethodSpec.swift +++ b/Libraries/Connect/Interfaces/MethodSpec.swift @@ -13,7 +13,7 @@ // limitations under the License. /// Contains metadata for a specific RPC method. -public struct MethodSpec: Equatable, Codable { +public struct MethodSpec: Equatable, Codable, Sendable { /// The name of the method (1:1 with the `.proto` file). E.g., `Foo`. public let name: String /// The fully qualified name of the method's service. E.g., `foo.v1.FooService`. @@ -27,7 +27,7 @@ public struct MethodSpec: Equatable, Codable { return "\(self.service)/\(self.name)" } - public enum MethodType: Equatable, Codable { + public enum MethodType: Equatable, Codable, Sendable { case unary case clientStream case serverStream diff --git a/Libraries/Connect/Interfaces/ResponseMessage.swift b/Libraries/Connect/Interfaces/ResponseMessage.swift index df87c6d7..a7b209b2 100644 --- a/Libraries/Connect/Interfaces/ResponseMessage.swift +++ b/Libraries/Connect/Interfaces/ResponseMessage.swift @@ -15,7 +15,7 @@ import SwiftProtobuf /// Typed unary response from an RPC. -public struct ResponseMessage { +public struct ResponseMessage: Sendable { /// The status code of the response. public let code: Code /// Response headers specified by the server. diff --git a/Libraries/Connect/Interfaces/Streams/StreamResult.swift b/Libraries/Connect/Interfaces/Streams/StreamResult.swift index 8d5dfc90..a4120be6 100644 --- a/Libraries/Connect/Interfaces/Streams/StreamResult.swift +++ b/Libraries/Connect/Interfaces/Streams/StreamResult.swift @@ -16,7 +16,7 @@ /// /// A typical stream receives `headers > message > message > message ... > complete`. @frozen -public enum StreamResult { +public enum StreamResult: Sendable { /// Stream is complete. Provides the end status code and optionally an error and trailers. case complete(code: Code, error: Swift.Error?, trailers: Trailers?) /// Headers have been received over the stream.