diff --git a/CHANGELOG.md b/CHANGELOG.md index 141db64d51..b7bafc679e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,14 @@ allows submitting to the app store with Xcode 12. * Using the dynamic subscript API on unmanaged objects before first opening a Realm or if `objectTypes` was set when opening a Realm would throw an exception ([#7786](https://github.com/realm/realm-swift/issues/7786)). +* None. +* Add ability to use Swift Query syntax in `@ObservedResults`, which allows you + to filter results using the `where` parameter. +* Add ability to use `MutableSet` with `StateRealmObject` in SwiftUI. + +### Fixed +* Adding a Realm Object to a `ObservedResults` or a collections using `StateRealmObject` that is managed by the same Realm + would throw if the Object was frozen and not thawed before hand. @@ -71,6 +79,9 @@ allows submitting to the app store with Xcode 12. * Carthage release for Swift is built with Xcode 13.4. * CocoaPods: 1.10 or later. * Xcode: 13.1-13.4. +* Carthage release for Swift is built with Xcode 13.3. +* CocoaPods: 1.10 or later. +* Xcode: 12.4-13.3. ### Internal * Upgraded realm-core from ? to ? diff --git a/Package.swift b/Package.swift index 1be9460f06..21b5caedca 100644 --- a/Package.swift +++ b/Package.swift @@ -110,7 +110,7 @@ let package = Package( targets: ["Realm", "RealmSwift"]), ], dependencies: [ - .package(name: "RealmDatabase", url: "https://github.com/realm/realm-core", .exact(Version(coreVersionStr)!)) + .package(name: "RealmDatabase", path: "../realm-core-networking") ], targets: [ .target( diff --git a/Realm/RLMApp.mm b/Realm/RLMApp.mm index 30a3d65be1..d6aad9663c 100644 --- a/Realm/RLMApp.mm +++ b/Realm/RLMApp.mm @@ -56,6 +56,7 @@ void send_request_to_server(app::Request&& request, rlmRequest.method = static_cast(request.method); rlmRequest.timeout = request.timeout_ms / 1000.0; + __block auto blockCompletionBlock = std::move(completion); // Send the request through to the Cocoa level transport auto completion_ptr = completion.release(); [m_transport sendRequestToServer:rlmRequest completion:^(RLMResponse *response) { @@ -67,7 +68,7 @@ void send_request_to_server(app::Request&& request, // Convert the RLMResponse to an app:Response and pass downstream to // the object store - completion(app::Response{ + blockCompletionBlock(app::Response{ .http_status_code = static_cast(response.httpStatusCode), .custom_status_code = static_cast(response.customStatusCode), .headers = bridgingHeaders, diff --git a/Realm/RLMSyncConfiguration.mm b/Realm/RLMSyncConfiguration.mm index 81592238cd..91ade5140d 100644 --- a/Realm/RLMSyncConfiguration.mm +++ b/Realm/RLMSyncConfiguration.mm @@ -35,9 +35,159 @@ #import #import #import +#import using namespace realm; +API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) +@interface RLMCocoaSocketDelegate : NSObject +@property realm::util::websocket::EZObserver *observer; +@property util::Logger *logger; +@end + +@implementation RLMCocoaSocketDelegate + +- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *)protocol { + REALM_ASSERT([protocol length] > 0); + _logger->info(">>> CocoaSocket didOpenWithProtocol"); + _observer->websocket_handshake_completion_handler([protocol cStringUsingEncoding:NSUTF8StringEncoding]); +} + +- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didCloseWithCode:(NSURLSessionWebSocketCloseCode)closeCode reason:(NSData *)reason { + _logger->info(">>> CocoaSocket didCloseWithCode '%1'", closeCode); + _observer->websocket_close_message_received(std::error_code(static_cast(closeCode), std::generic_category()), + RLMStringDataWithNSString([[NSString alloc] initWithData: reason encoding:NSUTF8StringEncoding])); +} + +@end + +namespace realm::util::websocket { +class API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) CocoaSocket: public EZSocket { + +public: + CocoaSocket(EZConfig config, EZObserver* observer, EZEndpoint&& endpoint): + m_config(config) + , delegate([RLMCocoaSocketDelegate new]) + { + delegate.logger = &m_config.logger; + setup(observer, std::move(endpoint)); + } + + RLMCocoaSocketDelegate *delegate; + NSURLSessionWebSocketTask *task; + NSURLSession *session; + + ~CocoaSocket() + { + destroySocket(); + } + + util::Logger& logger() const + { + return m_config.logger; + } + + util::UniqueFunction m_write_completion_handler; + + void async_write_binary(const char *data, size_t size, util::UniqueFunction &&handler) override + { + logger().info(">>> CocoaSocket async_write_binary"); + m_write_completion_handler = std::move(handler); + + auto message = [[NSURLSessionWebSocketMessage alloc] initWithString:@(data)]; + [task sendMessage:message + completionHandler:^(NSError * _Nullable error) { + if (error) { + logger().error("Failed to send message: %1", error.localizedDescription.UTF8String); // Throws + delegate.observer->websocket_read_or_write_error_handler(std::error_code(static_cast(error.code), std::generic_category())); // Throws + destroySocket(error); + return; + } + m_write_completion_handler(); + m_write_completion_handler = nullptr; + }]; + } + +private: + + void destroySocket(NSError *error = nil) { + if (error) { + [session invalidateAndCancel]; + } else { + [session finishTasksAndInvalidate]; + } + } + + NSURL *buildUrl(EZEndpoint endpoint) { + NSMutableArray *strs = [NSMutableArray new]; + endpoint.is_ssl ? [strs addObject: @"wss://"] : [strs addObject: @"ws://"]; + endpoint.proxy ? [strs addObject: @(endpoint.proxy->address.data())] : [strs addObject: @(endpoint.address.data())]; + endpoint.proxy ? [strs addObject: [NSString stringWithFormat:@":%u", endpoint.proxy->port]] : [strs addObject: [NSString stringWithFormat:@":%u", endpoint.port]]; + [strs addObject:@(endpoint.path.data())]; + logger().info("Connecting to '%1'", [[strs componentsJoinedByString:@""] cStringUsingEncoding:NSUTF8StringEncoding]); + return [[NSURL alloc] initWithString:[strs componentsJoinedByString:@""]]; + } + + void setup(EZObserver* observer, EZEndpoint&& endpoint) + { + logger().info(">>> CocoaSocket setup"); + delegate.observer = observer; + + NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; + + auto sessionHeaders = [NSMutableDictionary new]; + for (const auto &x: endpoint.headers) { + sessionHeaders[@(x.first.c_str())] = @(x.second.c_str()); + } + configuration.HTTPAdditionalHeaders = sessionHeaders; + + auto protocols = [[NSString stringWithUTF8String:endpoint.protocols.c_str()] componentsSeparatedByString:@","]; + + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:nil]; + + task = [session webSocketTaskWithURL:buildUrl(endpoint) protocols:protocols]; + [task receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage * _Nullable message, NSError * _Nullable error) { + if (error) { + logger().error("Failed to connect to endpoint '%1:%2'", endpoint.address, endpoint.proxy->port); // Throws + observer->websocket_read_or_write_error_handler(std::error_code(static_cast(error.code), std::generic_category())); // Throws + destroySocket(error); + return; + } + switch (message.type) { + case NSURLSessionWebSocketMessageTypeData: + observer->websocket_binary_message_received([[[NSString alloc] initWithData:message.data encoding:NSUTF8StringEncoding] UTF8String], + [message.data length]); + break; + case NSURLSessionWebSocketMessageTypeString: + observer->websocket_binary_message_received([message.string UTF8String], + [message.data length]); + break; + } + }]; + [task resume]; + } + + EZConfig m_config; +}; + +class API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) CocoaSocketFactory: public EZSocketFactory { +public: + CocoaSocketFactory(EZConfig config) + : EZSocketFactory(config) + , m_config(config) + { + } + + std::unique_ptr connect(EZObserver* observer, EZEndpoint&& endpoint) override + { + return std::unique_ptr(new CocoaSocket(std::move(m_config), observer, std::move(endpoint))); + } + +private: + EZConfig m_config; +}; +} + namespace { using ProtocolError = realm::sync::ProtocolError; @@ -381,6 +531,7 @@ - (instancetype)initWithUser:(RLMUser *)user } } + _config->socket_factory = defaultSocketFactory(); self.customFileURL = customFileURL; return self; } @@ -388,3 +539,15 @@ - (instancetype)initWithUser:(RLMUser *)user } @end + +using namespace realm::util::websocket; + +std::function(EZConfig&&)> defaultSocketFactory() { + return [](EZConfig&& config) mutable { + if (@available(macOS 10.15, *)) { + return std::unique_ptr(new CocoaSocketFactory(std::move(config))); + } else { + return std::unique_ptr(new EZSocketFactory(std::move(config))); + } + }; +} diff --git a/Realm/RLMSyncConfiguration_Private.hpp b/Realm/RLMSyncConfiguration_Private.hpp index d76284c799..a777e429be 100644 --- a/Realm/RLMSyncConfiguration_Private.hpp +++ b/Realm/RLMSyncConfiguration_Private.hpp @@ -20,6 +20,7 @@ #import #import +#import namespace realm { class SyncSession; @@ -28,6 +29,9 @@ struct SyncError; using SyncSessionErrorHandler = void(std::shared_ptr, SyncError); } +using namespace realm::util::websocket; +extern std::function(realm::util::websocket::EZConfig&&)> defaultSocketFactory(); + NS_ASSUME_NONNULL_BEGIN @interface RLMSyncConfiguration () diff --git a/Realm/RLMSyncManager.mm b/Realm/RLMSyncManager.mm index 3be2ca9925..dd260b7dc3 100644 --- a/Realm/RLMSyncManager.mm +++ b/Realm/RLMSyncManager.mm @@ -23,6 +23,7 @@ #import "RLMUser_Private.hpp" #import "RLMSyncUtil_Private.hpp" #import "RLMUtil.hpp" +#import "RLMSyncConfiguration_Private.hpp" #import #import @@ -131,6 +132,7 @@ + (SyncClientConfig)configurationWithRootDirectory:(NSURL *)rootDirectory appId: config.user_agent_application_info = RLMStringDataWithNSString(appId); } + config.socket_factory = defaultSocketFactory(); return config; }