123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // Copyright 2014 Realm Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- ////////////////////////////////////////////////////////////////////////////
- #import "RLMRealm_Private.hpp"
- #import "RLMAnalytics.hpp"
- #import "RLMArray_Private.hpp"
- #import "RLMMigration_Private.h"
- #import "RLMObject_Private.h"
- #import "RLMObject_Private.hpp"
- #import "RLMObjectSchema_Private.hpp"
- #import "RLMObjectStore.h"
- #import "RLMObservation.hpp"
- #import "RLMProperty.h"
- #import "RLMProperty_Private.h"
- #import "RLMQueryUtil.hpp"
- #import "RLMRealmConfiguration_Private.hpp"
- #import "RLMRealmUtil.hpp"
- #import "RLMSchema_Private.hpp"
- #import "RLMThreadSafeReference_Private.hpp"
- #import "RLMUpdateChecker.hpp"
- #import "RLMUtil.hpp"
- #include "impl/realm_coordinator.hpp"
- #include "object_store.hpp"
- #include "schema.hpp"
- #include "shared_realm.hpp"
- #include "thread_safe_reference.hpp"
- #include <realm/disable_sync_to_disk.hpp>
- #include <realm/util/scope_exit.hpp>
- #include <realm/version.hpp>
- #if REALM_ENABLE_SYNC
- #import "RLMSyncManager_Private.h"
- #import "RLMSyncUtil_Private.hpp"
- #import "sync/async_open_task.hpp"
- #import "sync/sync_session.hpp"
- #endif
- using namespace realm;
- using util::File;
- @interface RLMRealmNotificationToken : RLMNotificationToken
- @property (nonatomic, strong) RLMRealm *realm;
- @property (nonatomic, copy) RLMNotificationBlock block;
- @end
- @interface RLMRealm ()
- @property (nonatomic, strong) NSHashTable<RLMRealmNotificationToken *> *notificationHandlers;
- - (void)sendNotifications:(RLMNotification)notification;
- @end
- void RLMDisableSyncToDisk() {
- realm::disable_sync_to_disk();
- }
- static void RLMAddSkipBackupAttributeToItemAtPath(std::string const& path) {
- [[NSURL fileURLWithPath:@(path.c_str())] setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
- }
- @implementation RLMRealmNotificationToken
- - (void)invalidate {
- [_realm verifyThread];
- [_realm.notificationHandlers removeObject:self];
- _realm = nil;
- _block = nil;
- }
- - (void)suppressNextNotification {
- // Temporarily replace the block with one which restores the old block
- // rather than producing a notification.
- // This briefly creates a retain cycle but it's fine because the block will
- // be synchronously called shortly after this method is called. Unlike with
- // collection notifications, this does not have to go through the object
- // store or do fancy things to handle transaction coalescing because it's
- // called synchronously by the obj-c code and not by the object store.
- auto notificationBlock = _block;
- _block = ^(RLMNotification, RLMRealm *) {
- _block = notificationBlock;
- };
- }
- - (void)dealloc {
- if (_realm || _block) {
- NSLog(@"RLMNotificationToken released without unregistering a notification. You must hold "
- @"on to the RLMNotificationToken returned from addNotificationBlock and call "
- @"-[RLMNotificationToken invalidate] when you no longer wish to receive RLMRealm notifications.");
- }
- }
- @end
- static bool shouldForciblyDisableEncryption() {
- static bool disableEncryption = getenv("REALM_DISABLE_ENCRYPTION");
- return disableEncryption;
- }
- NSData *RLMRealmValidatedEncryptionKey(NSData *key) {
- if (shouldForciblyDisableEncryption()) {
- return nil;
- }
- if (key && key.length != 64) {
- @throw RLMException(@"Encryption key must be exactly 64 bytes long");
- }
- return key;
- }
- @implementation RLMRealm {
- NSHashTable<RLMFastEnumerator *> *_collectionEnumerators;
- bool _sendingNotifications;
- }
- + (BOOL)isCoreDebug {
- return realm::Version::has_feature(realm::feature_Debug);
- }
- + (void)initialize {
- static bool initialized;
- if (initialized) {
- return;
- }
- initialized = true;
- RLMCheckForUpdates();
- RLMSendAnalytics();
- }
- - (instancetype)initPrivate {
- self = [super init];
- return self;
- }
- - (BOOL)isEmpty {
- return realm::ObjectStore::is_empty(self.group);
- }
- - (void)verifyThread {
- try {
- _realm->verify_thread();
- }
- catch (std::exception const& e) {
- @throw RLMException(e);
- }
- }
- - (BOOL)inWriteTransaction {
- return _realm->is_in_transaction();
- }
- - (realm::Group &)group {
- return _realm->read_group();
- }
- - (BOOL)autorefresh {
- return _realm->auto_refresh();
- }
- - (void)setAutorefresh:(BOOL)autorefresh {
- _realm->set_auto_refresh(autorefresh);
- }
- + (instancetype)defaultRealm {
- return [RLMRealm realmWithConfiguration:[RLMRealmConfiguration rawDefaultConfiguration] error:nil];
- }
- + (instancetype)realmWithURL:(NSURL *)fileURL {
- RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
- configuration.fileURL = fileURL;
- return [RLMRealm realmWithConfiguration:configuration error:nil];
- }
- // The server doesn't send us the subscriptions for permission types until the
- // first subscription is created. This is fine for synchronous opens (if we're
- // creating a new Realm we create the permission objects ourselves), but it
- // causes issues for asyncOpen because it means that when our download completes
- // we don't actually have the full Realm state yet.
- static void waitForPartialSyncSubscriptions(Realm::Config const& config) {
- #if REALM_ENABLE_SYNC
- auto realm = Realm::get_shared_realm(config);
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "__ResultSets");
- realm->begin_transaction();
- size_t row = realm::sync::create_object(realm->read_group(), *table);
- // Set expires_at to time 0 so that this object will be cleaned up the first
- // time the user creates a subscription
- size_t expires_at_col = table->get_column_index("expires_at");
- if (expires_at_col == npos) {
- expires_at_col = table->add_column(type_Timestamp, "expires_at", true);
- }
- table->set_timestamp(expires_at_col, row, Timestamp(0, 0));
- realm->commit_transaction();
- NotificationToken token;
- Results results(realm, *table);
- CFRunLoopRef runLoop = CFRunLoopGetCurrent();
- CFRunLoopPerformBlock(runLoop, kCFRunLoopDefaultMode, [&]() mutable {
- token = results.add_notification_callback([&](CollectionChangeSet const&, std::exception_ptr) mutable {
- if (table->size() > 1) {
- token = {};
- CFRunLoopStop(runLoop);
- }
- });
- });
- CFRunLoopRun();
- #else
- static_cast<void>(config);
- #endif
- }
- + (void)asyncOpenWithConfiguration:(RLMRealmConfiguration *)configuration
- callbackQueue:(dispatch_queue_t)callbackQueue
- callback:(RLMAsyncOpenRealmCallback)callback {
- static dispatch_queue_t queue = dispatch_queue_create("io.realm.asyncOpenDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
- auto openCompletion = [=](ThreadSafeReference<Realm> ref, std::exception_ptr err) {
- @autoreleasepool {
- if (err) {
- try {
- std::rethrow_exception(err);
- }
- catch (...) {
- NSError *error;
- RLMRealmTranslateException(&error);
- dispatch_async(callbackQueue, ^{
- callback(nil, error);
- });
- }
- return;
- }
- auto complete = ^{
- dispatch_async(callbackQueue, ^{
- @autoreleasepool {
- NSError *error;
- RLMRealm *localRealm = [RLMRealm realmWithConfiguration:configuration error:&error];
- callback(localRealm, error);
- }
- });
- };
- auto realm = Realm::get_shared_realm(std::move(ref));
- bool needsSubscriptions = realm->is_partial() && ObjectStore::table_for_object_type(realm->read_group(), "__ResultSets")->size() == 0;
- if (needsSubscriptions) {
- // We need to dispatch back to the work queue to wait for the
- // subscriptions as we're currently running on the sync worker
- // thread and blocking it to wait for subscriptions means no syncing
- dispatch_async(queue, ^{
- @autoreleasepool {
- waitForPartialSyncSubscriptions(realm->config());
- complete();
- }
- });
- }
- else {
- complete();
- }
- }
- };
- dispatch_async(queue, ^{
- @autoreleasepool {
- Realm::Config& config = configuration.config;
- if (config.sync_config) {
- #if REALM_ENABLE_SYNC
- realm::Realm::get_synchronized_realm(config)->start(openCompletion);
- #else
- @throw RLMException(@"Realm was not built with sync enabled");
- #endif
- }
- else {
- try {
- openCompletion(realm::_impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm(), nullptr);
- }
- catch (...) {
- openCompletion({}, std::current_exception());
- }
- }
- }
- });
- }
- // ARC tries to eliminate calls to autorelease when the value is then immediately
- // returned, but this results in significantly different semantics between debug
- // and release builds for RLMRealm, so force it to always autorelease.
- static id RLMAutorelease(__unsafe_unretained id value) {
- // +1 __bridge_retained, -1 CFAutorelease
- return value ? (__bridge id)CFAutorelease((__bridge_retained CFTypeRef)value) : nil;
- }
- + (instancetype)realmWithSharedRealm:(SharedRealm)sharedRealm schema:(RLMSchema *)schema {
- RLMRealm *realm = [[RLMRealm alloc] initPrivate];
- realm->_realm = sharedRealm;
- realm->_dynamic = YES;
- realm->_schema = schema;
- realm->_info = RLMSchemaInfo(realm);
- return RLMAutorelease(realm);
- }
- REALM_NOINLINE void RLMRealmTranslateException(NSError **error) {
- try {
- throw;
- }
- catch (RealmFileException const& ex) {
- switch (ex.kind()) {
- case RealmFileException::Kind::PermissionDenied:
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFilePermissionDenied, ex), error);
- break;
- case RealmFileException::Kind::IncompatibleLockFile: {
- NSString *err = @"Realm file is currently open in another process "
- "which cannot share access with this process. All "
- "processes sharing a single file must be the same "
- "architecture. For sharing files between the Realm "
- "Browser and an iOS simulator, this means that you "
- "must use a 64-bit simulator.";
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorIncompatibleLockFile,
- File::PermissionDenied(err.UTF8String, ex.path())), error);
- break;
- }
- case RealmFileException::Kind::NotFound:
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileNotFound, ex), error);
- break;
- case RealmFileException::Kind::Exists:
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileExists, ex), error);
- break;
- case RealmFileException::Kind::BadHistoryError: {
- NSString *err = @"Realm file's history format is incompatible with the "
- "settings in the configuration object being used to open "
- "the Realm. Note that Realms configured for sync cannot be "
- "opened as non-synced Realms, and vice versa. Otherwise, the "
- "file may be corrupt.";
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileAccess,
- File::AccessError(err.UTF8String, ex.path())), error);
- break;
- }
- case RealmFileException::Kind::AccessError:
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileAccess, ex), error);
- break;
- case RealmFileException::Kind::FormatUpgradeRequired:
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileFormatUpgradeRequired, ex), error);
- break;
- default:
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, ex), error);
- break;
- }
- }
- catch (AddressSpaceExhausted const &ex) {
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorAddressSpaceExhausted, ex), error);
- }
- catch (SchemaMismatchException const& ex) {
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorSchemaMismatch, ex), error);
- }
- catch (std::system_error const& ex) {
- RLMSetErrorOrThrow(RLMMakeError(ex), error);
- }
- catch (const std::exception &exp) {
- RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, exp), error);
- }
- }
- REALM_NOINLINE static void translateSharedGroupOpenException(RLMRealmConfiguration *originalConfiguration, NSError **error) {
- try {
- throw;
- }
- catch (RealmFileException const& ex) {
- switch (ex.kind()) {
- case RealmFileException::Kind::IncompatibleSyncedRealm: {
- RLMRealmConfiguration *configuration = [originalConfiguration copy];
- configuration.fileURL = [NSURL fileURLWithPath:@(ex.path().data())];
- configuration.readOnly = YES;
- NSError *intermediateError = RLMMakeError(RLMErrorIncompatibleSyncedFile, ex);
- NSMutableDictionary *userInfo = [intermediateError.userInfo mutableCopy];
- userInfo[RLMBackupRealmConfigurationErrorKey] = configuration;
- NSError *finalError = [NSError errorWithDomain:intermediateError.domain code:intermediateError.code
- userInfo:userInfo];
- RLMSetErrorOrThrow(finalError, error);
- break;
- }
- default:
- RLMRealmTranslateException(error);
- break;
- }
- }
- catch (...) {
- RLMRealmTranslateException(error);
- }
- }
- + (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error {
- bool dynamic = configuration.dynamic;
- bool cache = configuration.cache;
- bool readOnly = configuration.readOnly;
- {
- Realm::Config& config = configuration.config;
- // try to reuse existing realm first
- if (cache || dynamic) {
- if (RLMRealm *realm = RLMGetThreadLocalCachedRealmForPath(config.path)) {
- auto const& old_config = realm->_realm->config();
- if (old_config.immutable() != config.immutable()
- || old_config.read_only_alternative() != config.read_only_alternative()) {
- @throw RLMException(@"Realm at path '%s' already opened with different read permissions", config.path.c_str());
- }
- if (old_config.in_memory != config.in_memory) {
- @throw RLMException(@"Realm at path '%s' already opened with different inMemory settings", config.path.c_str());
- }
- if (realm->_dynamic != dynamic) {
- @throw RLMException(@"Realm at path '%s' already opened with different dynamic settings", config.path.c_str());
- }
- if (old_config.encryption_key != config.encryption_key) {
- @throw RLMException(@"Realm at path '%s' already opened with different encryption key", config.path.c_str());
- }
- return RLMAutorelease(realm);
- }
- }
- }
- configuration = [configuration copy];
- Realm::Config& config = configuration.config;
- RLMRealm *realm = [[self alloc] initPrivate];
- realm->_dynamic = dynamic;
- // protects the realm cache and accessors cache
- static std::mutex& initLock = *new std::mutex();
- std::lock_guard<std::mutex> lock(initLock);
- try {
- realm->_realm = Realm::get_shared_realm(config);
- }
- catch (...) {
- translateSharedGroupOpenException(configuration, error);
- return nil;
- }
- // if we have a cached realm on another thread we can skip a few steps and
- // just grab its schema
- @autoreleasepool {
- // ensure that cachedRealm doesn't end up in this thread's autorelease pool
- if (auto cachedRealm = RLMGetAnyCachedRealmForPath(config.path)) {
- realm->_realm->set_schema_subset(cachedRealm->_realm->schema());
- realm->_schema = cachedRealm.schema;
- realm->_info = cachedRealm->_info.clone(cachedRealm->_realm->schema(), realm);
- }
- }
- if (realm->_schema) { }
- else if (dynamic) {
- realm->_schema = [RLMSchema dynamicSchemaFromObjectStoreSchema:realm->_realm->schema()];
- realm->_info = RLMSchemaInfo(realm);
- }
- else {
- // set/align schema or perform migration if needed
- RLMSchema *schema = configuration.customSchema ?: RLMSchema.sharedSchema;
- Realm::MigrationFunction migrationFunction;
- auto migrationBlock = configuration.migrationBlock;
- if (migrationBlock && configuration.schemaVersion > 0) {
- migrationFunction = [=](SharedRealm old_realm, SharedRealm realm, Schema& mutableSchema) {
- RLMSchema *oldSchema = [RLMSchema dynamicSchemaFromObjectStoreSchema:old_realm->schema()];
- RLMRealm *oldRealm = [RLMRealm realmWithSharedRealm:old_realm schema:oldSchema];
- // The destination RLMRealm can't just use the schema from the
- // SharedRealm because it doesn't have information about whether or
- // not a class was defined in Swift, which effects how new objects
- // are created
- RLMRealm *newRealm = [RLMRealm realmWithSharedRealm:realm schema:schema.copy];
- [[[RLMMigration alloc] initWithRealm:newRealm oldRealm:oldRealm schema:mutableSchema] execute:migrationBlock];
- oldRealm->_realm = nullptr;
- newRealm->_realm = nullptr;
- };
- }
- try {
- realm->_realm->update_schema(schema.objectStoreCopy, config.schema_version,
- std::move(migrationFunction));
- }
- catch (...) {
- RLMRealmTranslateException(error);
- return nil;
- }
- realm->_schema = schema;
- realm->_info = RLMSchemaInfo(realm);
- RLMRealmCreateAccessors(realm.schema);
- if (!readOnly) {
- // initializing the schema started a read transaction, so end it
- [realm invalidate];
- RLMAddSkipBackupAttributeToItemAtPath(config.path + ".management");
- RLMAddSkipBackupAttributeToItemAtPath(config.path + ".lock");
- RLMAddSkipBackupAttributeToItemAtPath(config.path + ".note");
- }
- }
- if (cache) {
- RLMCacheRealm(config.path, realm);
- }
- if (!readOnly) {
- realm->_realm->m_binding_context = RLMCreateBindingContext(realm);
- realm->_realm->m_binding_context->realm = realm->_realm;
- }
- return RLMAutorelease(realm);
- }
- + (instancetype)uncachedSchemalessRealmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error {
- RLMRealm *realm = [[RLMRealm alloc] initPrivate];
- try {
- realm->_realm = Realm::get_shared_realm(configuration.config);
- }
- catch (...) {
- translateSharedGroupOpenException(configuration, error);
- return nil;
- }
- return realm;
- }
- + (void)resetRealmState {
- RLMClearRealmCache();
- realm::_impl::RealmCoordinator::clear_cache();
- [RLMRealmConfiguration resetRealmConfigurationState];
- }
- - (void)verifyNotificationsAreSupported:(bool)isCollection {
- [self verifyThread];
- if (_realm->config().immutable()) {
- @throw RLMException(@"Read-only Realms do not change and do not have change notifications");
- }
- if (!_realm->can_deliver_notifications()) {
- @throw RLMException(@"Can only add notification blocks from within runloops.");
- }
- if (isCollection && _realm->is_in_transaction()) {
- @throw RLMException(@"Cannot register notification blocks from within write transactions.");
- }
- }
- - (RLMNotificationToken *)addNotificationBlock:(RLMNotificationBlock)block {
- if (!block) {
- @throw RLMException(@"The notification block should not be nil");
- }
- [self verifyNotificationsAreSupported:false];
- _realm->read_group();
- if (!_notificationHandlers) {
- _notificationHandlers = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
- }
- RLMRealmNotificationToken *token = [[RLMRealmNotificationToken alloc] init];
- token.realm = self;
- token.block = block;
- [_notificationHandlers addObject:token];
- return token;
- }
- - (void)sendNotifications:(RLMNotification)notification {
- NSAssert(!_realm->config().immutable(), @"Read-only realms do not have notifications");
- if (_sendingNotifications) {
- return;
- }
- NSUInteger count = _notificationHandlers.count;
- if (count == 0) {
- return;
- }
- _sendingNotifications = true;
- auto cleanup = realm::util::make_scope_exit([&]() noexcept {
- _sendingNotifications = false;
- });
- // call this realm's notification blocks
- if (count == 1) {
- if (auto block = [_notificationHandlers.anyObject block]) {
- block(notification, self);
- }
- }
- else {
- for (RLMRealmNotificationToken *token in _notificationHandlers.allObjects) {
- if (auto block = token.block) {
- block(notification, self);
- }
- }
- }
- }
- - (RLMRealmConfiguration *)configuration {
- RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init];
- configuration.config = _realm->config();
- configuration.dynamic = _dynamic;
- configuration.customSchema = _schema;
- return configuration;
- }
- - (void)beginWriteTransaction {
- try {
- _realm->begin_transaction();
- }
- catch (std::exception &ex) {
- @throw RLMException(ex);
- }
- }
- - (void)commitWriteTransaction {
- [self commitWriteTransaction:nil];
- }
- - (BOOL)commitWriteTransaction:(NSError **)outError {
- try {
- _realm->commit_transaction();
- return YES;
- }
- catch (...) {
- RLMRealmTranslateException(outError);
- return NO;
- }
- }
- - (BOOL)commitWriteTransactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens error:(NSError **)error {
- for (RLMNotificationToken *token in tokens) {
- if (token.realm != self) {
- @throw RLMException(@"Incorrect Realm: only notifications for the Realm being modified can be skipped.");
- }
- [token suppressNextNotification];
- }
- try {
- _realm->commit_transaction();
- return YES;
- }
- catch (...) {
- RLMRealmTranslateException(error);
- return NO;
- }
- }
- - (void)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block {
- [self transactionWithBlock:block error:nil];
- }
- - (BOOL)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block error:(NSError **)outError {
- [self beginWriteTransaction];
- block();
- if (_realm->is_in_transaction()) {
- return [self commitWriteTransaction:outError];
- }
- return YES;
- }
- - (void)cancelWriteTransaction {
- try {
- _realm->cancel_transaction();
- }
- catch (std::exception &ex) {
- @throw RLMException(ex);
- }
- }
- - (void)invalidate {
- if (_realm->is_in_transaction()) {
- NSLog(@"WARNING: An RLMRealm instance was invalidated during a write "
- "transaction and all pending changes have been rolled back.");
- }
- [self detachAllEnumerators];
- for (auto& objectInfo : _info) {
- for (RLMObservationInfo *info : objectInfo.second.observedObjects) {
- info->willChange(RLMInvalidatedKey);
- }
- }
- _realm->invalidate();
- for (auto& objectInfo : _info) {
- for (RLMObservationInfo *info : objectInfo.second.observedObjects) {
- info->didChange(RLMInvalidatedKey);
- }
- objectInfo.second.releaseTable();
- }
- }
- - (nullable id)resolveThreadSafeReference:(RLMThreadSafeReference *)reference {
- return [reference resolveReferenceInRealm:self];
- }
- /**
- Replaces all string columns in this Realm with a string enumeration column and compacts the
- database file.
- Cannot be called from a write transaction.
- Compaction will not occur if other `RLMRealm` instances exist.
- While compaction is in progress, attempts by other threads or processes to open the database will
- wait.
- Be warned that resource requirements for compaction is proportional to the amount of live data in
- the database.
- Compaction works by writing the database contents to a temporary database file and then replacing
- the database with the temporary one. The name of the temporary file is formed by appending
- `.tmp_compaction_space` to the name of the database.
- @return YES if the compaction succeeded.
- */
- - (BOOL)compact {
- // compact() automatically ends the read transaction, but we need to clean
- // up cached state and send invalidated notifications when that happens, so
- // explicitly end it first unless we're in a write transaction (in which
- // case compact() will throw an exception)
- if (!_realm->is_in_transaction()) {
- [self invalidate];
- }
- try {
- return _realm->compact();
- }
- catch (std::exception const& ex) {
- @throw RLMException(ex);
- }
- }
- - (void)dealloc {
- if (_realm) {
- if (_realm->is_in_transaction()) {
- [self cancelWriteTransaction];
- NSLog(@"WARNING: An RLMRealm instance was deallocated during a write transaction and all "
- "pending changes have been rolled back. Make sure to retain a reference to the "
- "RLMRealm for the duration of the write transaction.");
- }
- }
- }
- - (BOOL)refresh {
- return _realm->refresh();
- }
- - (void)addObject:(__unsafe_unretained RLMObject *const)object {
- RLMAddObjectToRealm(object, self, RLMUpdatePolicyError);
- }
- - (void)addObjects:(id<NSFastEnumeration>)objects {
- for (RLMObject *obj in objects) {
- if (![obj isKindOfClass:RLMObjectBase.class]) {
- @throw RLMException(@"Cannot insert objects of type %@ with addObjects:. Only RLMObjects are supported.",
- NSStringFromClass(obj.class));
- }
- [self addObject:obj];
- }
- }
- - (void)addOrUpdateObject:(RLMObject *)object {
- // verify primary key
- if (!object.objectSchema.primaryKeyProperty) {
- @throw RLMException(@"'%@' does not have a primary key and can not be updated", object.objectSchema.className);
- }
- RLMAddObjectToRealm(object, self, RLMUpdatePolicyUpdateAll);
- }
- - (void)addOrUpdateObjects:(id<NSFastEnumeration>)objects {
- for (RLMObject *obj in objects) {
- if (![obj isKindOfClass:RLMObjectBase.class]) {
- @throw RLMException(@"Cannot add or update objects of type %@ with addOrUpdateObjects:. Only RLMObjects are"
- " supported.",
- NSStringFromClass(obj.class));
- }
- [self addOrUpdateObject:obj];
- }
- }
- - (void)deleteObject:(RLMObject *)object {
- RLMDeleteObjectFromRealm(object, self);
- }
- - (void)deleteObjects:(id<NSFastEnumeration>)objects {
- id idObjects = objects;
- if ([idObjects respondsToSelector:@selector(realm)]
- && [idObjects respondsToSelector:@selector(deleteObjectsFromRealm)]) {
- if (self != (RLMRealm *)[idObjects realm]) {
- @throw RLMException(@"Can only delete objects from the Realm they belong to.");
- }
- [idObjects deleteObjectsFromRealm];
- return;
- }
- if (auto array = RLMDynamicCast<RLMArray>(objects)) {
- if (array.type != RLMPropertyTypeObject) {
- @throw RLMException(@"Cannot delete objects from RLMArray<%@>: only RLMObjects can be deleted.",
- RLMTypeToString(array.type));
- }
- }
- for (RLMObject *obj in objects) {
- if (![obj isKindOfClass:RLMObjectBase.class]) {
- @throw RLMException(@"Cannot delete objects of type %@ with deleteObjects:. Only RLMObjects can be deleted.",
- NSStringFromClass(obj.class));
- }
- RLMDeleteObjectFromRealm(obj, self);
- }
- }
- - (void)deleteAllObjects {
- RLMDeleteAllObjectsFromRealm(self);
- }
- - (RLMResults *)allObjects:(NSString *)objectClassName {
- return RLMGetObjects(self, objectClassName, nil);
- }
- - (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat, ... {
- va_list args;
- va_start(args, predicateFormat);
- RLMResults *results = [self objects:objectClassName where:predicateFormat args:args];
- va_end(args);
- return results;
- }
- - (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat args:(va_list)args {
- return [self objects:objectClassName withPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
- }
- - (RLMResults *)objects:(NSString *)objectClassName withPredicate:(NSPredicate *)predicate {
- return RLMGetObjects(self, objectClassName, predicate);
- }
- - (RLMObject *)objectWithClassName:(NSString *)className forPrimaryKey:(id)primaryKey {
- return RLMGetObject(self, className, primaryKey);
- }
- + (uint64_t)schemaVersionAtURL:(NSURL *)fileURL encryptionKey:(NSData *)key error:(NSError **)error {
- RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init];
- try {
- config.fileURL = fileURL;
- config.encryptionKey = RLMRealmValidatedEncryptionKey(key);
- uint64_t version = Realm::get_schema_version(config.config);
- if (version == realm::ObjectStore::NotVersioned) {
- RLMSetErrorOrThrow([NSError errorWithDomain:RLMErrorDomain code:RLMErrorFail userInfo:@{NSLocalizedDescriptionKey:@"Cannot open an uninitialized realm in read-only mode"}], error);
- }
- return version;
- }
- catch (...) {
- translateSharedGroupOpenException(config, error);
- return RLMNotVersioned;
- }
- }
- + (BOOL)performMigrationForConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error {
- if (RLMGetAnyCachedRealmForPath(configuration.config.path)) {
- @throw RLMException(@"Cannot migrate Realms that are already open.");
- }
- NSError *localError; // Prevents autorelease
- BOOL success;
- @autoreleasepool {
- success = [RLMRealm realmWithConfiguration:configuration error:&localError] != nil;
- }
- if (!success && error) {
- *error = localError; // Must set outside pool otherwise will free anyway
- }
- return success;
- }
- - (RLMObject *)createObject:(NSString *)className withValue:(id)value {
- return (RLMObject *)RLMCreateObjectInRealmWithValue(self, className, value, RLMUpdatePolicyError);
- }
- - (BOOL)writeCopyToURL:(NSURL *)fileURL encryptionKey:(NSData *)key error:(NSError **)error {
- key = RLMRealmValidatedEncryptionKey(key);
- NSString *path = fileURL.path;
- try {
- _realm->write_copy(path.UTF8String, {static_cast<const char *>(key.bytes), key.length});
- return YES;
- }
- catch (...) {
- __autoreleasing NSError *dummyError;
- if (!error) {
- error = &dummyError;
- }
- RLMRealmTranslateException(error);
- return NO;
- }
- return NO;
- }
- #if REALM_ENABLE_SYNC
- using Privilege = realm::ComputedPrivileges;
- static bool hasPrivilege(realm::ComputedPrivileges actual, realm::ComputedPrivileges expected) {
- return (static_cast<int>(actual) & static_cast<int>(expected)) == static_cast<int>(expected);
- }
- - (RLMRealmPrivileges)privilegesForRealm {
- auto p = _realm->get_privileges();
- return {
- .read = hasPrivilege(p, Privilege::Read),
- .update = hasPrivilege(p, Privilege::Update),
- .setPermissions = hasPrivilege(p, Privilege::SetPermissions),
- .modifySchema = hasPrivilege(p, Privilege::ModifySchema),
- };
- }
- - (RLMObjectPrivileges)privilegesForObject:(RLMObject *)object {
- RLMVerifyAttached(object);
- auto p = _realm->get_privileges(object->_row);
- return {
- .read = hasPrivilege(p, Privilege::Read),
- .update = hasPrivilege(p, Privilege::Update),
- .del = hasPrivilege(p, Privilege::Delete),
- .setPermissions = hasPrivilege(p, Privilege::Delete),
- };
- }
- - (RLMClassPrivileges)privilegesForClass:(Class)cls {
- if (![cls respondsToSelector:@selector(_realmObjectName)]) {
- @throw RLMException(@"Cannot get privileges for non-RLMObject class %@", cls);
- }
- return [self privilegesForClassNamed:[cls _realmObjectName] ?: [cls className]];
- }
- - (RLMClassPrivileges)privilegesForClassNamed:(NSString *)className {
- auto p = _realm->get_privileges(className.UTF8String);
- return {
- .read = hasPrivilege(p, Privilege::Read),
- .update = hasPrivilege(p, Privilege::Update),
- .setPermissions = hasPrivilege(p, Privilege::SetPermissions),
- .subscribe = hasPrivilege(p, Privilege::Query),
- .create = hasPrivilege(p, Privilege::Create),
- };
- }
- #endif
- - (void)registerEnumerator:(RLMFastEnumerator *)enumerator {
- if (!_collectionEnumerators) {
- _collectionEnumerators = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
- }
- [_collectionEnumerators addObject:enumerator];
- }
- - (void)unregisterEnumerator:(RLMFastEnumerator *)enumerator {
- [_collectionEnumerators removeObject:enumerator];
- }
- - (void)detachAllEnumerators {
- for (RLMFastEnumerator *enumerator in _collectionEnumerators) {
- [enumerator detach];
- }
- _collectionEnumerators = nil;
- }
- @end
|